diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index e9ac52c28..000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,13 +0,0 @@ -[build] -rustflags = ["--cfg", "tokio_unstable", "-D", "warnings"] - -[unstable] -bindeps = true - -# Linker wrappers for musl targets. On Linux hosts these use the system cc directly; -# on non-Linux hosts (macOS, Windows) they cross-compile via cargo-zigbuild. -[target.x86_64-unknown-linux-musl] -rustflags = ["-C", "linker=.cargo/zigcc-x86_64-unknown-linux-musl"] - -[target.aarch64-unknown-linux-musl] -rustflags = ["-C", "linker=.cargo/zigcc-aarch64-unknown-linux-musl"] diff --git a/.cargo/zigcc-aarch64-unknown-linux-musl b/.cargo/zigcc-aarch64-unknown-linux-musl deleted file mode 100755 index 6dba882b7..000000000 --- a/.cargo/zigcc-aarch64-unknown-linux-musl +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -# Linker wrapper for aarch64-unknown-linux-musl targets. -# On Linux, use the system cc directly. On other hosts, cross-compile via cargo-zigbuild. -if [ "$(uname -s)" = "Linux" ]; then - exec cc "$@" -fi -exec cargo-zigbuild zig cc -- -fno-sanitize=all -target aarch64-linux-musl "$@" diff --git a/.cargo/zigcc-x86_64-unknown-linux-musl b/.cargo/zigcc-x86_64-unknown-linux-musl deleted file mode 100755 index c7e04fda2..000000000 --- a/.cargo/zigcc-x86_64-unknown-linux-musl +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -# Linker wrapper for x86_64-unknown-linux-musl targets. -# On Linux, use the system cc directly. On other hosts, cross-compile via cargo-zigbuild. -if [ "$(uname -s)" = "Linux" ]; then - exec cc "$@" -fi -exec cargo-zigbuild zig cc -- -fno-sanitize=all -target x86_64-linux-musl "$@" diff --git a/.claude/agents/monorepo-architect.md b/.claude/agents/monorepo-architect.md deleted file mode 100644 index 3afe17a79..000000000 --- a/.claude/agents/monorepo-architect.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -name: monorepo-architect -description: Use this agent when you need architectural guidance for monorepo tooling, particularly for reviewing code organization, module boundaries, and ensuring proper separation of concerns in Rust/Node.js projects. This agent should be invoked after implementing new features or refactoring existing code to validate architectural decisions and placement of functionality.\n\nExamples:\n- \n Context: The user has just implemented a new caching mechanism for the monorepo task runner.\n user: "I've added a new caching system to handle task outputs"\n assistant: "I'll use the monorepo-architect agent to review the architectural decisions and ensure the caching logic is properly placed within the module structure."\n \n Since new functionality was added, use the monorepo-architect agent to review the code architecture and module boundaries.\n \n\n- \n Context: The user is refactoring the task dependency resolution system.\n user: "I've refactored how we resolve task dependencies across packages"\n assistant: "Let me invoke the monorepo-architect agent to review the refactored code and ensure proper separation of concerns."\n \n After refactoring core functionality, use the monorepo-architect agent to validate architectural decisions.\n \n\n- \n Context: The user is adding cross-package communication features.\n user: "I've implemented a new IPC mechanism for packages to communicate during builds"\n assistant: "I'll use the monorepo-architect agent to review where this IPC logic lives and ensure it doesn't create inappropriate cross-module dependencies."\n \n When adding features that span multiple modules, use the monorepo-architect agent to prevent architectural violations.\n \n -model: opus -color: purple ---- - -You are a senior software architect with deep expertise in Rust and Node.js ecosystems, specializing in monorepo tooling and build systems. You have extensively studied and analyzed the architectures of nx, Turborepo, Rush, and Lage, understanding their design decisions, trade-offs, and implementation patterns. - -Your primary responsibility is to review code architecture and ensure that functionality is properly organized within the codebase. You focus on: - -**Core Architectural Principles:** - -- Single Responsibility: Each module, file, and function should have one clear purpose -- Separation of Concerns: Business logic, I/O operations, and configuration should be clearly separated -- Module Boundaries: Enforce clean interfaces between modules, preventing tight coupling -- Dependency Direction: Dependencies should flow in one direction, typically from high-level to low-level modules - -**When reviewing code, you will:** - -1. **Analyze Module Structure**: Examine where new functionality has been placed and determine if it belongs there based on the module's responsibility. Look for code that crosses logical boundaries or mixes concerns. - -2. **Identify Architectural Violations**: - - Cross-module responsibilities where one module is doing work that belongs to another - - Circular dependencies or bidirectional coupling - - Business logic mixed with I/O operations - - Configuration logic scattered across multiple modules - - Violation of the dependency inversion principle - -3. **Suggest Proper Placement**: When you identify misplaced functionality, provide specific recommendations: - - Identify the correct module/file where the code should reside - - Explain why the current placement violates architectural principles - - Suggest how to refactor without breaking existing functionality - - Consider the impact on testing and maintainability - -4. **Reference Industry Standards**: Draw from your knowledge of nx, Turborepo, Rush, and Lage to: - - Compare architectural decisions with proven patterns from these tools - - Highlight when a different approach might be more scalable or maintainable - - Suggest battle-tested patterns for common monorepo challenges - -5. **Focus on Rust/Node.js Best Practices**: - - In Rust: Ensure proper use of ownership, traits for abstraction, and module organization - - In Node.js: Validate CommonJS/ESM module patterns, async patterns, and package boundaries - - For interop: Review FFI boundaries and data serialization approaches - -**Review Methodology:** - -1. Start by understanding the intent of the recent changes -2. Map out the affected modules and their responsibilities -3. Identify any code that seems out of place or creates inappropriate coupling -4. Provide a prioritized list of architectural concerns (critical, important, minor) -5. For each concern, explain the principle being violated and suggest a concrete fix - -**Output Format:** - -Structure your review as: - -- **Summary**: Brief overview of architectural health -- **Critical Issues**: Must-fix architectural violations that will cause problems -- **Recommendations**: Suggested improvements with rationale -- **Positive Patterns**: Acknowledge well-architected decisions -- **Comparison Notes**: When relevant, note how similar problems are solved in nx/Turborepo/Rush/Lage - -You are pragmatic and understand that perfect architecture must be balanced with delivery speed. Focus on issues that will genuinely impact maintainability, testability, or scalability. Avoid nitpicking and recognize when 'good enough' is appropriate for the current stage of the project. - -When you lack context about the broader system, ask clarifying questions rather than making assumptions. Your goal is to ensure the codebase remains maintainable and follows established architectural patterns while evolving to meet new requirements. diff --git a/.claude/skills/update-changelog.md b/.claude/skills/update-changelog.md deleted file mode 100644 index da4805658..000000000 --- a/.claude/skills/update-changelog.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -description: Update CHANGELOG.md with a new entry for the current change -user_invocable: true ---- - -# Update Changelog - -Add a new entry to `CHANGELOG.md` for the change being made in the current branch. - -## Instructions - -1. Read `CHANGELOG.md` to understand the current format. -2. Determine the appropriate category for the change: - - **Added** — new user-facing feature or capability - - **Changed** — modification to existing user-facing behavior - - **Removed** — removal of user-facing feature or option - - **Fixed** — bug fix affecting users - - **Perf** — performance improvement noticeable to users -3. Write a concise, user-facing description. Focus on what changed from the user's perspective, not implementation details. -4. Include a PR link in the format `([#NNN](https://github.com/voidzero-dev/vite-task/pull/NNN))`. If the PR number is not yet known, leave a `([#???](https://github.com/voidzero-dev/vite-task/pull/???))` placeholder. -5. Insert the new entry at the **top** of the existing list in `CHANGELOG.md` (newest first). -6. If the current change is closely related to an existing entry (e.g., multiple PRs contributing to the same feature or fix), group them into a single item with multiple PR links rather than adding a separate entry. - -## What NOT to include - -Do not add entries for: - -- Internal refactors with no user-facing effect -- CI/CD changes -- Dependency bumps -- Test-only fixes (flaky tests, test infrastructure) -- Documentation changes (CLAUDE.md, README, etc.) -- Chore/tooling changes - -The changelog is for **end-users only**. - -## Entry format - -``` -- **Category** description ([#NNN](https://github.com/voidzero-dev/vite-task/pull/NNN)) -``` diff --git a/.clippy.toml b/.clippy.toml deleted file mode 100644 index 8965e2866..000000000 --- a/.clippy.toml +++ /dev/null @@ -1,24 +0,0 @@ -avoid-breaking-exported-api = false - -disallowed-methods = [ - { path = "str::to_ascii_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_lowercase` instead." }, - { path = "str::to_ascii_uppercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_uppercase` instead." }, - { path = "str::to_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_lowercase` instead." }, - { path = "str::to_uppercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_uppercase` instead." }, - { path = "str::replace", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_replace` instead." }, - { path = "str::replacen", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_replacen` instead." }, - { path = "std::env::current_dir", reason = "To get an `AbsolutePathBuf`, Use `vite_path::current_dir` instead." }, - { path = "std::env::vars_os", reason = "Read process env only in `Session::init`, then use the session env snapshot downstream." }, - { path = "std::thread::sleep", reason = "Use proper synchronization (channels, condvars, etc.) instead of sleeping. Sleep is only acceptable for intentional user-facing delays (e.g. animations, debouncing)." }, - { path = "tokio::time::sleep", reason = "Use proper synchronization (channels, signals, etc.) instead of sleeping. Sleep is only acceptable for intentional user-facing delays (e.g. animations, debouncing)." }, -] - -disallowed-types = [ - { path = "std::collections::HashMap", reason = "Use `rustc_hash::FxHashMap` instead, which is typically faster." }, - { path = "std::collections::HashSet", reason = "Use `rustc_hash::FxHashSet` instead, which is typically faster." }, - { path = "std::path::Path", reason = "Use `vite_path::RelativePath` or `vite_path::AbsolutePath` instead" }, - { path = "std::path::PathBuf", reason = "Use `vite_path::RelativePathBuf` or `vite_path::AbsolutePathBuf` instead" }, - { path = "std::string::String", reason = "Use `vite_str::Str` for small strings. For large strings, prefer `Box/Rc/Arc` if mutation is not needed." }, -] - -disallowed-macros = [{ path = "std::format", reason = "Use `vite_str::format` for small strings." }] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index bc402cf2a..000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,48 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/rust -{ - "name": "Rust", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu-22.04", - "updateContentCommand": { - "rustToolchain": "rustup show" - }, - "containerEnv": { - "CARGO_TARGET_DIR": "/tmp/target" - }, - "features": { - "ghcr.io/devcontainers/features/rust:1": {}, - "ghcr.io/devcontainers-extra/features/fish-apt-get:1": {} - }, - "customizations": { - "vscode": { - "extensions": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml", "fill-labs.dependi"], - "settings": { - "terminal.integrated.defaultProfile.linux": "fish", - "terminal.integrated.profiles.linux": { - "fish": { - "path": "/usr/bin/fish" - } - } - } - } - } - // Use 'mounts' to make the cargo cache persistent in a Docker Volume. - // "mounts": [ - // { - // "source": "devcontainer-cargo-cache-${devcontainerId}", - // "target": "/usr/local/cargo", - // "type": "volume" - // } - // ] - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "rustc --version", - // Configure tool-specific properties. - // "customizations": {}, - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index 20ca7872e..000000000 --- a/.github/renovate.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["github>Boshen/renovate"], - "ignorePaths": ["**/fixtures/**"] -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index e941e6b90..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,388 +0,0 @@ -name: CI - -permissions: - # Doing it explicitly because the default permission only includes metadata: read. - contents: read - -on: - workflow_dispatch: - pull_request: - types: [opened, synchronize] - push: - branches: - - main - paths-ignore: - - '**/*.md' - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: ${{ github.ref_name != 'main' }} - -defaults: - run: - shell: bash - -jobs: - detect-changes: - runs-on: namespace-profile-linux-x64-default - permissions: - contents: read - pull-requests: read - outputs: - code-changed: ${{ steps.filter.outputs.code }} - steps: - - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 - id: filter - with: - filters: | - code: - - '!**/*.md' - - clippy: - needs: detect-changes - if: needs.detect-changes.outputs.code-changed == 'true' - name: Clippy - runs-on: namespace-profile-linux-x64-default - steps: - - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - - name: Update submodules - run: git submodule update --init --recursive - - - uses: oxc-project/setup-rust@3d6fb132fbe7cdcb66bf8ec193911c2945369d12 # v1.0.17 - with: - save-cache: ${{ github.ref_name == 'main' }} - cache-key: clippy - components: clippy - - - run: rustup target add x86_64-unknown-linux-musl - - run: pipx install cargo-zigbuild - # pipx isolates cargo-zigbuild in its own venv, so its ziglang dependency - # (which bundles zig) isn't on PATH. Install zig separately. - - uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 - - # --locked: verify Cargo.lock is up to date (replaces the removed `cargo check --locked`) - - run: cargo clippy --locked --all-targets --all-features -- -D warnings - - test: - needs: detect-changes - if: needs.detect-changes.outputs.code-changed == 'true' - name: Test (${{ matrix.shard }}) - env: - # Playwright detects the physical Apple Silicon CPU under Rosetta. Force - # its x64 browser for the x86_64 shard so fspy can inject its x64 preload. - PLAYWRIGHT_HOST_PLATFORM_OVERRIDE: ${{ matrix.target == 'x86_64-apple-darwin' && 'mac15' || '' }} - strategy: - fail-fast: false - matrix: - include: - - os: namespace-profile-linux-x64-default - target: x86_64-unknown-linux-gnu - cargo_cmd: cargo-zigbuild - build_target: x86_64-unknown-linux-gnu.2.17 - shard: linux-gnu - - os: namespace-profile-mac-default - target: aarch64-apple-darwin - cargo_cmd: cargo - build_target: aarch64-apple-darwin - shard: macos-arm64 - - os: namespace-profile-mac-default - target: x86_64-apple-darwin - cargo_cmd: cargo - build_target: x86_64-apple-darwin - shard: macos-x64 - runs-on: ${{ matrix.os }} - steps: - - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - - name: Update submodules - run: git submodule update --init --recursive - - - uses: oxc-project/setup-rust@3d6fb132fbe7cdcb66bf8ec193911c2945369d12 # v1.0.17 - with: - save-cache: ${{ github.ref_name == 'main' }} - cache-key: test - - - run: rustup target add ${{ matrix.target }} - - - run: rustup target add x86_64-unknown-linux-musl - if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} - - - run: pipx install cargo-zigbuild - if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} - - # pipx isolates cargo-zigbuild in its own venv, so its ziglang dependency - # (which bundles zig) isn't on PATH. Install zig separately. - - uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 - if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} - - - name: Build tests - run: ${{ matrix.cargo_cmd }} test --no-run --target ${{ matrix.build_target }} - - # Default `cargo test` runs only tests that need nothing beyond the - # Rust toolchain; this step verifies that contract before Node.js - # and pnpm enter the picture. - - name: Run tests - run: ${{ matrix.cargo_cmd }} test --target ${{ matrix.build_target }} - - # x86_64-apple-darwin runs on arm64 runner under Rosetta; install x64 Node - # so fspy's x86_64 preload dylib can be injected into spawned node procs. - - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 - with: - architecture: ${{ matrix.target == 'x86_64-apple-darwin' && 'x64' || '' }} - - - name: Install runtime binaries - run: | - if [[ '${{ matrix.target }}' == 'x86_64-apple-darwin' ]]; then - # Bun normally prefers the native arm64 binary under Rosetta, but - # this job needs x64 Bun so the x64 preload dylib can be injected. - PATH="$(dirname "$(command -v node)"):$PNPM_HOME:/usr/bin:/bin" \ - pnpm --filter vite-task-tools rebuild --pending - else - pnpm --filter vite-task-tools rebuild --pending - fi - - - name: Install Chromium system dependencies - if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} - run: pnpm --filter vite-task-tools exec playwright install-deps chromium - - - name: Run ignored tests - run: ${{ matrix.cargo_cmd }} test --target ${{ matrix.build_target }} -- --ignored - - # Windows tests are cross-compiled on a fast Linux runner with cargo-xwin - # (clang-cl + lld-link against the xwin-downloaded MSVC CRT/Windows SDK) - # and packed into a portable nextest archive. The Windows runners then only - # download the archive and run it: no Rust toolchain or compilation on the - # slow runners. Two nextest partitions avoid most repeated setup while keeping - # enough parallelism for the Windows-heavy default test workload. The much - # smaller Node-backed ignored suite runs once in its own job, avoiding - # duplicate Windows Node setup. - build-windows-tests: - needs: detect-changes - if: needs.detect-changes.outputs.code-changed == 'true' - name: Build Windows tests - runs-on: namespace-profile-linux-x64-default - env: - XWIN_ACCEPT_LICENSE: '1' - # The MSVC STL from xwin's VS17 manifest static-asserts a minimum Clang - # version newer than what runner images ship. Microsoft's documented - # escape hatch lets Detours (the only C++ in the workspace, and far older - # than any STL/clang concern here) compile with the system clang-cl. - # cargo-xwin folds a pre-set CXXFLAGS into the env it generates. - CXXFLAGS: -D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH - steps: - - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - - name: Update submodules - run: git submodule update --init --recursive - - # llvm-tools provides llvm-ar in the toolchain's rustlib bin dir; - # cargo-xwin symlinks the MSVC-style llvm-lib/llvm-dlltool (used by cc-rs - # to archive Detours) and lld-link from there when the runner image has - # no system LLVM. - - uses: oxc-project/setup-rust@3d6fb132fbe7cdcb66bf8ec193911c2945369d12 # v1.0.17 - with: - save-cache: ${{ github.ref_name == 'main' }} - cache-key: windows-cross - components: llvm-tools - - - run: rustup target add x86_64-pc-windows-msvc - - - uses: taiki-e/install-action@16b05812d776ae1dfaabc8277e421fb6d2506419 # v2.82.7 - with: - tool: cargo-nextest,cargo-xwin - - # Downloading and unpacking the MSVC CRT/Windows SDK dominates this - # job's wall time on a cold cache; the unpacked result is immutable for a - # given manifest version, so cache it. Bump the key when bumping the - # xwin version. - - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: xwin-cache - with: - path: ~/.cache/cargo-xwin - key: cargo-xwin-msvc-17 - - - name: Build test archive - run: | - # cargo-xwin resolves the rustflags configured in .cargo/config.toml, - # appends its -Lnative Windows SDK paths, and re-exports the result as - # CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS, so the plain cargo - # that nextest invokes cross-compiles exactly like `cargo xwin` would. - eval "$(cargo xwin env --target x86_64-pc-windows-msvc | grep '^export ')" - # `cargo xwin env` renders its intended *removal* of RUSTFLAGS as - # `export RUSTFLAGS="";` — actually remove it so it cannot shadow the - # target-specific rustflags set above. - unset RUSTFLAGS - cargo nextest archive --workspace --target x86_64-pc-windows-msvc --archive-file windows-tests.tar.zst - - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: windows-test-archive - path: windows-tests.tar.zst - retention-days: 7 - - - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - if: steps.xwin-cache.outputs.cache-hit != 'true' - with: - path: ~/.cache/cargo-xwin - key: cargo-xwin-msvc-17 - - test-windows: - needs: build-windows-tests - name: Test (${{ matrix.shard }}) - strategy: - fail-fast: false - matrix: - include: - - shard: windows-1 - partition: count:1/2 - mode: default - - shard: windows-2 - partition: count:2/2 - mode: default - - shard: windows-ignored - partition: '' - mode: ignored - runs-on: namespace-profile-windows-4c-8g - env: - PARTITION: ${{ matrix.partition }} - steps: - - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - # The Detours submodule is needed at run time: fspy_detours_sys's - # bindings test re-generates bindgen bindings against the checked-out - # headers. - - name: Update submodules - run: git submodule update --init --recursive - - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: windows-test-archive - - - uses: taiki-e/install-action@16b05812d776ae1dfaabc8277e421fb6d2506419 # v2.82.7 - with: - tool: cargo-nextest - - # The default (non-ignored) test set needs nothing beyond the test - # binaries themselves; this step verifies that contract before Node.js - # and pnpm enter the picture. `cargo-nextest` is invoked directly so the - # job never depends on the runner's Rust toolchain. - - name: Run tests - if: matrix.mode == 'default' - run: cargo-nextest nextest run --archive-file windows-tests.tar.zst --workspace-remap . --partition "$PARTITION" - - - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 - if: matrix.mode == 'ignored' - - - name: Install runtime binaries - if: matrix.mode == 'ignored' - run: pnpm --filter vite-task-tools rebuild --pending - - - name: Run ignored tests - if: matrix.mode == 'ignored' - run: cargo-nextest nextest run --archive-file windows-tests.tar.zst --workspace-remap . --run-ignored ignored-only - - test-musl: - needs: detect-changes - if: needs.detect-changes.outputs.code-changed == 'true' - name: Test (musl) - runs-on: namespace-profile-linux-x64-default - container: - image: node:25-alpine3.21 - options: --shm-size=256m # shm_io tests need bigger shared memory - env: - # Override all rustflags to skip the zig cross-linker from .cargo/config.toml. - # Alpine's cc is already musl-native, so no custom linker is needed. - # Must mirror [build].rustflags and target rustflags from .cargo/config.toml - # (RUSTFLAGS env var overrides both levels). - # -crt-static: vite-task is shipped as a NAPI module in vite+, and musl Node - # with native modules links to musl libc dynamically, so we must do the same. - RUSTFLAGS: --cfg tokio_unstable -D warnings -C target-feature=-crt-static - # On musl, concurrent PTY operations can trigger SIGSEGV in musl internals. - # Run test threads sequentially to avoid the race. - RUST_TEST_THREADS: 1 - # Playwright's bundled Chromium does not support musl. The browser fixture - # is filtered out there, so avoid downloading an unusable glibc binary. - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - steps: - - name: Install Alpine dependencies - shell: sh {0} - run: apk add --no-cache bash curl git musl-dev gcc g++ python3 - - - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - - name: Update submodules - run: git submodule update --init --recursive - - - name: Install rustup - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none - echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - - - name: Install Rust toolchain - run: rustup show - - - name: Build tests - run: cargo test --no-run - - # Default `cargo test` runs only tests that need nothing beyond the - # Rust toolchain; this step verifies that contract before pnpm - # populates `packages/tools/node_modules`. - - name: Run tests - run: cargo test - - - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 - - - name: Run ignored tests - run: cargo test -- --ignored - - fmt: - name: Format and Check Deps - runs-on: namespace-profile-linux-x64-default - steps: - - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - - name: Update submodules - run: git submodule update --init --recursive - - - uses: oxc-project/setup-rust@3d6fb132fbe7cdcb66bf8ec193911c2945369d12 # v1.0.17 - with: - save-cache: ${{ github.ref_name == 'main' }} - cache-key: fmt - tools: cargo-shear@1.11.1,cargo-autoinherit@0.1.6 - components: clippy rust-docs rustfmt - - - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 - - run: pnpm exec vp fmt --check - - run: cargo autoinherit && git diff --exit-code - - run: cargo shear --deny-warnings - - run: cargo fmt --check - - run: RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --document-private-items - - - uses: crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # v1.48.0 - with: - files: . - - - name: Deduplicate dependencies - run: pnpm dedupe --check - - - name: Check vite-task-client types are not stale - run: | - pnpm build-vite-task-client-types - git diff --exit-code packages/vite-task-client/src/index.d.ts - - done: - runs-on: namespace-profile-linux-x64-default - if: always() - needs: - - clippy - - test - - test-musl - - build-windows-tests - - test-windows - - fmt - steps: - - run: exit 1 - # Thank you, next https://github.com/vercel/next.js/blob/canary/.github/workflows/build_and_test.yml#L379 - if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }} diff --git a/.github/workflows/cleanup-cache.yml b/.github/workflows/cleanup-cache.yml deleted file mode 100644 index 6909d3157..000000000 --- a/.github/workflows/cleanup-cache.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Cleanup github runner caches on closed pull requests -on: - pull_request: - types: - - closed - -jobs: - cleanup: - runs-on: ubuntu-latest - permissions: - actions: write - steps: - - name: Cleanup - run: | - echo "Fetching list of cache keys" - cacheKeysForPR=$(gh cache list --ref $BRANCH --limit 100 --json id --jq '.[].id') - - ## Setting this to not fail the workflow while deleting cache keys. - set +e - echo "Deleting caches..." - for cacheKey in $cacheKeysForPR - do - gh cache delete $cacheKey - done - echo "Done" - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge diff --git a/.github/workflows/release-vite-task-client.yml b/.github/workflows/release-vite-task-client.yml deleted file mode 100644 index 4bd3a081f..000000000 --- a/.github/workflows/release-vite-task-client.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Release vite-task-client - -on: - push: - branches: [main] - paths: - - packages/vite-task-client/package.json - -permissions: - contents: read - -jobs: - check-version: - name: detect version bump - runs-on: ubuntu-latest - outputs: - changed: ${{ steps.check.outputs.changed }} - version: ${{ steps.check.outputs.version }} - steps: - - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - id: check - uses: EndBug/version-check@095362f3cd50f690c8fa0e6afeea81834bd8d320 # v3.0.0 - with: - file-name: packages/vite-task-client/package.json - file-url: https://unpkg.com/@voidzero-dev/vite-task-client@latest/package.json - static-checking: localIsNew - - publish: - name: publish to npm - needs: check-version - if: needs.check-version.outputs.changed == 'true' - runs-on: ubuntu-latest - environment: release - permissions: - id-token: write - steps: - - uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2 - - uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1 - - run: pnpm build-vite-task-client-types - - run: pnpm publish --provenance --access public - working-directory: packages/vite-task-client diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml deleted file mode 100644 index fc0e83e97..000000000 --- a/.github/workflows/security.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Security Analysis - -on: - workflow_dispatch: - pull_request: - types: [opened, synchronize] - push: - branches: - - main - paths: - - '.github/workflows/**' - -permissions: {} - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: ${{ github.ref_name != 'main' }} - -jobs: - security: - name: Security Analysis - runs-on: ubuntu-latest - steps: - - uses: oxc-project/security-action@2ac862d109c9728f5bf439d249b69f68905a5de4 # v1.0.8 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 624e0664b..000000000 --- a/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -/target -node_modules -# Fixture node_modules that stand in for real npm installs in snapshot tests. -# `**` covers nested monorepo layouts (e.g. packages/foo/node_modules/.bin). -!crates/vite_task_plan/tests/plan_snapshots/fixtures/*/**/node_modules -dist -.claude/settings.local.json -*.tsbuildinfo -.DS_Store -/.vscode/settings.json -*.snap.new -*.md.new diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index ab8b378fe..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "crates/fspy_detours_sys/detours"] - path = crates/fspy_detours_sys/detours - url = https://github.com/microsoft/Detours diff --git a/.ignore b/.ignore deleted file mode 100644 index ee7372bb2..000000000 --- a/.ignore +++ /dev/null @@ -1,4 +0,0 @@ -# For watchexec https://github.com/watchexec/watchexec/tree/main/crates/cli#features - -target/** -**/node_modules/** diff --git a/.node-version b/.node-version deleted file mode 100644 index e2228113d..000000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ -22.19.0 diff --git a/.non-vite.clippy.toml b/.non-vite.clippy.toml deleted file mode 100644 index 8f85a92f3..000000000 --- a/.non-vite.clippy.toml +++ /dev/null @@ -1,26 +0,0 @@ -# Clippy configuration for non-vite crates (fspy_*, subprocess_test, pty_terminal, etc.) -# that don't depend on vite_str/vite_path. -# -# This is a subset of the root .clippy.toml, with rules recommending vite_str/vite_path -# alternatives removed. Generic rules (cow_utils, rustc-hash) still apply. -# -# To use: symlink as `.clippy.toml` in the crate's directory: -# ln -s ../../.non-vite.clippy.toml .clippy.toml - -avoid-breaking-exported-api = false - -disallowed-methods = [ - { path = "str::to_ascii_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_lowercase` instead." }, - { path = "str::to_ascii_uppercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_uppercase` instead." }, - { path = "str::to_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_lowercase` instead." }, - { path = "str::to_uppercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_uppercase` instead." }, - { path = "str::replace", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_replace` instead." }, - { path = "str::replacen", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_replacen` instead." }, - { path = "std::thread::sleep", reason = "Use proper synchronization (channels, condvars, etc.) instead of sleeping. Sleep is only acceptable for intentional user-facing delays (e.g. animations, debouncing)." }, - { path = "tokio::time::sleep", reason = "Use proper synchronization (channels, signals, etc.) instead of sleeping. Sleep is only acceptable for intentional user-facing delays (e.g. animations, debouncing)." }, -] - -disallowed-types = [ - { path = "std::collections::HashMap", reason = "Use `rustc_hash::FxHashMap` instead, which is typically faster." }, - { path = "std::collections::HashSet", reason = "Use `rustc_hash::FxHashSet` instead, which is typically faster." }, -] diff --git a/.rustfmt.toml b/.rustfmt.toml deleted file mode 100644 index 6dc2e6c60..000000000 --- a/.rustfmt.toml +++ /dev/null @@ -1,21 +0,0 @@ -style_edition = "2024" - -# Make Rust more readable given most people have wide screens nowadays. -# This is also the setting used by [rustc](https://github.com/rust-lang/rust/blob/master/rustfmt.toml) -use_small_heuristics = "Max" - -# Use field initialize shorthand if possible -use_field_init_shorthand = true -reorder_modules = true - -# All unstable features that we wish for -# unstable_features = true -# Provide a cleaner impl order -reorder_impl_items = true -# Provide a cleaner import sort order -group_imports = "StdExternalCrate" -# Group "use" statements by crate -imports_granularity = "Crate" - -# Skip generated files -ignore = ["crates/fspy_detours_sys/src/generated_bindings.rs"] diff --git a/.typos.toml b/.typos.toml deleted file mode 100644 index c97dbfdec..000000000 --- a/.typos.toml +++ /dev/null @@ -1,12 +0,0 @@ -[default.extend-words] -ratatui = "ratatui" -PUNICODE = "PUNICODE" - -[files] -extend-exclude = [ - "crates/fspy_detours_sys/detours", - "crates/fspy_detours_sys/src/generated_bindings.rs", - # Intentional typos for testing fuzzy matching and "did you mean" suggestions - "crates/vite_select/src/fuzzy.rs", - "crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select", -] diff --git a/.vite-hooks/pre-commit b/.vite-hooks/pre-commit deleted file mode 100755 index b64a1f1fb..000000000 --- a/.vite-hooks/pre-commit +++ /dev/null @@ -1 +0,0 @@ -vp staged \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md deleted file mode 120000 index 681311eb9..000000000 --- a/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -CLAUDE.md \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index a76e814d4..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,42 +0,0 @@ -# Changelog - -- **Fixed** The task cache now supports much larger automatically tracked input sets without hitting wincode's default 4 MiB sequence preallocation limit ([#554](https://github.com/voidzero-dev/vite-task/pull/554)). -- **Fixed** npm workspace patterns beginning with `./` now discover matching packages correctly ([vite-plus#2201](https://github.com/voidzero-dev/vite-plus/issues/2201), [#547](https://github.com/voidzero-dev/vite-task/pull/547)). -- **Fixed** Failures while forwarding output from a started task process no longer incorrectly say the process failed to spawn ([#506](https://github.com/voidzero-dev/vite-task/issues/506)). -- **Fixed** An issue where Bun tasks on macOS did not rerun when files they read, wrote, or listed changed ([#532](https://github.com/voidzero-dev/vite-task/issues/532), [#542](https://github.com/voidzero-dev/vite-task/pull/542)). -- **Improved** Windows file-access tracking now uses sparse temporary backing files where supported, avoiding upfront allocation of the full backing file on disk ([#524](https://github.com/voidzero-dev/vite-task/pull/524)). -- **Fixed** Automatic file-access tracking on Linux now works in containers and Kubernetes runners with limited `/dev/shm` space ([#353](https://github.com/voidzero-dev/vite-task/issues/353), [#523](https://github.com/voidzero-dev/vite-task/pull/523)). -- **Fixed** Windows automatic input tracking now records executable image reads when process creation performs the lookup inside `NtCreateUserProcess` ([#518](https://github.com/voidzero-dev/vite-task/pull/518)). -- **Fixed** Failures while waiting for a started task process to exit no longer incorrectly say the process failed to spawn ([#515](https://github.com/voidzero-dev/vite-task/pull/515)). -- **Fixed** Missing env vars requested through `@voidzero-dev/vite-task-client` now return `undefined` instead of `null`, preserving Vite production `NODE_ENV` semantics when builds run through `vp run` ([#508](https://github.com/voidzero-dev/vite-task/pull/508)). -- **Fixed** Windows builds no longer hang on CI when a `node_modules/.bin` `.cmd` shim is routed through PowerShell: the npm/pnpm/yarn `.ps1` wrappers read stdin and block forever on a non-TTY pipe, so the PowerShell rewrite is now skipped when stdin is not an interactive terminal, falling back to the `.cmd` (which never reads stdin) ([#491](https://github.com/voidzero-dev/vite-task/pull/491)). -- **Added** First-party support for caching `vite build` with zero cache config, giving Vite projects correct cache hits out of the box ([vitejs/vite#22453](https://github.com/vitejs/vite/pull/22453)). -- **Added** Support for specifying tasks from dependency packages in `dependsOn`, such as `dependsOn: [{ "task": "build", "from": "dependencies" }]` ([#479](https://github.com/voidzero-dev/vite-task/pull/479)). -- **Added** [`@voidzero-dev/vite-task-client`](https://npmx.dev/package/@voidzero-dev/vite-task-client), allowing tools to report cache information to Vite Task at runtime so users do not need to configure it manually ([#441](https://github.com/voidzero-dev/vite-task/pull/441), [#454](https://github.com/voidzero-dev/vite-task/pull/454), [#449](https://github.com/voidzero-dev/vite-task/pull/449), [#450](https://github.com/voidzero-dev/vite-task/pull/450), [#458](https://github.com/voidzero-dev/vite-task/pull/458), [#431](https://github.com/voidzero-dev/vite-task/pull/431), [#459](https://github.com/voidzero-dev/vite-task/pull/459), [#472](https://github.com/voidzero-dev/vite-task/pull/472)). -- **Changed** Cached tasks now restore automatically tracked output files by default; use `output: []` to disable restoration ([#460](https://github.com/voidzero-dev/vite-task/pull/460), [#461](https://github.com/voidzero-dev/vite-task/pull/461)). -- **Changed** Environment values in task cache fingerprints are now stored only as SHA-256 digests, and env-related cache miss details report names without values ([#455](https://github.com/voidzero-dev/vite-task/pull/455)). -- **Fixed** Prefix environment assignments like `PATH=... command` now affect executable lookup during task planning, so tools provided only by the prefixed `PATH` can be resolved correctly ([#440](https://github.com/voidzero-dev/vite-task/pull/440)) -- **Changed** Cache misses caused by a tracked env var now name the env var inline, for example `cache miss: env 'NODE_ENV' changed`, instead of the generic `envs changed` message ([#438](https://github.com/voidzero-dev/vite-task/pull/438)) -- **Fixed** The task cache is now stored in a per-schema-version subdirectory (e.g. `node_modules/.vite/task-cache/v13/`), so switching between branches that pin different Vite+ versions no longer fails with `Unrecognized database version`. Each version keeps its own cache directory; a cache from a different version is ignored rather than aborting the run ([#433](https://github.com/voidzero-dev/vite-task/pull/433)) -- **Added** A task's `env` and `untrackedEnv` glob patterns now support `!` negation: a `!`-prefixed pattern excludes matching variables (e.g. `["VITE_*", "!VITE_SECRET"]` tracks every `VITE_*` except `VITE_SECRET`) ([#425](https://github.com/voidzero-dev/vite-task/pull/425)) -- **Fixed** `package.json` and `pnpm-workspace.yaml` files with a UTF-8 BOM no longer fail to parse ([#424](https://github.com/voidzero-dev/vite-task/pull/424)) -- **Changed** `vp run --filter ` now exits 0 with a warning when the filter matches no packages, matching pnpm. Use `--fail-if-no-match` to restore the previous strict behavior ([#393](https://github.com/voidzero-dev/vite-task/pull/393)) -- **Added** task command shorthands for defining tasks as command strings or command string arrays ([#391](https://github.com/voidzero-dev/vite-task/pull/391)) -- **Changed** Cached logs are stored with colors intact (`FORCE_COLOR=1` is auto-injected into spawned tasks). Colors are then stripped at display time when the terminal does not support them. Other color-related env vars (`NO_COLOR`, `COLORTERM`, `TERM`, `TERM_PROGRAM`) are no longer passed through by default. Opt in via a task's `env`/`untrackedEnv` ([#378](https://github.com/voidzero-dev/vite-task/pull/378)) -- **Added** `output` field for cached tasks: archives matching files after a successful run and restores them on cache hit ([#375](https://github.com/voidzero-dev/vite-task/pull/375)) -- **Fixed** Windows cached tasks can now run package shims rewritten through PowerShell; default env passthrough now preserves `PATHEXT` ([#366](https://github.com/voidzero-dev/vite-task/pull/366)) -- **Added** Platform support for targets without `input` auto-inference (e.g. Android). Tasks still run; those relying on auto-inference run uncached, with the summary noting that `input` must be configured manually to enable caching ([#352](https://github.com/voidzero-dev/vite-task/pull/352)) -- **Fixed** `vp run` no longer aborts with `failed to prepare the command for injection: Invalid argument` when the user environment already has `LD_PRELOAD` (Linux) or `DYLD_INSERT_LIBRARIES` (macOS) set. The tracer shim is now appended to any existing value and placed last, so user preloads keep their symbol-interposition precedence ([#340](https://github.com/voidzero-dev/vite-task/issues/340)) -- **Changed** Arguments passed after a task name (e.g. `vp run test some-filter`) are now forwarded only to that task. Tasks pulled in via `dependsOn` no longer receive them ([#324](https://github.com/voidzero-dev/vite-task/issues/324)) -- **Fixed** Windows file access tracking no longer panics when a task touches malformed paths that cannot be represented as workspace-relative inputs ([#330](https://github.com/voidzero-dev/vite-task/pull/330)) -- **Fixed** `vp run --cache` now supports running without a task specifier and opens the interactive task selector, matching bare `vp run` behavior ([#312](https://github.com/voidzero-dev/vite-task/pull/313)) -- **Fixed** Ctrl-C now prevents future tasks from being scheduled and prevents caching of in-flight task results ([#309](https://github.com/voidzero-dev/vite-task/pull/309)) -- **Added** `--concurrency-limit` flag to limit the number of tasks running at the same time (defaults to 4) ([#288](https://github.com/voidzero-dev/vite-task/pull/288), [#309](https://github.com/voidzero-dev/vite-task/pull/309)) -- **Added** `--parallel` flag to ignore task dependencies and run all tasks at once with unlimited concurrency (unless `--concurrency-limit` is also specified) ([#309](https://github.com/voidzero-dev/vite-task/pull/309)) -- **Added** object form for `input` entries: `{ "pattern": "...", "base": "workspace" | "package" }` to resolve glob patterns relative to the workspace root instead of the package directory ([#295](https://github.com/voidzero-dev/vite-task/pull/295)) -- **Fixed** arguments after the task name being consumed by `vp` instead of passed through to the task ([#286](https://github.com/voidzero-dev/vite-task/pull/286), [#290](https://github.com/voidzero-dev/vite-task/pull/290)) -- **Changed** default untracked env patterns to align with Turborepo, covering more CI and platform-specific variables ([#262](https://github.com/voidzero-dev/vite-task/pull/262)) -- **Added** `--log=interleaved|labeled|grouped` flag to control task output display: `interleaved` (default) streams directly, `labeled` prefixes lines with `[pkg#task]`, `grouped` buffers output per task ([#266](https://github.com/voidzero-dev/vite-task/pull/266)) -- **Added** musl target support (`x86_64-unknown-linux-musl`) ([#273](https://github.com/voidzero-dev/vite-task/pull/273)) -- **Changed** cache hit/miss indicators to use neutral symbols (◉/〇) instead of ✓/✗ to avoid confusion with success/error ([#268](https://github.com/voidzero-dev/vite-task/pull/268)) -- **Added** automatic skip of caching for tasks that modify their own inputs ([#248](https://github.com/voidzero-dev/vite-task/pull/248)) diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index c394e67ea..000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,163 +0,0 @@ -# Vite Task - -A monorepo task runner (like Nx/Turbo) with intelligent caching and dependency resolution, distributed as the `vp run` command in [Vite+](https://github.com/voidzero-dev/vite-plus). - -## Repository Structure - -- `crates/vite_task` — Task execution engine with caching and session management -- `crates/vite_task_bin` — Internal dev CLI binary (`vt`) and task synthesizer -- `crates/vite_task_graph` — Task dependency graph construction and config loading -- `crates/vite_task_plan` — Execution planning (resolves env vars, working dirs, commands) -- `crates/vite_workspace` — Workspace detection and package dependency graph -- `crates/fspy*` — File system access tracing (9 crates: supervisor, preload libs, platform backends) -- `crates/pty_terminal*` — Cross-platform headless terminal emulator (3 crates) -- `crates/vite_path` — Type-safe absolute/relative path system -- `crates/vite_str` — Stack-allocated compact string type -- `crates/vite_glob` — Glob pattern matching -- `crates/vite_shell` — Shell command parsing -- `crates/vite_select` — Interactive fuzzy selection UI -- `crates/vite_tui` — Terminal UI components (WIP, unfinished) -- `crates/vite_graph_ser` — Graph serialization utilities -- `crates/subprocess_test` — Subprocess testing framework -- `packages/tools` — Node.js test utilities (print, json-edit, check-tty, etc.) -- `docs/` — Documentation (inputs configuration guide) - -## Development Commands - -```bash -just init # Install build tools and dependencies -just ready # Full quality check (fmt, check, test, lint, doc) -just fmt # Format code (cargo fmt, cargo shear, vp fmt) -just check # Check compilation with all features -just test # Run all tests -just lint # Clippy linting -just lint-linux # Cross-clippy for Linux (requires cargo-zigbuild) -just lint-windows # Cross-clippy for Windows (requires cargo-xwin) -just doc # Documentation generation -``` - -If `gt` (Graphite CLI) is available in PATH, use it instead of `gh` to create pull requests. - -PR titles must use [Conventional Commits](https://www.conventionalcommits.org) format: `type(scope): summary` (scope is optional), e.g. `feat(cache): add LRU eviction`, `fix: handle symlink loops`, `test(e2e): add ctrl-c propagation test`. - -Commit messages for AI-assisted changes must always include a `Co-authored-by:` trailer that discloses the AI model and version used, e.g. `Co-authored-by: GPT-5 Codex `. - -PR descriptions must include a `Motivation` section. If the motivation is not clear from the request or surrounding context, ask the user before creating the PR. Do not add `Validation`, `Test plan`, or similar testing/verification sections. - -## Tests - -```bash -cargo test # All tests -cargo test -p vite_task_bin --test e2e_snapshots # E2E snapshot tests -cargo test -p vite_task_plan --test plan_snapshots # Plan snapshot tests -cargo test --test e2e_snapshots -- stdin # Filter by test name -UPDATE_SNAPSHOTS=1 cargo test # Update snapshots -``` - -Default `cargo test` runs only the tests that need nothing beyond the Rust toolchain. Tests that require Node.js or `pnpm install` are marked `#[ignore]` and can be exercised with `cargo test -- --include-ignored` (or `--ignored` to run only them) after running `pnpm install` at the workspace root (which, via pnpm workspaces, also installs `packages/tools`). You don't need `pnpm install` in test fixture directories. - -### Test Reliability - -The test suite has no known pre-existing failures or flaky tests. If a test fails during your changes, treat it as a real regression caused by your work. Fix the root cause properly — do not skip, ignore, or work around failing tests. - -### Test Fixtures - -- **Plan**: `crates/vite_task_plan/tests/plan_snapshots/fixtures/` — quicker, sufficient for testing task graph, resolved configs, program paths, cwd, and env vars -- **E2E**: `crates/vite_task_bin/tests/e2e_snapshots/fixtures/` — needed for testing execution, caching, output styling - -### Cross-Platform Testing - -**CRITICAL**: This project must work on both Unix (macOS/Linux) and Windows. Skipping tests on either platform is **UNACCEPTABLE**, except on musl targets when an essential dependency or required platform capability is unavailable. Document the unavailable requirement and keep the skip scoped to musl. - -- Use `#[cfg(unix)]` and `#[cfg(windows)]` for platform-specific code within tests -- Both platforms must execute the test and verify the feature works correctly -- Use cross-platform libraries for common operations (e.g., `terminal_size` for terminal dimensions) - -## Architecture - -### Task Execution Pipeline - -``` -CLI (vite_task_bin) → Task Graph (vite_task_graph) → Plan (vite_task_plan) → Execution (vite_task) - ↑ ↓ - vite_workspace fspy (file tracing) -``` - -### Task Dependencies - -1. **Explicit**: Defined via `dependsOn` in `vite-task.json` (skip with `--ignore-depends-on`) -2. **Topological**: Based on package.json dependencies - - With `-r/--recursive`: runs task across all packages in dependency order - - With `-t/--transitive`: runs task in current package and its dependencies - -### Task Configuration - -Tasks are defined in `vite-task.json`: - -```json -{ - "cache": true | false | { "scripts": bool, "tasks": bool }, - "tasks": { - "test": { - "command": "vitest run", - "cwd": "relative/path", - "dependsOn": ["build", "package#task"], - "cache": true, - "env": ["NODE_ENV"], - "untrackedEnv": ["CI"], - "input": ["src/**", "!dist/**", { "auto": true }, { "pattern": "tsconfig.json", "base": "workspace" }] - } - } -} -``` - -## Internal vs Public Naming - -This repo uses internal names that differ from the public-facing product. In code comments, docs, and user-facing strings, use the public names: - -| Internal (this repo) | Public (Vite+) | -| -------------------- | --------------- | -| `vt` | `vp` | -| `vite-task.json` | `vite.config.*` | - -`vite-task.json` and `vt` are fine in implementation code, test fixtures, and CLAUDE.md itself — just not in doc comments or user-facing messages. - -## Code Constraints - -### Required Patterns - -Enforced by `.clippy.toml`: - -| Instead of | Use | -| ----------------------------------- | ---------------------------------------- | -| `HashMap`/`HashSet` | `FxHashMap`/`FxHashSet` from rustc-hash | -| `std::path::Path`/`PathBuf` | `vite_path::AbsolutePath`/`RelativePath` | -| `std::format!` | `vite_str::format!` | -| `String` (for small strings) | `vite_str::Str` | -| `std::env::current_dir` | `vite_path::current_dir` | -| `.to_lowercase()`/`.to_uppercase()` | `cow_utils` methods | - -### Path Type System - -- Use `AbsolutePath` for internal data flow; only convert to `RelativePath` when saving to cache -- Use methods like `strip_prefix`/`join` from `vite_path` instead of converting to std paths -- Only convert to std paths when interfacing with std library functions -- Add necessary methods in `vite_path` instead of falling back to std path types - -### Cross-Platform Requirements - -All code must work on both Unix and Windows without platform skipping. The only exception is for tests on musl targets when an essential dependency or required platform capability is unavailable; document the reason and scope the skip to musl. - -- Use `#[cfg(unix)]` / `#[cfg(windows)]` for platform-specific implementations -- Platform differences should be handled gracefully, not skipped -- After major changes to `fspy*` or platform-specific crates, run `just lint-linux` and `just lint-windows` - -## Changelog - -When a change is user-facing (new feature, changed behavior, bug fix, removal, or perf improvement), run `/update-changelog` to add an entry to `CHANGELOG.md`. Do not add entries for internal refactors, CI, dep bumps, test fixes, or docs changes. - -## Quick Reference - -- **Task Format**: `package#task` (e.g., `app#build`, `@test/utils#lint`) -- **Config File**: `vite-task.json` in each package -- **Rust Edition**: 2024, MSRV 1.88.0 diff --git a/CODEBASE_ASSESSMENT.md b/CODEBASE_ASSESSMENT.md new file mode 100644 index 000000000..0089c3d43 --- /dev/null +++ b/CODEBASE_ASSESSMENT.md @@ -0,0 +1,294 @@ +# Codebase Assessment + +## Executive summary + +The architecture is fundamentally strong: workspace discovery, task graph construction, execution planning, and runtime scheduling/cache are cleanly layered. Typed paths and graph invariants reduce invalid states, and the repository has substantial plan and end-to-end snapshot coverage. + +The largest risks are concentrated in filesystem tracing, cache correctness, nested concurrency, cache durability, and cross-platform filesystem semantics. The highest-value work is to tighten these existing boundaries rather than rewrite the architecture. + +## Highest-priority improvements + +### 1. Fix variadic `exec*` argument construction immediately + +The heap branch allocates `argc` pointers but writes the null terminator at `out[argc]`. The stack branch passes uninitialized elements through a typed slice. This can abort intercepted processes and invokes undefined behavior. + +Allocate `argc + 1`, expose only the initialized `[..=argc]` region, and add tests around the 31/32-argument boundary under sanitizers or Miri. + +Reference: `crates/fspy_preload_unix/src/interceptions/spawn/exec/with_argv.rs:28-58` + +### 2. Make external executable changes invalidate cache entries + +Programs outside the workspace are fingerprinted only by basename. Changing `PATH` or upgrading `node`, a compiler, package manager, or another global tool can therefore reuse output generated by a different executable. + +Include the resolved executable path and a session-cached content or stable metadata fingerprint. Include interpreter chains for scripts. + +Reference: `crates/vite_task_plan/src/cache_metadata.rs:122-130` + +### 3. Define concurrency as global or per nested graph + +Documentation presents the concurrency limit as an upper bound, but every nested execution graph creates an independent semaphore. Total leaf-process concurrency can consequently exceed the configured limit. + +Prefer one shared execution budget across the expanded execution tree. Add end-to-end tests for nested runs with limits 1, 2, the default, explicit nested limits, and unlimited mode. If per-level concurrency is intentional, document it prominently and avoid describing the setting as a global upper bound. + +References: + +- `docs/concurrency.md:3-16` +- `crates/vite_task/src/session/execute/scheduler.rs:67-85` +- `crates/vite_task/src/session/execute/scheduler.rs:149-168` + +### 4. Bound IPC frame sizes + +A child can submit a `u32` frame length that causes the runner to attempt a multi-gigabyte allocation. + +Add a small protocol maximum before resizing, reject zero and oversized frames, add malformed-frame tests, and introduce protocol version or capability negotiation. + +Reference: `crates/vite_task_server/src/lib.rs:481-488` + +### 5. Correct metadata and symlink-sensitive cache tracking + +`stat`, `lstat`, and no-follow accesses collapse into ordinary content reads. Tasks observing timestamps, permissions, ownership, executable bits, or symlink identity can receive incorrect cache hits. + +Preserve access semantics through fspy and fingerprint the observable metadata or link target appropriate to each operation. + +References: + +- `crates/fspy_shared/src/ipc/mod.rs:34-38` +- `crates/fspy_preload_unix/src/interceptions/stat.rs:19-30` +- `crates/vite_task/src/session/execute/fingerprint.rs:388-438` + +### 6. Remove the unconditional `dist` fingerprint exclusion + +Directory fingerprints ignore any entry named `dist`, even when it is a legitimate task input. A task that enumerates a directory can therefore receive a stale cache hit when that entry is added or removed. + +Derive exclusions from resolved task configuration or explicit runner-aware ignore requests instead of a global filename rule. + +Reference: `crates/vite_task/src/session/execute/fingerprint.rs:383-386` + +## Cache durability and fidelity + +### 7. Make cache publication transactional and self-healing + +Recommended changes: + +- Write archives to temporary files and atomically rename them after successful completion. +- Wrap related cache-entry and task-key updates in one SQLite transaction. +- On a missing, truncated, or corrupt archive, invalidate the entry and execute the task instead of failing the invocation. +- Reclaim orphaned archives with bounded cleanup. +- Add multi-process stress and fault-injection tests around every publication boundary. + +### 8. Preserve the complete output filesystem state + +Archive creation follows symlinks, omits directories and non-regular nodes, and cache restoration does not remove stale outputs. A cache hit can therefore produce a filesystem state different from a successful uncached run. + +Persist a complete output manifest, use `symlink_metadata`, reproduce symlinks and empty directories, and safely remove stale matching outputs before extraction. Prevent traversal outside the workspace. + +Reference: `crates/vite_task/src/session/cache/archive.rs:15-62` + +### 9. Bound captured stdout and stderr + +Cached task output is retained in memory and later stored in SQLite without a size limit. A verbose task can exhaust runner memory or create very large database rows. + +Use bounded capture or spool output to files. Disable cache replay beyond a configurable threshold while preserving normal live reporting. + +### 10. Move blocking cache work off async execution threads + +SQLite queries, input hashing, tar creation, and zstd compression execute synchronously inside async orchestration. Slow storage can stall unrelated task progress, output draining, and IPC handling. + +Use a dedicated cache worker or bounded `spawn_blocking` pool. Instrument planning, hashing, database, archive, and child-execution timings separately. + +### 11. Treat fingerprint I/O errors accurately + +Only actual `NotFound` errors should become missing-path fingerprints. Permission and transient I/O errors should prevent cache use or update with a diagnostic rather than masquerading as absence. + +Reference: `crates/vite_task/src/session/execute/fingerprint.rs:388-419` + +### 12. Bound descendant drain behavior + +After the direct child exits, surviving descendants can retain runner-aware IPC or fspy handles indefinitely and prevent task completion. + +Apply a bounded drain timeout or cancellation policy and discard late descendant reports after the direct child exits. + +## Workspace and task-graph correctness + +### 13. Support local dependencies beyond `workspace:` + +Ordinary local semver ranges, `file:`, `link:`, aliases, and pnpm `link-workspace-packages` dependencies are discarded before graph resolution. This can produce incomplete package graphs and incorrect topological execution. + +Preserve dependency names and specifiers, then resolve them against known workspace packages using package-manager-aware rules. Emit diagnostics for unsupported forms rather than silently omitting edges. + +Reference: `crates/vite_workspace/src/package.rs:47-69` + +### 14. Do not silently discard workspace traversal failures + +Propagate permission and I/O errors with the affected glob and path. Ignore only expected disappearance races. + +Reference: `crates/vite_workspace/src/lib.rs:112-130` + +### 15. Add runtime coverage for complex dependency graphs + +Existing plan snapshots are strong, but runtime coverage should include: + +- object-form `dependsOn` ordering; +- diamond graphs and shared dependency deduplication; +- mixed explicit and topological dependencies; +- cycles formed across multiple edge types; +- packages missing the requested task; +- `--ignore-depends-on` execution behavior; +- extra arguments reaching only explicitly requested tasks. + +### 16. Track shebang interpreters consistently + +The preload execution path records shebang interpreter reads, but the seccomp/static-executable path records only the script. Reuse the shared parser with a bounded kernel-compatible recursion limit and test both paths. + +References: + +- `crates/fspy/src/unix/syscall_handler/execve.rs:8-9` +- `crates/fspy_shared_unix/src/exec/mod.rs:135-140` + +## Architecture and maintainability + +### 17. Split `vite_task` along existing seams + +The crate currently owns CLI types, session orchestration, scheduling, process execution, cache storage, reporting, fspy integration, IPC, and Node addon materialization. Natural boundaries are: + +- `vite_task_exec`: scheduling, cancellation, and process lifecycle; +- `vite_task_cache`: fingerprints, persistence, archives, and cache schema; +- `vite_task_report`: reporter interfaces and presentation; +- `vite_task`: composition and session facade. + +Move cache-domain types together; they are currently split between planning and runtime crates. + +### 18. Remove test-only milestone infrastructure from production + +`vite_task` directly depends on `pty_terminal_test_client` and emits test milestones from production selector code. + +Replace this with a feature-gated test hook, injected observer, or synchronization based on externally observable screen state. + +References: + +- `crates/vite_task/Cargo.toml:23-27` +- `crates/vite_task/src/session/mod.rs:536-543` + +### 19. Decouple Node integration from generic execution + +Model runner-aware IPC and language bridges as execution observers that provide child environment additions, a concurrently polled driver, and post-run reports. Lazy-materialize the N-API addon only when requested. + +### 20. Decide the status of `vite_tui` + +The TUI is disconnected from task execution, hard-codes system commands, disables tests, and broadens the dependency graph. + +Exclude it from normal workspace validation until active development resumes, or integrate it through a supported execution-event API and move blocking PTY work to dedicated threads. + +### 21. Improve workspace loading scalability + +Enumerate and sort package paths first, then read and parse package files and user configuration concurrently. Deterministically assemble graphs afterward to preserve snapshot stability. + +### 22. Consolidate duplicated semantics + +High-value consolidation opportunities include: + +- environment prefix/query matching shared by IPC serving and cache validation; +- structurally identical input/output glob configuration resolution; +- repeated reporter lifecycle plumbing; +- summary persistence, statistics, and formatting currently combined in one large module. + +## Testing and tooling + +### 23. Add focused scheduler tests + +Use deterministic fake leaf executions to cover: + +- chains, diamonds, fan-in, fan-out, and disconnected components; +- exact concurrency bounds; +- each node executing exactly once; +- dependency completion before dependent start; +- cancellation before scheduling and while waiting for permits; +- fast-fail with queued and running tasks; +- nested graph failure propagation; +- empty graphs and maximum permit clamping. + +### 24. Add direct archive and cache tests + +Cover: + +- nested and empty output sets; +- files disappearing during collection; +- executable-bit and read-only restoration; +- symlinks, empty directories, and non-regular nodes; +- malformed and truncated archives; +- extraction traversal safety; +- missing and corrupt database values; +- concurrent processes publishing the same and different entries; +- interruption at each archive/database publication boundary. + +### 25. Expand cross-platform filesystem fixtures + +Add equivalent platform-paired cases for: + +- symlinks and Windows junctions; +- hardlinks; +- case sensitivity; +- non-UTF-8 Unix filenames; +- Unicode normalization on macOS; +- permissions and executable bits; +- long and extended-length Windows paths. + +### 26. Add Linux arm64 and cross-target lint coverage + +The runtime matrix covers Linux x64, macOS arm64/x64, Windows x64, and Linux musl, but not Linux arm64. Add Linux arm64 compile/runtime coverage where practical, plus Windows and Linux cross-target Clippy jobs already represented by local `just` recipes. + +### 27. Introduce measured coverage + +Add `cargo llvm-cov` for default and ignored tests. Initially publish reports without a global percentage gate. Focus changed-line or per-file expectations later on scheduler, cache, task graph query, planning, and workspace loading modules. + +### 28. Enforce dependency policy + +The repository has a detailed `deny.toml`, but CI does not enforce it. Add pinned `cargo deny check` runs when manifests, the lockfile, or policy change, and on a schedule for newly disclosed advisories. + +### 29. Add direct JavaScript client tests + +Use a fake addon module to test no-runner behavior, missing or throwing addons, one-time loading, argument forwarding, fallback values, API version mismatch, and paths containing spaces. Gate publishing on these tests. + +## Documentation and developer experience + +### 30. Correct the E2E harness README + +The README documents shell-string commands and pipes, while the harness expects argument arrays and permits only `vt` and `vtt`. Document the real schema, supported interactions, platform filters, ignored tests, snapshot formatting, and timeout behavior. + +Reference: `crates/vite_task_bin/tests/e2e_snapshots/README.md:14-47` + +### 31. Correct the input configuration guide + +The guide repeatedly documents `inputs`, while the configuration field and fixtures use singular `input`. Validate documentation JSON examples against the real deserializer to prevent future schema drift. + +Reference: `docs/inputs.md:1-30` + +### 32. Align setup and version documentation + +`CONTRIBUTING.md` requires pnpm 10.x, while `package.json` pins pnpm 11.1.2. It also says `just init` bootstraps Node tooling, but that recipe installs only Rust tools. + +Either expand the setup recipe to install locked Node dependencies and initialize submodules, or correct the documentation. Reconcile the documented Rust MSRV with the manifest and pinned development toolchain. + +References: + +- `CONTRIBUTING.md:3-17` +- `package.json:17-20` +- `justfile:11-12` + +## Recommended implementation sequence + +1. Fix `exec*` undefined behavior, IPC allocation limits, and external executable fingerprints. +2. Resolve nested concurrency semantics and add focused scheduler tests. +3. Correct metadata, symlink, shebang interpreter, and `dist` fingerprinting. +4. Make cache publication transactional, bounded, self-healing, and filesystem-faithful. +5. Add archive, fingerprint, corruption, and multi-process fault tests. +6. Expand workspace dependency syntax support and runtime graph coverage. +7. Isolate blocking work and split runtime responsibilities along existing seams. +8. Improve platform CI, coverage, dependency-policy enforcement, and JavaScript client tests. +9. Correct contributor, E2E harness, concurrency, and input documentation. + +## Overall assessment + +The codebase has a sound conceptual architecture and unusually strong integration coverage for a cross-platform task runner. The main opportunities are correctness hardening at operating-system boundaries and making runtime/cache semantics explicit and testable. Addressing the first six findings would materially improve reliability without requiring a broad redesign. + +This assessment was produced through a read-only static review of source, tests, fixtures, manifests, workflows, and documentation. No compilation or test execution was performed as part of the review. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 87308b3d3..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,123 +0,0 @@ -# Contributing to Vite Task - -## Prerequisites - -- [Rust](https://rustup.rs/) (see [rust-toolchain.toml](rust-toolchain.toml) for the required version) -- [Node.js](https://nodejs.org/) (^20.19.0 || >=22.12.0) -- [pnpm](https://pnpm.io/) (11.x) -- [just](https://just.systems/) — task runner for build commands -- [cargo-binstall](https://github.com/cargo-bins/cargo-binstall) — for installing Rust tools - -## Initial Setup - -```bash -just init -``` - -This installs all required Rust tools (`cargo-insta`, `typos-cli`, `cargo-shear`, `taplo-cli`) and bootstraps the Node.js tooling. - -## Development Workflow - -Officially, Vite Task is distributed as part of Vite+ and invoked via `vp run`. The `vt` binary (`vite_task_bin` crate) is an internal development CLI for working on this repo in pure Rust without building the full Vite+ stack. Use `cargo run --bin vt` to build and run locally during development. Don't reference `vt` in user-facing documentation — it's not a public interface. - -| | `vp run` (Vite+) | `vt run` (this repo) | -| ------------ | -------------------------------------------- | -------------------- | -| Purpose | End-user CLI | Internal dev/testing | -| Config | `vite.config.ts` (`run` block) | `vite-task.json` | -| Distribution | Bundled in Vite+ | `cargo run --bin vt` | -| Scope | Full toolchain (dev, build, test, lint, ...) | Task runner only | - -```bash -just ready # Full quality check: typos, fmt, check, test, lint, doc -just fmt # Format code (cargo fmt + cargo shear + vp fmt) -just check # Check compilation with all features -just test # Run all tests -just lint # Clippy linting -just doc # Generate documentation -``` - -## Testing - -### Running Tests - -```bash -cargo test # All tests -cargo test -p vite_task_bin --test e2e_snapshots # E2E snapshot tests -cargo test -p vite_task_plan --test plan_snapshots # Plan snapshot tests -cargo test --test e2e_snapshots -- stdin # Filter by test name -UPDATE_SNAPSHOTS=1 cargo test # Update snapshots -``` - -Integration tests that need Node.js or the `packages/tools` binaries (e.g. `oxlint`) are marked `#[ignore]` — and `[[e2e]]` cases in e2e snapshots.toml can opt in with `ignore = true`. Default `cargo test` runs only the tests that need nothing beyond the Rust toolchain; to run the rest: - -```bash -pnpm install # at the workspace root (installs packages/tools and Chromium too) -cargo test -- --include-ignored # run everything -cargo test -- --ignored # run only the previously-ignored tests -``` - -You don't need `pnpm install` in test fixture directories. -Playwright's bundled Chromium is unavailable on musl, so the Vitest browser fixture is skipped there. - -### Test Fixtures - -- **Plan snapshots** — `crates/vite_task_plan/tests/plan_snapshots/fixtures/` — quicker, sufficient for testing task graph, resolved configs, program paths, cwd, and env vars -- **E2E snapshots** — `crates/vite_task_bin/tests/e2e_snapshots/fixtures/` — needed for testing actual execution, caching behavior, and output styling - -See individual crate READMEs for crate-specific testing details. - -### Playground - -The `playground/` directory is a small workspace for manually testing the task runner. It has three packages (`app → lib → utils`) with cached tasks (`build`, `test`, `lint`, `typecheck`) and an uncached `dev` script. - -```bash -cargo run --bin vt -- run -r build # run build across all packages -cargo run --bin vt -- run -r --parallel dev # start all dev scripts in parallel -``` - -See `playground/README.md` for the full task list and dependency structure. - -## Cross-Platform Development - -This project must work on macOS, Linux, and Windows. Skipping tests on any platform is not acceptable, except on musl targets when an essential dependency or required platform capability is unavailable. Document the unavailable requirement and keep the skip scoped to musl. - -- Use `#[cfg(unix)]` / `#[cfg(windows)]` for platform-specific code -- Use cross-platform libraries where possible (e.g., `terminal_size` instead of raw ioctl/ConPTY) - -### Cross-Platform Linting - -After changes to `fspy*` or platform-specific crates, run cross-platform clippy: - -```bash -just lint # Native (host platform) -just lint-linux # Linux via cargo-zigbuild -just lint-windows # Windows via cargo-xwin -``` - -## Code Conventions - -### Required Patterns - -These are enforced by `.clippy.toml`: - -| Instead of | Use | -| ------------------------------------- | ------------------------------------------ | -| `HashMap` / `HashSet` | `FxHashMap` / `FxHashSet` from rustc-hash | -| `std::path::Path` / `PathBuf` | `vite_path::AbsolutePath` / `RelativePath` | -| `std::format!` | `vite_str::format!` | -| `String` (for small strings) | `vite_str::Str` | -| `std::env::current_dir` | `vite_path::current_dir` | -| `.to_lowercase()` / `.to_uppercase()` | `cow_utils` methods | - -### Path Type System - -All paths use `vite_path` for type safety: - -- **`AbsolutePath` / `AbsolutePathBuf`** — for internal data flow -- **`RelativePath` / `RelativePathBuf`** — for cache storage and display - -Use `vite_path` methods (`strip_prefix`, `join`, etc.) instead of converting to `std::path`. Only convert to std paths when interfacing with std library functions. Add necessary methods to `vite_path` rather than falling back. - -## macOS Performance Tip - -Add your terminal app to the approved "Developer Tools" in System Settings > Privacy & Security. Your Rust builds will be ~30% faster. diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index fd72b2ce0..000000000 --- a/Cargo.lock +++ /dev/null @@ -1,5024 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse 0.2.7", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse 1.0.0", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "assert2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f7e619706e24555d32d89b4446960bec3d085d1f488c659874b0929998bc28" -dependencies = [ - "assert2-macros", - "diff", - "terminal_size", - "unicode-width", - "yansi", -] - -[[package]] -name = "assert2-macros" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "347ba98d9bd5467cf7e5b5ea0a90b509d9b589060b302fcc0d9ae6268ff7e02e" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version 0.4.1", - "syn 2.0.117", -] - -[[package]] -name = "assertables" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b3cadd448c7cd878da49dac40adcd0f7248d2911afe5102267f68c352d87882" -dependencies = [ - "regex", - "walkdir", -] - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "atomic" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bindgen" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags 2.10.0", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.117", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "bon" -version = "3.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe" -dependencies = [ - "bon-macros", - "rustversion", -] - -[[package]] -name = "bon-macros" -version = "3.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" -dependencies = [ - "darling 0.23.0", - "ident_case", - "prettyplease", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.117", -] - -[[package]] -name = "brush-parser" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f64b19efc02f0dd6cac1d462e20a1098f425f09cb13624efbb1b7d63f061735" -dependencies = [ - "bon", - "cached", - "getrandom 0.4.2", - "indenter", - "insta", - "peg", - "thiserror 2.0.18", - "tracing", - "utf8-chars", - "uuid", -] - -[[package]] -name = "bstr" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" - -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "cached" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b6f5d101f0f6322c8646a45b7c581a673e476329040d97565815c2461dd0c4" -dependencies = [ - "ahash", - "cached_proc_macro", - "cached_proc_macro_types", - "hashbrown 0.16.1", - "once_cell", - "parking_lot", - "thiserror 2.0.18", - "web-time", -] - -[[package]] -name = "cached_proc_macro" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ebcf9c75f17a17d55d11afc98e46167d4790a263f428891b8705ab2f793eca3" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "cached_proc_macro_types" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" - -[[package]] -name = "castaway" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" -dependencies = [ - "rustversion", -] - -[[package]] -name = "cc" -version = "1.2.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading 0.8.9", -] - -[[package]] -name = "clap" -version = "4.5.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" -dependencies = [ - "anstream 0.6.21", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "clap_lex" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" - -[[package]] -name = "color-eyre" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "compact_str" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "serde", - "static_assertions", -] - -[[package]] -name = "console" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" -dependencies = [ - "encode_unicode", - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - -[[package]] -name = "const_format" -version = "0.2.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "constcat" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d3e02915a2cea4d74caa8681e2d44b1c3254bdbf17d11d41d587ff858832c" - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "convert_case" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "copy_dir" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" -dependencies = [ - "walkdir", -] - -[[package]] -name = "cow-utils" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" - -[[package]] -name = "cp_r" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "837ca07dfd27a2663ac7c4701bb35856b534c2a61dd47af06ccf65d3bec79ebc" -dependencies = [ - "filetime", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crossterm" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" -dependencies = [ - "bitflags 2.10.0", - "crossterm_winapi", - "derive_more", - "document-features", - "futures-core", - "mio", - "parking_lot", - "rustix", - "signal-hook 0.3.18", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "crypto-common" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "csscolorparser" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" -dependencies = [ - "lab", - "phf 0.11.3", -] - -[[package]] -name = "csv-async" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888dbb0f640d2c4c04e50f933885c7e9c95995d93cec90aba8735b4c610f26f1" -dependencies = [ - "cfg-if", - "csv-core", - "futures", - "itoa", - "ryu", - "serde", - "tokio", - "tokio-stream", -] - -[[package]] -name = "csv-core" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" -dependencies = [ - "memchr", -] - -[[package]] -name = "ctor" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7335955a5f85f95f3188623240e081e7b2059a8ad1bae68944b7cfdd718fb10" -dependencies = [ - "link-section", - "linktime-proc-macro", -] - -[[package]] -name = "ctrlc" -version = "3.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" -dependencies = [ - "dispatch2", - "nix 0.31.2", - "windows-sys 0.61.2", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core 0.23.0", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "deltae" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" - -[[package]] -name = "deranged" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case 0.10.0", - "proc-macro2", - "quote", - "rustc_version 0.4.1", - "syn 2.0.117", - "unicode-xid", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "diff-struct" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79aac083112b31f7cb768b24b893dc0c34c296a4b06b250c407bfd495e42075c" -dependencies = [ - "diff_derive", - "num", - "serde", -] - -[[package]] -name = "diff_derive" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe165e7ead196bbbf44c7ce11a7a21157b5c002ce46d7098ff9c556784a4912d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "crypto-common 0.1.7", -] - -[[package]] -name = "digest" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer 0.12.0", - "const-oid", - "crypto-common 0.2.1", -] - -[[package]] -name = "directories" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags 2.10.0", - "block2", - "libc", - "objc2", -] - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "elf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55dd888a213fc57e957abf2aa305ee3e8a28dbe05687a251f33b637cd46b0070" - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "env_filter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" -dependencies = [ - "log", -] - -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" - -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream 0.6.21", - "anstyle", - "env_filter", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "escape8259" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" - -[[package]] -name = "euclid" -version = "0.22.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" -dependencies = [ - "num-traits", -] - -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] -name = "fancy-regex" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" -dependencies = [ - "bit-set", - "regex", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "filedescriptor" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" -dependencies = [ - "libc", - "thiserror 1.0.69", - "winapi", -] - -[[package]] -name = "filetime" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" -dependencies = [ - "cfg-if", - "libc", - "libredox", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "finl_unicode" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - -[[package]] -name = "fspy" -version = "0.1.0" -dependencies = [ - "anyhow", - "bstr", - "bumpalo", - "csv-async", - "ctor", - "derive_more", - "flate2", - "fspy_detours_sys", - "fspy_preload_unix", - "fspy_preload_windows", - "fspy_seccomp_unotify", - "fspy_shared", - "fspy_shared_unix", - "fspy_test_bin", - "futures-util", - "libc", - "materialized_artifact", - "materialized_artifact_build", - "nix 0.31.2", - "ntest", - "ouroboros", - "rustc-hash", - "sha2 0.11.0", - "subprocess_test", - "tar", - "tempfile", - "test-log", - "thiserror 2.0.18", - "tokio", - "tokio-util", - "which", - "winapi", - "wincode", - "winsafe 0.0.27", -] - -[[package]] -name = "fspy_detours_sys" -version = "0.0.0" -dependencies = [ - "bindgen", - "cc", - "cow-utils", - "winapi", -] - -[[package]] -name = "fspy_e2e" -version = "0.0.0" -dependencies = [ - "fspy", - "rustc-hash", - "serde", - "tokio", - "tokio-util", - "toml", -] - -[[package]] -name = "fspy_preload_unix" -version = "0.0.0" -dependencies = [ - "anyhow", - "bstr", - "ctor", - "fspy_shared", - "fspy_shared_unix", - "libc", - "nix 0.31.2", - "wincode", -] - -[[package]] -name = "fspy_preload_windows" -version = "0.1.0" -dependencies = [ - "constcat", - "fspy_detours_sys", - "fspy_shared", - "ntapi", - "smallvec 2.0.0-alpha.12", - "tempfile", - "widestring", - "winapi", - "wincode", - "windows-sys 0.61.2", - "winsafe 0.0.27", -] - -[[package]] -name = "fspy_seccomp_unotify" -version = "0.1.0" -dependencies = [ - "assertables", - "futures-util", - "libc", - "nix 0.31.2", - "passfd", - "seccompiler", - "syscalls", - "tempfile", - "test-log", - "tokio", - "tracing", - "wincode", -] - -[[package]] -name = "fspy_shared" -version = "0.0.0" -dependencies = [ - "assert2", - "bitflags 2.10.0", - "bstr", - "bumpalo", - "bytemuck", - "ctor", - "fspy_shm", - "native_str", - "rustc-hash", - "subprocess_test", - "thiserror 2.0.18", - "tokio", - "tracing", - "uuid", - "vite_path", - "winapi", - "wincode", -] - -[[package]] -name = "fspy_shared_unix" -version = "0.0.0" -dependencies = [ - "anyhow", - "base64", - "bstr", - "elf", - "fspy_seccomp_unotify", - "fspy_shared", - "memmap2", - "nix 0.31.2", - "phf 0.13.1", - "stackalloc", - "wincode", -] - -[[package]] -name = "fspy_shm" -version = "0.0.0" -dependencies = [ - "base64", - "ctor", - "memfd", - "memmap2", - "nix 0.31.2", - "passfd", - "subprocess_test", - "tokio", - "tokio-util", - "tracing", - "uuid", - "windows-sys 0.61.2", -] - -[[package]] -name = "fspy_test_bin" -version = "0.0.0" -dependencies = [ - "nix 0.31.2", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "globset" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] - -[[package]] -name = "hashlink" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" -dependencies = [ - "hashbrown 0.16.1", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hybrid-array" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" -dependencies = [ - "typenum", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "indenter" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - -[[package]] -name = "inotify" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" -dependencies = [ - "bitflags 2.10.0", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - -[[package]] -name = "insta" -version = "1.47.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" -dependencies = [ - "console", - "once_cell", - "pest", - "pest_derive", - "serde", - "similar 2.7.0", - "tempfile", -] - -[[package]] -name = "instability" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" -dependencies = [ - "darling 0.23.0", - "indoc", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "is-terminal" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "is_ci" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "jsonc-parser" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2e5dea04f54739ca5694ee06e4448ffda065a55b3427d2b131bd5ea697ea2c" -dependencies = [ - "serde", -] - -[[package]] -name = "kasuari" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" -dependencies = [ - "hashbrown 0.16.1", - "portable-atomic", - "thiserror 2.0.18", -] - -[[package]] -name = "kqueue" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - -[[package]] -name = "lab" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.185" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "libloading" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "libredox" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" -dependencies = [ - "bitflags 2.10.0", - "libc", - "redox_syscall 0.7.0", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libtest-mimic" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33" -dependencies = [ - "anstream 1.0.0", - "anstyle", - "clap", - "escape8259", -] - -[[package]] -name = "line-clipping" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" -dependencies = [ - "bitflags 2.10.0", -] - -[[package]] -name = "link-section" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2c24837c4fd5ab6a31d64133eae954f5199247523cf29586117e85245c0dd3" - -[[package]] -name = "linktime-proc-macro" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44cd706ff0d503ee32b2071166510ca27e281228de10cd3aa8d35ff94560f81" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" -dependencies = [ - "hashbrown 0.16.1", -] - -[[package]] -name = "mac_address" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" -dependencies = [ - "nix 0.29.0", - "winapi", -] - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "materialized_artifact" -version = "0.0.0" -dependencies = [ - "tempfile", -] - -[[package]] -name = "materialized_artifact_build" -version = "0.0.0" -dependencies = [ - "xxhash-rust", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "memfd" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" -dependencies = [ - "rustix", -] - -[[package]] -name = "memmap2" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" -dependencies = [ - "libc", -] - -[[package]] -name = "memmem" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "monostate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4cc965c89dd0615a9e822ff8002f7633d2466143d51bd58693e4b2c75aabad" -dependencies = [ - "monostate-impl", - "serde", - "serde_core", -] - -[[package]] -name = "monostate-impl" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f5b99488110875b5904839d396c2cdfaf241ff6622638acb879cc7effad5de" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "napi" -version = "3.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d395473824516f38dd1071a1a37bc57daa7be65b293ebba4ead5f7abb017a2" -dependencies = [ - "bitflags 2.10.0", - "ctor", - "futures", - "napi-build", - "napi-sys", - "nohash-hasher", - "rustc-hash", - "tracing", -] - -[[package]] -name = "napi-build" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1" - -[[package]] -name = "napi-derive" -version = "3.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b3f766e04667e6da0e181e2da4f85475d5a6513b7cf6a80bea184e224a5b42" -dependencies = [ - "convert_case 0.11.0", - "ctor", - "napi-derive-backend", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "napi-derive-backend" -version = "5.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d5af30503edf933ce7377cf6d4c877a62b0f1107ea05585f1b5e430e88d5baf" -dependencies = [ - "convert_case 0.11.0", - "proc-macro2", - "quote", - "semver 1.0.27", - "syn 2.0.117", -] - -[[package]] -name = "napi-sys" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb602b84d7c1edae45e50bbf1374696548f36ae179dfa667f577e384bb90c2b" -dependencies = [ - "libloading 0.9.0", -] - -[[package]] -name = "native_str" -version = "0.0.0" -dependencies = [ - "bumpalo", - "bytemuck", - "wincode", -] - -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases 0.1.1", - "libc", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases 0.2.1", - "libc", - "memoffset", -] - -[[package]] -name = "nix" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases 0.2.1", - "libc", - "memoffset", -] - -[[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "notify" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" -dependencies = [ - "bitflags 2.10.0", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio", - "notify-types", - "walkdir", - "windows-sys 0.60.2", -] - -[[package]] -name = "notify-types" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" -dependencies = [ - "bitflags 2.10.0", -] - -[[package]] -name = "ntapi" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" -dependencies = [ - "winapi", -] - -[[package]] -name = "ntest" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54d1aa56874c2152c24681ed0df95ee155cc06c5c61b78e2d1e8c0cae8bc5326" -dependencies = [ - "ntest_test_cases", - "ntest_timeout", -] - -[[package]] -name = "ntest_test_cases" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6913433c6319ef9b2df316bb8e3db864a41724c2bb8f12555e07dc4ec69d3db1" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ntest_timeout" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9224be3459a0c1d6e9b0f42ab0e76e98b29aef5aba33c0487dfcf47ea08b5150" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "nucleo-matcher" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85" -dependencies = [ - "memchr", - "unicode-segmentation", -] - -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - -[[package]] -name = "objc2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - -[[package]] -name = "os_str_bytes" -version = "7.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63eceb7b5d757011a87d08eb2123db15d87fb0c281f65d101ce30a1e96c3ad5c" -dependencies = [ - "memchr", -] - -[[package]] -name = "ouroboros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" -dependencies = [ - "aliasable", - "ouroboros_macro", - "static_assertions", -] - -[[package]] -name = "ouroboros_macro" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "owo-colors" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" -dependencies = [ - "supports-color 2.1.0", - "supports-color 3.0.2", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec 1.15.1", - "windows-link", -] - -[[package]] -name = "passfd" -version = "0.2.0" -source = "git+https://github.com/polachok/passfd?rev=d55881752c16aced1a49a75f9c428d38d3767213#d55881752c16aced1a49a75f9c428d38d3767213" -dependencies = [ - "futures-core", - "libc", - "tokio", -] - -[[package]] -name = "pastey" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" - -[[package]] -name = "path-clean" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "peg" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71" -dependencies = [ - "peg-runtime", - "proc-macro2", - "quote", -] - -[[package]] -name = "peg-runtime" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" - -[[package]] -name = "pest" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "pest_meta" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" -dependencies = [ - "pest", - "sha2 0.10.9", -] - -[[package]] -name = "petgraph" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" -dependencies = [ - "fixedbitset 0.5.7", - "hashbrown 0.15.5", - "indexmap", - "serde", - "serde_derive", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros 0.13.1", - "phf_shared 0.13.1", - "serde", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand", - "phf_shared 0.13.1", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "pori" -version = "0.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a63d338dec139f56dacc692ca63ad35a6be6a797442479b55acd611d79e906" -dependencies = [ - "nom", -] - -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "portable-pty" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e" -dependencies = [ - "anyhow", - "bitflags 1.3.2", - "downcast-rs", - "filedescriptor", - "lazy_static", - "libc", - "log", - "nix 0.28.0", - "serial2", - "shared_library", - "shell-words", - "winapi", - "winreg", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "preload_test_lib" -version = "0.0.0" -dependencies = [ - "libc", -] - -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "version_check", - "yansi", -] - -[[package]] -name = "pty_terminal" -version = "0.0.0" -dependencies = [ - "anyhow", - "ctor", - "ctrlc", - "nix 0.31.2", - "ntest", - "portable-pty", - "signal-hook 0.4.4", - "subprocess_test", - "terminal_size", - "vt100", -] - -[[package]] -name = "pty_terminal_test" -version = "0.0.0" -dependencies = [ - "anyhow", - "crossterm", - "ctor", - "ntest", - "portable-pty", - "pty_terminal", - "pty_terminal_test_client", - "subprocess_test", -] - -[[package]] -name = "pty_terminal_test_client" -version = "0.0.0" -dependencies = [ - "base64", - "getrandom 0.4.2", - "winapi", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "ratatui" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" -dependencies = [ - "instability", - "ratatui-core", - "ratatui-crossterm", - "ratatui-macros", - "ratatui-termwiz", - "ratatui-widgets", -] - -[[package]] -name = "ratatui-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" -dependencies = [ - "bitflags 2.10.0", - "compact_str", - "hashbrown 0.16.1", - "indoc", - "itertools 0.14.0", - "kasuari", - "lru", - "strum", - "thiserror 2.0.18", - "unicode-segmentation", - "unicode-truncate", - "unicode-width", -] - -[[package]] -name = "ratatui-crossterm" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" -dependencies = [ - "cfg-if", - "crossterm", - "instability", - "ratatui-core", -] - -[[package]] -name = "ratatui-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" -dependencies = [ - "ratatui-core", - "ratatui-widgets", -] - -[[package]] -name = "ratatui-termwiz" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" -dependencies = [ - "ratatui-core", - "termwiz", -] - -[[package]] -name = "ratatui-widgets" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" -dependencies = [ - "bitflags 2.10.0", - "hashbrown 0.16.1", - "indoc", - "instability", - "itertools 0.14.0", - "line-clipping", - "ratatui-core", - "strum", - "time", - "unicode-segmentation", - "unicode-width", -] - -[[package]] -name = "rayon" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.10.0", -] - -[[package]] -name = "redox_syscall" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" -dependencies = [ - "bitflags 2.10.0", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.18", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" - -[[package]] -name = "rsqlite-vfs" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" -dependencies = [ - "hashbrown 0.16.1", - "thiserror 2.0.18", -] - -[[package]] -name = "rusqlite" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" -dependencies = [ - "bitflags 2.10.0", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec 1.15.1", - "sqlite-wasm-rs", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver 1.0.27", -] - -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags 2.10.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "seccompiler" -version = "0.5.0" -source = "git+https://github.com/rust-vmm/seccompiler?rev=08587106340b8e3cb361c7561411510039436857#08587106340b8e3cb361c7561411510039436857" -dependencies = [ - "libc", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "indexmap", - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_norway" -version = "0.9.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e408f29489b5fd500fab51ff1484fc859bb655f32c671f307dcd733b72e8168c" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml-norway", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serial2" -version = "0.2.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc76fa68e25e771492ca1e3c53d447ef0be3093e05cd3b47f4b712ba10c6f3c" -dependencies = [ - "cfg-if", - "libc", - "winapi", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shared_library" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" -dependencies = [ - "lazy_static", - "libc", -] - -[[package]] -name = "shell-escape" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" - -[[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" -dependencies = [ - "libc", - "mio", - "signal-hook 0.3.18", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" - -[[package]] -name = "similar" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" - -[[package]] -name = "similar" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d93e861ede2e497b47833469b8ec9d5c07fa4c78ce7a00f6eb7dd8168b4b3f" -dependencies = [ - "bstr", -] - -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "smallvec" -version = "2.0.0-alpha.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef784004ca8777809dcdad6ac37629f0a97caee4c685fcea805278d81dd8b857" - -[[package]] -name = "snapshot_test" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "similar 3.1.0", -] - -[[package]] -name = "socket2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "sqlite-wasm-rs" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b2c760607300407ddeaee518acf28c795661b7108c75421303dbefb237d3a36" -dependencies = [ - "cc", - "js-sys", - "rsqlite-vfs", - "wasm-bindgen", -] - -[[package]] -name = "stackalloc" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b5b7088084b7f78305a42468c9a3693918620f0b8ec86260f1132dc0521e78" -dependencies = [ - "cc", - "rustc_version 0.2.3", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "subprocess_test" -version = "0.0.0" -dependencies = [ - "base64", - "ctor", - "fspy", - "portable-pty", - "rustc-hash", - "wincode", -] - -[[package]] -name = "supports-color" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" -dependencies = [ - "is-terminal", - "is_ci", -] - -[[package]] -name = "supports-color" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" -dependencies = [ - "is_ci", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syscalls" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81c645a4de0d803ced6ef0388a2646aa1ef8467173b5d59a2c33c88de4ab76e7" - -[[package]] -name = "tar" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "tempfile" -version = "3.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" -dependencies = [ - "fastrand", - "getrandom 0.4.2", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "terminal_size" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" -dependencies = [ - "rustix", - "windows-sys 0.60.2", -] - -[[package]] -name = "terminfo" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" -dependencies = [ - "fnv", - "nom", - "phf 0.11.3", - "phf_codegen", -] - -[[package]] -name = "termios" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" -dependencies = [ - "libc", -] - -[[package]] -name = "termwiz" -version = "0.23.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" -dependencies = [ - "anyhow", - "base64", - "bitflags 2.10.0", - "fancy-regex", - "filedescriptor", - "finl_unicode", - "fixedbitset 0.4.2", - "hex", - "lazy_static", - "libc", - "log", - "memmem", - "nix 0.29.0", - "num-derive", - "num-traits", - "ordered-float", - "pest", - "pest_derive", - "phf 0.11.3", - "sha2 0.10.9", - "signal-hook 0.3.18", - "siphasher", - "terminfo", - "termios", - "thiserror 1.0.69", - "ucd-trie", - "unicode-segmentation", - "vtparse", - "wezterm-bidi", - "wezterm-blob-leases", - "wezterm-color-types", - "wezterm-dynamic", - "wezterm-input-types", - "winapi", -] - -[[package]] -name = "test-log" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d53ac171c92a39e4769491c4b4dde7022c60042254b5fc044ae409d34a24d4" -dependencies = [ - "env_logger", - "test-log-macros", - "tracing-subscriber", -] - -[[package]] -name = "test-log-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde_core", - "time-core", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "tokio" -version = "1.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 1.0.2", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.23.10+spec-1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" -dependencies = [ - "indexmap", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "winnow 0.7.14", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.2", -] - -[[package]] -name = "toml_writer" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "serde", - "sharded-slab", - "smallvec 1.15.1", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "ts-rs" -version = "12.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756050066659291d47a554a9f558125db17428b073c5ffce1daf5dcb0f7231d8" -dependencies = [ - "thiserror 2.0.18", - "ts-rs-macros", -] - -[[package]] -name = "ts-rs-macros" -version = "12.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d90eea51bc7988ef9e674bf80a85ba6804739e535e9cab48e4bb34a8b652aa" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "termcolor", -] - -[[package]] -name = "tui-term" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f4d2473af6ae50523181a971dd8c8557416ece8ba4fcd2d63331be6f73759c" -dependencies = [ - "ratatui-core", - "ratatui-widgets", - "vt100", -] - -[[package]] -name = "twox-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" -dependencies = [ - "rand 0.9.2", -] - -[[package]] -name = "typenum" -version = "1.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] -name = "unicode-ident" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-truncate" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" -dependencies = [ - "itertools 0.14.0", - "unicode-segmentation", - "unicode-width", -] - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "unsafe-libyaml-norway" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39abd59bf32521c7f2301b52d05a6a2c975b6003521cbd0c6dc1582f0a22104" - -[[package]] -name = "utf8-chars" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe49e006d6df172d7f14794568a90fe41e05a1fa9e03dc276fa6da4bb747ec3" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" -dependencies = [ - "atomic", - "getrandom 0.4.2", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "vec1" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab68b56840f69efb0fefbe3ab6661499217ffdc58e2eef7c3f6f69835386322" -dependencies = [ - "serde", - "smallvec 1.15.1", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vite_glob" -version = "0.0.0" -dependencies = [ - "globset", - "thiserror 2.0.18", - "vite_str", - "wax", -] - -[[package]] -name = "vite_graph_ser" -version = "0.1.0" -dependencies = [ - "petgraph", - "serde", - "serde_json", -] - -[[package]] -name = "vite_path" -version = "0.1.0" -dependencies = [ - "assert2", - "diff-struct", - "os_str_bytes", - "path-clean", - "ref-cast", - "serde", - "thiserror 2.0.18", - "ts-rs", - "vite_str", - "wincode", -] - -[[package]] -name = "vite_powershell" -version = "0.1.0" -dependencies = [ - "tempfile", - "vite_path", - "which", -] - -[[package]] -name = "vite_select" -version = "0.0.0" -dependencies = [ - "anyhow", - "assert2", - "crossterm", - "nucleo-matcher", - "vite_str", -] - -[[package]] -name = "vite_shell" -version = "0.0.0" -dependencies = [ - "brush-parser", - "diff-struct", - "serde", - "shell-escape", - "vite_str", - "wincode", -] - -[[package]] -name = "vite_str" -version = "0.1.0" -dependencies = [ - "compact_str", - "diff-struct", - "serde", - "ts-rs", - "wincode", -] - -[[package]] -name = "vite_task" -version = "0.0.0" -dependencies = [ - "anstream 1.0.0", - "anyhow", - "async-trait", - "clap", - "ctrlc", - "derive_more", - "fspy", - "futures-util", - "materialized_artifact", - "materialized_artifact_build", - "nix 0.31.2", - "once_cell", - "owo-colors", - "petgraph", - "pty_terminal_test_client", - "rayon", - "rusqlite", - "rustc-hash", - "serde", - "serde_json", - "supports-color 3.0.2", - "tar", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tokio-util", - "tracing", - "twox-hash", - "uuid", - "vite_glob", - "vite_path", - "vite_select", - "vite_str", - "vite_task_client_napi", - "vite_task_graph", - "vite_task_ipc_shared", - "vite_task_plan", - "vite_task_server", - "vite_workspace", - "wax", - "winapi", - "wincode", - "zstd", -] - -[[package]] -name = "vite_task_bin" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "clap", - "cow-utils", - "cp_r", - "ctrlc", - "jsonc-parser", - "libc", - "libtest-mimic", - "nix 0.31.2", - "notify", - "preload_test_lib", - "pty_terminal", - "pty_terminal_test", - "pty_terminal_test_client", - "regex", - "serde", - "serde_json", - "shell-escape", - "snapshot_test", - "tempfile", - "tokio", - "toml", - "vec1", - "vite_path", - "vite_str", - "vite_task", - "vite_workspace", - "which", -] - -[[package]] -name = "vite_task_client" -version = "0.0.0" -dependencies = [ - "native_str", - "rustc-hash", - "vite_path", - "vite_task_ipc_shared", - "winapi", - "wincode", -] - -[[package]] -name = "vite_task_client_napi" -version = "0.1.0" -dependencies = [ - "napi", - "napi-build", - "napi-derive", - "vite_str", - "vite_task_client", -] - -[[package]] -name = "vite_task_graph" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "monostate", - "petgraph", - "pretty_assertions", - "rustc-hash", - "serde", - "serde_json", - "thiserror 2.0.18", - "tracing", - "ts-rs", - "vec1", - "vite_path", - "vite_str", - "vite_workspace", - "wax", - "wincode", -] - -[[package]] -name = "vite_task_ipc_shared" -version = "0.0.0" -dependencies = [ - "native_str", - "rustc-hash", - "wincode", -] - -[[package]] -name = "vite_task_plan" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "clap", - "copy_dir", - "cow-utils", - "futures-util", - "libtest-mimic", - "pathdiff", - "petgraph", - "rustc-hash", - "serde", - "serde_json", - "sha2 0.11.0", - "shell-escape", - "snapshot_test", - "tempfile", - "thiserror 2.0.18", - "tokio", - "toml", - "tracing", - "vite_glob", - "vite_graph_ser", - "vite_path", - "vite_powershell", - "vite_shell", - "vite_str", - "vite_task", - "vite_task_bin", - "vite_task_graph", - "vite_workspace", - "which", - "wincode", -] - -[[package]] -name = "vite_task_server" -version = "0.0.0" -dependencies = [ - "futures", - "native_str", - "rustc-hash", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tokio-util", - "tracing", - "uuid", - "vite_glob", - "vite_path", - "vite_task_client", - "vite_task_ipc_shared", - "wincode", -] - -[[package]] -name = "vite_tui" -version = "0.0.0" -dependencies = [ - "color-eyre", - "crossterm", - "directories", - "futures", - "portable-pty", - "ratatui", - "rustc-hash", - "tokio", - "tokio-util", - "tracing", - "tracing-error", - "tracing-subscriber", - "tui-term", -] - -[[package]] -name = "vite_workspace" -version = "0.0.0" -dependencies = [ - "clap", - "petgraph", - "rustc-hash", - "serde", - "serde_json", - "serde_norway", - "tempfile", - "thiserror 2.0.18", - "tracing", - "vec1", - "vite_glob", - "vite_path", - "vite_str", - "wax", -] - -[[package]] -name = "vt100" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ff75fb8fa83e609e685106df4faeffdf3a735d3c74ebce97ec557d5d36fd9" -dependencies = [ - "itoa", - "unicode-width", - "vte", -] - -[[package]] -name = "vte" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" -dependencies = [ - "arrayvec", - "memchr", -] - -[[package]] -name = "vtparse" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.117", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.10.0", - "hashbrown 0.15.5", - "indexmap", - "semver 1.0.27", -] - -[[package]] -name = "wax" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8cbf8125142b9b30321ac8721f54c52fbcd6659f76cf863d5e2e38c07a3d7b" -dependencies = [ - "const_format", - "itertools 0.14.0", - "nom", - "pori", - "regex", - "thiserror 2.0.18", - "walkdir", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wezterm-bidi" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" -dependencies = [ - "log", - "wezterm-dynamic", -] - -[[package]] -name = "wezterm-blob-leases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" -dependencies = [ - "getrandom 0.3.4", - "mac_address", - "sha2 0.10.9", - "thiserror 1.0.69", - "uuid", -] - -[[package]] -name = "wezterm-color-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" -dependencies = [ - "csscolorparser", - "deltae", - "lazy_static", - "wezterm-dynamic", -] - -[[package]] -name = "wezterm-dynamic" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" -dependencies = [ - "log", - "ordered-float", - "strsim", - "thiserror 1.0.69", - "wezterm-dynamic-derive", -] - -[[package]] -name = "wezterm-dynamic-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "wezterm-input-types" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" -dependencies = [ - "bitflags 1.3.2", - "euclid", - "lazy_static", - "serde", - "wezterm-dynamic", -] - -[[package]] -name = "which" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" -dependencies = [ - "env_home", - "rustix", - "tracing", - "winsafe 0.0.19", -] - -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "wincode" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5d39d1a984eb7ae37afa348f058216d62a6d5f71640f4113a8114386c2a812a" -dependencies = [ - "pastey", - "proc-macro2", - "quote", - "thiserror 2.0.18", - "wincode-derive", -] - -[[package]] -name = "wincode-derive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb427b174d0f50b407f003623db1d892986e780651ee9d4231f3c9fe9ffcbb7" -dependencies = [ - "darling 0.23.0", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" - -[[package]] -name = "winsafe" -version = "0.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcfff8264c3af1b0352006934e2265365ecb5a145aee88e770dc8b82e0456f4f" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.10.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver 1.0.27", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix", -] - -[[package]] -name = "xxhash-rust" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "zerocopy" -version = "0.8.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "zmij" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 804541230..000000000 --- a/Cargo.toml +++ /dev/null @@ -1,201 +0,0 @@ -[workspace] -resolver = "3" -members = ["crates/*"] - -[workspace.package] -authors = ["Vite+ Authors"] -edition = "2024" -license = "MIT" -publish = false -rust-version = "1.89.0" - -[workspace.lints.rust] -absolute_paths_not_starting_with_crate = "warn" -non_ascii_idents = "warn" -unit-bindings = "warn" -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage)', 'cfg(coverage_nightly)'] } -tail_expr_drop_order = "warn" -unsafe_op_in_unsafe_fn = "warn" -unused_unsafe = "warn" -long_running_const_eval = "allow" - -[workspace.lints.clippy] -all = { level = "warn", priority = -1 } -# restriction -dbg_macro = "warn" -todo = "warn" -unimplemented = "warn" -print_stdout = "warn" -print_stderr = "warn" -allow_attributes = "warn" -allow_attributes_without_reason = "warn" -undocumented_unsafe_blocks = "warn" -pedantic = { level = "warn", priority = -1 } -nursery = { level = "warn", priority = -1 } -cargo = { level = "warn", priority = -1 } -cargo_common_metadata = "allow" -multiple_crate_versions = "allow" -# The task scheduling workflow is a single-threaded async task, so Send bounds are unnecessary. -future_not_send = "allow" - -[workspace.dependencies] -anstream = "1.0.0" -anyhow = "1.0.103" -assert2 = "0.4.0" -assertables = "10.0.0" -async-trait = "0.1.89" -base64 = "0.22.1" -wincode = "0.6.0" -bindgen = "0.72.1" -bitflags = "2.10.0" -brush-parser = "0.4.0" -bstr = { version = "1.12.0", default-features = false, features = ["alloc", "std"] } -bumpalo = { version = "3.17.0", features = ["collections"] } -bytemuck = { version = "1.23.0", features = ["extern_crate_alloc", "must_cast"] } -cc = "1.2.39" -clap = "4.5.53" -color-eyre = "0.6.5" -compact_str = "0.9.0" -constcat = "0.6.1" -copy_dir = "0.1.3" -cow-utils = "0.1.3" -cp_r = "0.5.2" -crossterm = { version = "0.29.0", features = ["event-stream"] } -csv-async = { version = "1.3.1", features = ["tokio"] } -ctor = "1.0" -ctrlc = "3.5.2" -derive_more = "2.0.1" -diff-struct = "0.5.3" -directories = "6.0.0" -elf = { version = "0.8.0", default-features = false } -materialized_artifact = { path = "crates/materialized_artifact" } -materialized_artifact_build = { path = "crates/materialized_artifact_build" } -flate2 = "1.0.35" -fspy = { path = "crates/fspy" } -fspy_detours_sys = { path = "crates/fspy_detours_sys" } -fspy_preload_unix = { path = "crates/fspy_preload_unix", artifact = "cdylib", target = "target" } -fspy_preload_windows = { path = "crates/fspy_preload_windows", artifact = "cdylib", target = "target" } -fspy_seccomp_unotify = { path = "crates/fspy_seccomp_unotify" } -fspy_shm = { path = "crates/fspy_shm" } -fspy_shared = { path = "crates/fspy_shared" } -fspy_shared_unix = { path = "crates/fspy_shared_unix" } -futures = "0.3.31" -futures-util = "0.3.31" -globset = "0.4.18" -getrandom = "0.4.2" -jsonc-parser = { version = "0.32.0", features = ["serde"] } -libc = "0.2.185" -libtest-mimic = "0.8.2" -memmap2 = "0.9.11" -memfd = "0.6.5" -monostate = "1.0.2" -napi = "3" -napi-build = "2" -napi-derive = "3" -native_str = { path = "crates/native_str" } -nix = { version = "0.31.2", features = ["dir", "signal"] } -ntapi = "0.4.1" -nucleo-matcher = "0.3.1" -once_cell = "1.19" -os_str_bytes = "7.1.1" -ouroboros = "0.18.5" -owo-colors = { version = "4.1.0", features = ["supports-colors"] } -passfd = { git = "https://github.com/polachok/passfd", rev = "d55881752c16aced1a49a75f9c428d38d3767213", default-features = false } -notify = "8.0.0" -path-clean = "1.0.1" -pathdiff = "0.2.3" -petgraph = "0.8.2" -phf = { version = "0.13.0", features = ["macros"] } -portable-pty = "0.9.0" -pretty_assertions = "1.4.1" -pty_terminal = { path = "crates/pty_terminal" } -pty_terminal_test = { path = "crates/pty_terminal_test" } -pty_terminal_test_client = { path = "crates/pty_terminal_test_client" } -ratatui = "0.30.0" -rayon = "1.10.0" -ref-cast = "1.0.24" -regex = "1.11.3" -rusqlite = "0.39.0" -rustc-hash = "2.1.1" -# SeccompAction::UserNotif (SECCOMP_RET_USER_NOTIF) was added after the latest published release (v0.5.0) -seccompiler = { git = "https://github.com/rust-vmm/seccompiler", rev = "08587106340b8e3cb361c7561411510039436857" } -serde = "1.0.219" -serde_json = "1.0.140" -serde_norway = "0.9.42" -sha2 = "0.11.0" -shell-escape = "0.1.5" -similar = "3.0.0" -smallvec = { version = "2.0.0-alpha.12", features = ["std"] } -snapshot_test = { path = "crates/snapshot_test" } -stackalloc = "1.2.1" -subprocess_test = { path = "crates/subprocess_test" } -supports-color = "3.0.1" -syscalls = { version = "0.8.0", default-features = false } -tar = "0.4.45" -tempfile = "3.14.0" -test-log = { version = "0.2.18", features = ["trace"] } -thiserror = "2" -tokio = "1.48.0" -tokio-util = "0.7.17" -toml = "1.0.0" -tracing = "0.1.43" -tracing-error = "0.2.1" -tracing-subscriber = { version = "0.3.19", features = ["env-filter", "serde"] } -ts-rs = { version = "12.0.0" } -tui-term = "0.3.1" -twox-hash = "2.1.1" -uuid = "1.18.1" -vec1 = "1.12.1" -vite_glob = { path = "crates/vite_glob" } -vite_graph_ser = { path = "crates/vite_graph_ser" } -vite_path = { path = "crates/vite_path" } -vite_powershell = { path = "crates/vite_powershell" } -vite_select = { path = "crates/vite_select" } -vite_shell = { path = "crates/vite_shell" } -vite_str = { path = "crates/vite_str" } -vite_task = { path = "crates/vite_task" } -vite_task_bin = { path = "crates/vite_task_bin" } -vite_task_client = { path = "crates/vite_task_client" } -vite_task_client_napi = { path = "crates/vite_task_client_napi", artifact = "cdylib", target = "target" } -vite_task_graph = { path = "crates/vite_task_graph" } -vite_task_ipc_shared = { path = "crates/vite_task_ipc_shared" } -vite_task_plan = { path = "crates/vite_task_plan" } -vite_task_server = { path = "crates/vite_task_server" } -vite_workspace = { path = "crates/vite_workspace" } -vt100 = "0.16.2" -wax = "0.7.0" -which = "8.0.0" -widestring = "1.2.0" -winapi = "0.3.9" -windows-sys = "0.61" -winsafe = { version = "0.0.27", features = ["kernel"] } -xxhash-rust = { version = "0.8.15", features = ["const_xxh3"] } -ntest = "0.9.5" -terminal_size = "0.4" -zstd = "0.13" - -[workspace.metadata.cargo-shear] -ignored = [ - # These are artifact dependencies. They are not directly `use`d in Rust code. - "fspy_preload_unix", - "fspy_preload_windows", - "vite_task_client_napi", -] - -[profile.dev] -# Disabling debug info speeds up local and CI builds, -# and we don't rely on it for debugging that much. -debug = false - -[profile.test] -debug = false - -[profile.release] -# Configurations explicitly listed here for clarity. -# Using the best options for performance. -opt-level = 3 -lto = "fat" -codegen-units = 1 -strip = "symbols" # set to `false` for debug information -debug = false # set to `true` for debug information -panic = "abort" # Let it crash and force ourselves to write safe Rust. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index cf067b10e..000000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026-present, VoidZero Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 26206b796..000000000 --- a/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Vite Task - -Monorepo task runner with intelligent caching and dependency-aware scheduling, powering [`vp run`](https://github.com/voidzero-dev/vite-plus) in [Vite+](https://viteplus.dev). - -## Getting Started - -Install [Vite+](https://viteplus.dev), then run tasks from your workspace. See the [documentation](https://viteplus.dev/guide/run) for full usage. - -```bash -vp run build # run a task in the current package -vp run -r build # run across all packages in dependency order -vp run -t @my/app#build # run in a package and its transitive dependencies -vp run --cache build # run with caching enabled -``` - -## Sponsors - -Thanks to [namespace.so](https://namespace.so) for powering our CI/CD pipelines with fast, free macOS and Linux runners. - -## License - -[MIT](LICENSE) - -Copyright (c) 2026-present [VoidZero Inc.](https://voidzero.dev/) diff --git a/crates/fspy/.clippy.toml b/crates/fspy/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/fspy/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/fspy/Cargo.toml b/crates/fspy/Cargo.toml deleted file mode 100644 index 236a0f7bd..000000000 --- a/crates/fspy/Cargo.toml +++ /dev/null @@ -1,78 +0,0 @@ -[package] -name = "fspy" -version = "0.1.0" -edition = "2024" -license.workspace = true -publish = false - -[dependencies] -wincode = { workspace = true } -bstr = { workspace = true, default-features = false } -bumpalo = { workspace = true } -derive_more = { workspace = true, features = ["debug"] } -materialized_artifact = { workspace = true } -fspy_shared = { workspace = true } -futures-util = { workspace = true } -libc = { workspace = true } -ouroboros = { workspace = true } -rustc-hash = { workspace = true } -tempfile = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true, features = ["net", "process", "io-util", "sync", "rt"] } -tokio-util = { workspace = true } -which = { workspace = true, features = ["tracing"] } - -[target.'cfg(target_os = "linux")'.dependencies] -fspy_seccomp_unotify = { workspace = true, features = ["supervisor"] } -nix = { workspace = true, features = ["uio"] } -tokio = { workspace = true, features = ["bytes"] } - -[target.'cfg(unix)'.dependencies] -fspy_shared_unix = { workspace = true } -nix = { workspace = true, features = ["fs", "process", "socket", "feature"] } - -[target.'cfg(target_os = "windows")'.dependencies] -fspy_detours_sys = { workspace = true } -winapi = { workspace = true, features = ["winbase", "securitybaseapi", "handleapi"] } -winsafe = { workspace = true } - -[target.'cfg(target_os = "macos")'.dev-dependencies] -tempfile = { workspace = true } - -[dev-dependencies] -anyhow = { workspace = true } -csv-async = { workspace = true } -ctor = { workspace = true } -ntest = { workspace = true } -subprocess_test = { workspace = true, features = ["fspy"] } -test-log = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread", "macros", "fs", "io-std"] } - -[target.'cfg(all(target_os = "linux", target_arch = "aarch64"))'.dev-dependencies] -fspy_test_bin = { path = "../fspy_test_bin", artifact = "bin", target = "aarch64-unknown-linux-musl" } - -[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dev-dependencies] -fspy_test_bin = { path = "../fspy_test_bin", artifact = "bin", target = "x86_64-unknown-linux-musl" } - -# Artifact build-deps must be unconditional: cargo's resolver panics when -# `artifact = "cdylib"` deps live under a `[target.cfg.build-dependencies]` -# block on cross-compile. Each preload crate's source is cfg-gated to compile -# as an empty cdylib on non-applicable targets, so the unused cross-target -# builds are cheap. -[build-dependencies] -anyhow = { workspace = true } -materialized_artifact_build = { workspace = true } -flate2 = { workspace = true } -fspy_preload_unix = { workspace = true } -fspy_preload_windows = { workspace = true } -sha2 = { workspace = true } -tar = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false - -[package.metadata.cargo-shear] -ignored = ["ctor", "fspy_test_bin", "fspy_preload_unix", "fspy_preload_windows"] diff --git a/crates/fspy/README.md b/crates/fspy/README.md deleted file mode 100644 index cf7fcba0e..000000000 --- a/crates/fspy/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# fspy - -Run a command and capture all the paths it tries to access. - -## macOS/Linux (glibc) implementation - -It uses `DYLD_INSERT_LIBRARIES` on macOS and `LD_PRELOAD` on Linux to inject a shared library that intercepts file system calls. -The injection process is almost identical on both platforms other than the environment variable name. The implementation is in `src/unix`. - -## Linux-specific implementation for fully static binaries - -For fully static binaries (such as `esbuild`), `LD_PRELOAD` does not work. In this case, `seccomp_unotify` is used to intercept direct system calls. The handler is implemented in `src/unix/syscall_handler`. - -## Linux musl implementation - -On musl targets, only `seccomp_unotify`-based tracking is used (no preload library). - -## Windows implementation - -It uses [Detours](https://github.com/microsoft/Detours) to intercept file system calls. The implementation is in `src/windows`. - -## Unified interface - -The unified interface of `Command` is in `src/command.rs`. - -## Preload Libraries - -`DYLD_INSERT_LIBRARIES`, `LD_PRELOAD`, `Detours` all require a shared library to be injected. The shared libraries of macOS/Linux are in the `fspy_preload_unix` crate, and the shared library of Windows is in the `fspy_preload_windows` crate. diff --git a/crates/fspy/build.rs b/crates/fspy/build.rs deleted file mode 100644 index 90b7bbcf4..000000000 --- a/crates/fspy/build.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::{ - env, - fmt::Write as _, - fs, - io::{Cursor, Read}, - path::{Path, PathBuf}, - process::{Command, Stdio}, -}; - -use anyhow::{Context, bail}; -use sha2::{Digest, Sha256}; - -fn download(url: &str) -> anyhow::Result> { - let curl = Command::new("curl") - .args([ - "-f", // fail on HTTP errors - "-L", // follow redirects - url, - ]) - .stdout(Stdio::piped()) - .spawn()?; - let output = curl.wait_with_output()?; - if !output.status.success() { - bail!("curl exited with status {} trying to download {}", output.status, url); - } - Ok(output.stdout) -} - -fn unpack_tar_gz(tarball: impl Read, path: &str) -> anyhow::Result> { - use flate2::read::GzDecoder; - use tar::Archive; - - let tar = GzDecoder::new(tarball); - let mut archive = Archive::new(tar); - for entry in archive.entries()? { - let mut entry = entry?; - if entry.path_bytes().as_ref() == path.as_bytes() { - let mut data = Vec::::with_capacity(entry.size().try_into().unwrap()); - entry.read_to_end(&mut data)?; - return Ok(data); - } - } - bail!("Path {path} not found in tar gz") -} - -fn sha256_hex(bytes: &[u8]) -> String { - let digest = Sha256::digest(bytes); - let mut s = String::with_capacity(64); - for b in digest { - write!(&mut s, "{b:02x}").unwrap(); - } - s -} - -struct BinaryDownload { - /// Identifier used both as the on-disk filename in `OUT_DIR` and as the - /// env-var prefix consumed by `artifact!($name)` at runtime. - name: &'static str, - /// GitHub release asset URL. - url: &'static str, - /// Path of the binary within the tarball. - path_in_targz: &'static str, - /// SHA-256 of the extracted binary. Doubles as the cache key: an - /// already-extracted binary in `OUT_DIR` whose content hashes to this - /// value is reused without hitting the network. - expected_sha256: &'static str, -} - -const MACOS_BINARY_DOWNLOADS: &[(&str, &[BinaryDownload])] = &[ - ( - "aarch64", - &[ - // https://github.com/wan9chi/oils-for-unix-build/releases/tag/oils-for-unix-0.37.0 - BinaryDownload { - name: "oils_for_unix", - url: "https://github.com/wan9chi/oils-for-unix-build/releases/download/oils-for-unix-0.37.0/oils-for-unix-0.37.0-darwin-arm64.tar.gz", - path_in_targz: "oils-for-unix", - expected_sha256: "ce4bb80b15f0a0371af08b19b65bfa5ea17d30429ebb911f487de3d2bcc7a07d", - }, - // https://github.com/uutils/coreutils/releases/tag/0.4.0 - BinaryDownload { - name: "coreutils", - url: "https://github.com/uutils/coreutils/releases/download/0.4.0/coreutils-0.4.0-aarch64-apple-darwin.tar.gz", - path_in_targz: "coreutils-0.4.0-aarch64-apple-darwin/coreutils", - expected_sha256: "8e8f38d9323135a19a73d617336fce85380f3c46fcb83d3ae3e031d1c0372f21", - }, - ], - ), - ( - "x86_64", - &[ - // https://github.com/wan9chi/oils-for-unix-build/releases/tag/oils-for-unix-0.37.0 - BinaryDownload { - name: "oils_for_unix", - url: "https://github.com/wan9chi/oils-for-unix-build/releases/download/oils-for-unix-0.37.0/oils-for-unix-0.37.0-darwin-x86_64.tar.gz", - path_in_targz: "oils-for-unix", - expected_sha256: "cf1a95993127770e2a5fff277cd256a2bb28cf97d7f83ae42fdccc172cdb540d", - }, - // https://github.com/uutils/coreutils/releases/tag/0.4.0 - BinaryDownload { - name: "coreutils", - url: "https://github.com/uutils/coreutils/releases/download/0.4.0/coreutils-0.4.0-x86_64-apple-darwin.tar.gz", - path_in_targz: "coreutils-0.4.0-x86_64-apple-darwin/coreutils", - expected_sha256: "6be8bee6e8b91fc44a465203b9cc30538af00084b6657dc136d9e55837753eb1", - }, - ], - ), -]; - -fn fetch_macos_binaries(out_dir: &Path) -> anyhow::Result<()> { - if env::var("CARGO_CFG_TARGET_OS").unwrap() != "macos" { - return Ok(()); - } - - let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - let downloads = MACOS_BINARY_DOWNLOADS - .iter() - .find(|(arch, _)| *arch == target_arch) - .context(format!("Unsupported macOS arch: {target_arch}"))? - .1; - - for BinaryDownload { name, url, path_in_targz, expected_sha256 } in downloads { - let dest = out_dir.join(name); - // Cache hit: an already-extracted binary whose contents hash to - // `expected_sha256` is known-good and reused without redownloading. - let cached = matches!( - fs::read(&dest), - Ok(existing) if sha256_hex(&existing) == *expected_sha256, - ); - if !cached { - let tarball = download(url).context(format!("Failed to download {url}"))?; - let data = unpack_tar_gz(Cursor::new(tarball), path_in_targz) - .context(format!("Failed to extract {path_in_targz} from {url}"))?; - let actual_sha256 = sha256_hex(&data); - assert_eq!( - &actual_sha256, expected_sha256, - "sha256 of {path_in_targz} in {url} does not match — update expected value in MACOS_BINARY_DOWNLOADS", - ); - fs::write(&dest, &data).with_context(|| format!("writing {}", dest.display()))?; - } - materialized_artifact_build::register(name, &dest); - } - Ok(()) -} - -fn register_preload_cdylib() -> anyhow::Result<()> { - let env_name = match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() { - "windows" => "CARGO_CDYLIB_FILE_FSPY_PRELOAD_WINDOWS", - _ if env::var("CARGO_CFG_TARGET_ENV").unwrap() == "musl" => return Ok(()), - _ => "CARGO_CDYLIB_FILE_FSPY_PRELOAD_UNIX", - }; - // The cdylib path is content-addressed by cargo; when its content changes - // the path changes. Track it so we re-publish the hash on update. - println!("cargo:rerun-if-env-changed={env_name}"); - let dylib_path = env::var_os(env_name).with_context(|| format!("{env_name} not set"))?; - materialized_artifact_build::register("fspy_preload", Path::new(&dylib_path)); - Ok(()) -} - -fn main() -> anyhow::Result<()> { - println!("cargo:rerun-if-changed=build.rs"); - let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); - fetch_macos_binaries(&out_dir).context("Failed to fetch macOS binaries")?; - register_preload_cdylib().context("Failed to register preload cdylib")?; - Ok(()) -} diff --git a/crates/fspy/examples/cli.rs b/crates/fspy/examples/cli.rs deleted file mode 100644 index 4ae34790f..000000000 --- a/crates/fspy/examples/cli.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::{env::args_os, ffi::OsStr, path::PathBuf, pin::Pin}; - -use tokio::{ - fs::File, - io::{AsyncWrite, stdout}, -}; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let mut args = args_os(); - let _ = args.next(); - assert_eq!(args.next().as_deref(), Some(OsStr::new("-o"))); - - let out_path = args.next().unwrap(); - - let program = PathBuf::from(args.next().unwrap()); - - let mut command = fspy::Command::new(program); - command.envs(std::env::vars_os()).args(args); - - let child = command.spawn(tokio_util::sync::CancellationToken::new()).await?; - let termination = child.wait_handle.await?; - - let mut path_count = 0usize; - let out_file: Pin> = - if out_path == "-" { Box::pin(stdout()) } else { Box::pin(File::create(out_path).await?) }; - - let mut csv_writer = csv_async::AsyncWriter::from_writer(out_file); - - for acc in termination.path_accesses.iter() { - path_count += 1; - let path_str = format!("{:?}", acc.path); - let mode_str = format!("{:?}", acc.mode); - csv_writer.write_record(&[path_str.as_bytes(), mode_str.as_bytes()]).await?; - } - csv_writer.flush().await?; - - #[expect( - clippy::print_stderr, - reason = "CLI example: stderr output is intentional for user feedback" - )] - { - eprintln!("\nfspy: {path_count} paths accessed. status: {}", termination.status); - } - Ok(()) -} diff --git a/crates/fspy/src/arena.rs b/crates/fspy/src/arena.rs deleted file mode 100644 index e0d466ba7..000000000 --- a/crates/fspy/src/arena.rs +++ /dev/null @@ -1,36 +0,0 @@ -use bumpalo::{Bump, collections::Vec}; - -use crate::PathAccess; - -#[ouroboros::self_referencing] -#[derive(Debug)] -pub struct PathAccessArena { - pub bump: Bump, - #[borrows(bump)] - #[covariant] - // TODO(pref): use linked list to avoid realloc & copy. We don't need random access. - pub accesses: Vec<'this, PathAccess<'this>>, -} - -impl Default for PathAccessArena { - fn default() -> Self { - Self::new(Bump::new(), |bump| Vec::new_in(bump)) - } -} - -impl PathAccessArena { - pub fn add(&mut self, access: PathAccess<'_>) { - self.with_mut(|fields| { - let path = access.path.clone_in(fields.bump); - let path_access = PathAccess { mode: access.mode, path }; - fields.accesses.push(path_access); - }); - } -} - -#[expect( - clippy::non_send_fields_in_send_ty, - reason = "bump and accesses are safe to be sent across threads together" -)] -/// SAFETY: bump and accesses are safe to send together -unsafe impl Send for PathAccessArena {} diff --git a/crates/fspy/src/command.rs b/crates/fspy/src/command.rs deleted file mode 100644 index fb150b26c..000000000 --- a/crates/fspy/src/command.rs +++ /dev/null @@ -1,265 +0,0 @@ -use std::{ - ffi::{OsStr, OsString}, - path::{Path, PathBuf}, - process::Stdio, -}; - -#[cfg(unix)] -use fspy_shared_unix::exec::Exec; -use rustc_hash::FxHashMap; -use tokio::process::Command as TokioCommand; -use tokio_util::sync::CancellationToken; - -use crate::{SPY_IMPL, TrackedChild, error::SpawnError}; - -#[derive(derive_more::Debug)] -pub struct Command { - program: OsString, - args: Vec, - envs: FxHashMap, - cwd: Option, - #[cfg(unix)] - arg0: Option, - - stderr: Option, - stdout: Option, - stdin: Option, - - #[cfg(unix)] - #[debug("({} pre_exec closures)", pre_exec_closures.len())] - pre_exec_closures: Vec std::io::Result<()> + Send + Sync>>, -} - -impl Command { - /// Create a new command to spy on the given program. - /// Initially, environment variables are not inherited from the parent. - /// To inherit, explicitly use `.envs(std::env::vars_os())`. - pub fn new>(program: P) -> Self { - Self { - program: program.as_ref().to_os_string(), - args: Vec::new(), - envs: FxHashMap::default(), - cwd: None, - #[cfg(unix)] - arg0: None, - stderr: None, - stdout: None, - stdin: None, - #[cfg(unix)] - pre_exec_closures: Vec::new(), - } - } - - #[cfg(unix)] - #[must_use] - pub(crate) fn get_exec(&self) -> Exec { - use std::{ - iter::once, - os::unix::ffi::{OsStrExt, OsStringExt}, - }; - - use bstr::{BString, ByteSlice as _}; - let arg0 = - BString::from(self.arg0.clone().unwrap_or_else(|| self.program.clone()).into_vec()); - Exec { - program: self.program.as_bytes().into(), - args: once(arg0) - .chain(self.args.iter().map(|arg| arg.as_bytes().as_bstr().to_owned())) - .collect(), - envs: self - .envs - .iter() - .map(|(name, value)| (name.as_bytes().into(), Some(value.as_bytes().into()))) - .collect(), - } - } - - #[cfg(unix)] - pub(crate) fn set_exec(&mut self, mut exec: Exec) { - use std::os::unix::ffi::OsStringExt; - - self.program = OsString::from_vec(exec.program.into()); - self.arg0 = Some(OsString::from_vec(exec.args.remove(0).into())); - self.args = exec.args.into_iter().map(|arg| OsString::from_vec(arg.into())).collect(); - self.envs = exec - .envs - .into_iter() - .map(|(name, value)| { - ( - OsString::from_vec(name.into()), - OsString::from_vec(value.unwrap_or_default().into()), - ) - }) - .collect(); - } - - pub fn env_remove>(&mut self, key: K) -> &mut Self { - self.envs.remove(key.as_ref()); - self - } - - pub fn stderr>(&mut self, cfg: T) -> &mut Self { - self.stderr = Some(cfg.into()); - self - } - - pub fn stdout>(&mut self, cfg: T) -> &mut Self { - self.stdout = Some(cfg.into()); - self - } - - pub fn stdin>(&mut self, cfg: T) -> &mut Self { - self.stdin = Some(cfg.into()); - self - } - - pub fn env(&mut self, key: K, val: V) -> &mut Self - where - K: AsRef, - V: AsRef, - { - self.envs.insert(key.as_ref().to_os_string(), val.as_ref().to_os_string()); - self - } - - pub fn envs(&mut self, vars: I) -> &mut Self - where - I: IntoIterator, - K: AsRef, - V: AsRef, - { - self.envs.extend( - vars.into_iter() - .map(|(key, val)| (key.as_ref().to_os_string(), val.as_ref().to_os_string())), - ); - self - } - - pub fn current_dir>(&mut self, dir: P) -> &mut Self { - self.cwd = Some(dir.as_ref().to_owned()); - self - } - - pub fn arg>(&mut self, arg: S) -> &mut Self { - self.args.push(arg.as_ref().to_os_string()); - self - } - - pub fn args(&mut self, args: I) -> &mut Self - where - I: IntoIterator, - S: AsRef, - { - self.args.extend(args.into_iter().map(|arg| arg.as_ref().to_os_string())); - self - } - - #[cfg(unix)] - pub fn arg0(&mut self, arg: S) -> &mut Self - where - S: AsRef, - { - self.arg0 = Some(arg.as_ref().to_os_string()); - self - } - - /// Spawn the command with file system access tracking. - /// - /// # Errors - /// - /// Returns [`SpawnError`] if program resolution fails or the process cannot be spawned. - pub async fn spawn( - mut self, - cancellation_token: CancellationToken, - ) -> Result { - self.resolve_program()?; - SPY_IMPL.spawn(self, cancellation_token).await - } - - /// Resolve program name to full path using `PATH` and cwd. - /// - /// # Errors - /// - /// Returns [`SpawnError::Which`] if the program cannot be found in `PATH`. - /// - /// # Panics - /// - /// Panics if no `cwd` is set and `std::env::current_dir()` fails. - pub fn resolve_program(&mut self) -> Result<(), SpawnError> { - let mut path_env: Option<&OsStr> = None; - for (env_name, env_value) in &self.envs { - let Some(env_name) = env_name.to_str() else { - continue; - }; - if env_name.eq_ignore_ascii_case("path") { - path_env = Some(env_value.as_ref()); - break; - } - } - - let cwd = self - .cwd - .clone() - .unwrap_or_else(|| std::env::current_dir().expect("failed to get current dir")); - self.program = which::which_in(self.program.as_os_str(), path_env, &cwd) - .map_err(|err| SpawnError::Which { - program: self.program.clone(), - path: path_env.map(OsStr::to_owned), - cwd, - cause: err, - })? - .into_os_string(); - Ok(()) - } - - /// Schedules a closure to be run just before the exec function is invoked. - /// - /// # Safety - /// - /// - #[cfg(unix)] - pub unsafe fn pre_exec(&mut self, f: F) -> &mut Self - where - F: FnMut() -> std::io::Result<()> + Send + Sync + 'static, - { - self.pre_exec_closures.push(Box::new(f)); - self - } - - /// Convert to a `tokio::process::Command` without tracking. - #[must_use] - pub(crate) fn into_tokio_command(self) -> TokioCommand { - let mut tokio_cmd = TokioCommand::new(self.program); - if let Some(cwd) = &self.cwd { - tokio_cmd.current_dir(cwd); - } - - #[cfg(unix)] - if let Some(arg0) = self.arg0 { - tokio_cmd.arg0(arg0); - } - tokio_cmd.args(self.args); - tokio_cmd.env_clear(); - tokio_cmd.envs(self.envs); - - if let Some(stdin) = self.stdin { - tokio_cmd.stdin(stdin); - } - - if let Some(stdout) = self.stdout { - tokio_cmd.stdout(stdout); - } - - if let Some(stderr) = self.stderr { - tokio_cmd.stderr(stderr); - } - - #[cfg(unix)] - for pre_exec in self.pre_exec_closures { - // Safety: The caller of `pre_exec` is responsible for ensuring safety. - unsafe { tokio_cmd.pre_exec(pre_exec) }; - } - - tokio_cmd - } -} diff --git a/crates/fspy/src/error.rs b/crates/fspy/src/error.rs deleted file mode 100644 index 017d82a0e..000000000 --- a/crates/fspy/src/error.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::{ffi::OsString, path::PathBuf}; - -#[derive(thiserror::Error, Debug)] -pub enum SpawnError { - #[error( - "could not resolve the full path of program '{program:?}' with PATH={path:?} under cwd({cwd:?})" - )] - Which { - program: OsString, - path: Option, - cwd: PathBuf, - #[source] - cause: which::Error, - }, - - #[error("failed to initialize seccomp_unotify supervisor: {0}")] - Supervisor(std::io::Error), - - #[error("failed to create IPC channel: {0}")] - ChannelCreation(std::io::Error), - - /// On unix systems, the injection happens before the spawn actually occurs on. - /// On Windows, the injection happens after the spawn but before resuming the process. - #[error("failed to prepare the command for injection: {0}")] - Injection(std::io::Error), - - #[error("underlying os error: {0}")] - OsSpawn(std::io::Error), -} diff --git a/crates/fspy/src/ipc.rs b/crates/fspy/src/ipc.rs deleted file mode 100644 index 51d498600..000000000 --- a/crates/fspy/src/ipc.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::io; - -use fspy_shared::ipc::{ - PathAccess, - channel::{Receiver, ReceiverLockGuard}, -}; -use tokio::task::spawn_blocking; - -// Shared memory size for storing path accesses. -// 4 GiB is large enough to store path accesses in almost any realistic scenario. -// This doesn't allocate physical memory until it's actually used. -pub const SHM_CAPACITY: usize = 4 * 1024 * 1024 * 1024; - -#[ouroboros::self_referencing] -pub struct OwnedReceiverLockGuard { - /// Owns the shared memory - receiver: Receiver, - /// Borrows the shared memory and owns the file lock - #[borrows(receiver)] - #[covariant] - lock_guard: ReceiverLockGuard<'this>, -} - -impl OwnedReceiverLockGuard { - pub fn lock(receiver: Receiver) -> io::Result { - Self::try_new(receiver, fspy_shared::ipc::channel::Receiver::lock) - } - - pub async fn lock_async(receiver: Receiver) -> io::Result { - spawn_blocking(move || Self::lock(receiver)).await.expect("lock task panicked") - } - - pub fn iter_path_accesses(&self) -> impl Iterator> { - self.borrow_lock_guard() - .iter_frames() - .map(|frame| wincode::deserialize_exact(frame).unwrap()) - } -} diff --git a/crates/fspy/src/lib.rs b/crates/fspy/src/lib.rs deleted file mode 100644 index 6c89414ba..000000000 --- a/crates/fspy/src/lib.rs +++ /dev/null @@ -1,65 +0,0 @@ -#![cfg_attr(target_os = "windows", feature(windows_process_extensions_main_thread_handle))] - -pub mod error; - -#[cfg(not(target_env = "musl"))] -mod ipc; - -#[cfg(unix)] -#[path = "./unix/mod.rs"] -mod os_impl; - -#[cfg(target_os = "windows")] -#[path = "./windows/mod.rs"] -mod os_impl; - -#[cfg(unix)] -mod arena; -mod command; - -use std::{env::temp_dir, fs::create_dir, io, process::ExitStatus, sync::LazyLock}; - -pub use command::Command; -pub use fspy_shared::ipc::{AccessMode, PathAccess}; -use futures_util::future::BoxFuture; -pub use os_impl::PathAccessIterable; -use os_impl::SpyImpl; -use tokio::process::{ChildStderr, ChildStdin, ChildStdout}; - -/// The result of a tracked child process upon its termination. -pub struct ChildTermination { - /// The exit status of the child process. - pub status: ExitStatus, - /// The path accesses captured from the child process. - pub path_accesses: PathAccessIterable, -} - -pub struct TrackedChild { - /// The handle for writing to the child's standard input (stdin), if it has - /// been captured. - pub stdin: Option, - - /// The handle for reading from the child's standard output (stdout), if it - /// has been captured. - pub stdout: Option, - - /// The handle for reading from the child's standard error (stderr), if it - /// has been captured. - pub stderr: Option, - - /// The future that resolves to exit status and path accesses when the process exits. - pub wait_handle: BoxFuture<'static, io::Result>, - - /// A duplicated process handle of the child, captured before the tokio `Child` - /// is moved into the background wait task. This is an independently owned handle - /// (via `DuplicateHandle`) so it remains valid even after tokio closes its copy. - /// Callers can use this to assign the process to a Win32 Job Object. - #[cfg(windows)] - pub process_handle: std::os::windows::io::OwnedHandle, -} - -pub(crate) static SPY_IMPL: LazyLock = LazyLock::new(|| { - let tmp_dir = temp_dir().join("fspy"); - let _ = create_dir(&tmp_dir); - SpyImpl::init_in(&tmp_dir).expect("Failed to initialize global spy") -}); diff --git a/crates/fspy/src/unix/macos_artifacts.rs b/crates/fspy/src/unix/macos_artifacts.rs deleted file mode 100644 index 17b014bd7..000000000 --- a/crates/fspy/src/unix/macos_artifacts.rs +++ /dev/null @@ -1,34 +0,0 @@ -use materialized_artifact::{Artifact, artifact}; - -pub const COREUTILS_BINARY: Artifact = artifact!("coreutils"); -pub const OILS_BINARY: Artifact = artifact!("oils_for_unix"); - -#[cfg(test)] -mod tests { - use std::{process::Command, str::from_utf8}; - - use fspy_shared_unix::spawn::COREUTILS_FUNCTIONS_FOR_TEST; - - use super::*; - - #[test] - fn coreutils_functions() { - let tmpdir = tempfile::tempdir().unwrap(); - let coreutils_path = COREUTILS_BINARY.materialize().executable().at(&tmpdir).unwrap(); - let output = Command::new(coreutils_path).arg("--list").output().unwrap(); - let mut expected_functions: Vec<&str> = output - .stdout - .split(|byte| *byte == b'\n') - .filter_map(|line| { - let line = line.trim_ascii(); - if line.is_empty() { None } else { Some(from_utf8(line).unwrap()) } - }) - .collect(); - let mut actual_functions: Vec<&str> = - COREUTILS_FUNCTIONS_FOR_TEST.iter().copied().map(|f| from_utf8(f).unwrap()).collect(); - - expected_functions.sort_unstable(); - actual_functions.sort_unstable(); - assert_eq!(expected_functions, actual_functions); - } -} diff --git a/crates/fspy/src/unix/mod.rs b/crates/fspy/src/unix/mod.rs deleted file mode 100644 index f01f63b5d..000000000 --- a/crates/fspy/src/unix/mod.rs +++ /dev/null @@ -1,201 +0,0 @@ -#[cfg(target_os = "linux")] -mod syscall_handler; - -#[cfg(target_os = "macos")] -mod macos_artifacts; - -use std::{io, path::Path}; - -#[cfg(target_os = "linux")] -use fspy_seccomp_unotify::supervisor::supervise; -use fspy_shared::ipc::PathAccess; -#[cfg(not(target_env = "musl"))] -use fspy_shared::ipc::{NativeStr, channel::channel}; -#[cfg(target_os = "macos")] -use fspy_shared_unix::payload::Artifacts; -use fspy_shared_unix::{ - exec::ExecResolveConfig, - payload::{Payload, encode_payload}, - spawn::handle_exec, -}; -use futures_util::FutureExt; -#[cfg(target_os = "linux")] -use syscall_handler::SyscallHandler; -use tokio::task::spawn_blocking; -use tokio_util::sync::CancellationToken; - -#[cfg(not(target_env = "musl"))] -use crate::ipc::{OwnedReceiverLockGuard, SHM_CAPACITY}; -use crate::{ChildTermination, Command, TrackedChild, arena::PathAccessArena, error::SpawnError}; - -#[derive(Debug)] -pub struct SpyImpl { - #[cfg(target_os = "macos")] - artifacts: Artifacts, - - #[cfg(not(target_env = "musl"))] - preload_path: Box, -} - -impl SpyImpl { - /// Initialize the fs access spy by writing the preload library on disk. - /// - /// On musl targets, we don't build a preload library — - /// only seccomp-based tracking is used. - pub fn init_in(#[cfg_attr(target_env = "musl", allow(unused))] dir: &Path) -> io::Result { - #[cfg(not(target_env = "musl"))] - let preload_path = { - use materialized_artifact::{Artifact, artifact}; - - const PRELOAD_CDYLIB: Artifact = artifact!("fspy_preload"); - - let preload_cdylib_path = PRELOAD_CDYLIB.materialize().suffix(".dylib").at(dir)?; - preload_cdylib_path.as_path().into() - }; - - Ok(Self { - #[cfg(not(target_env = "musl"))] - preload_path, - #[cfg(target_os = "macos")] - artifacts: { - let coreutils_path = - macos_artifacts::COREUTILS_BINARY.materialize().executable().at(dir)?; - let bash_path = macos_artifacts::OILS_BINARY.materialize().executable().at(dir)?; - Artifacts { - bash_path: bash_path.as_path().into(), - coreutils_path: coreutils_path.as_path().into(), - } - }, - }) - } - - pub(crate) async fn spawn( - &self, - mut command: Command, - cancellation_token: CancellationToken, - ) -> Result { - #[cfg(target_os = "linux")] - let supervisor = supervise::().map_err(SpawnError::Supervisor)?; - - #[cfg(not(target_env = "musl"))] - let (ipc_channel_conf, ipc_receiver) = - channel(SHM_CAPACITY).map_err(SpawnError::ChannelCreation)?; - - let payload = Payload { - #[cfg(not(target_env = "musl"))] - ipc_channel_conf, - - #[cfg(target_os = "macos")] - artifacts: self.artifacts.clone(), - - #[cfg(not(target_env = "musl"))] - preload_path: self.preload_path.clone(), - - #[cfg(target_os = "linux")] - seccomp_payload: supervisor.payload().clone(), - }; - - let encoded_payload = encode_payload(payload); - - let mut exec = command.get_exec(); - let mut exec_resolve_accesses = PathAccessArena::default(); - let pre_exec = handle_exec( - &mut exec, - ExecResolveConfig::search_path_enabled(None), - &encoded_payload, - |mode, path| { - exec_resolve_accesses.add(PathAccess { mode, path: path.into() }); - }, - ) - .map_err(|err| SpawnError::Injection(err.into()))?; - command.set_exec(exec); - command.env("FSPY", "1"); - - let mut tokio_command = command.into_tokio_command(); - - // SAFETY: the pre_exec closure only calls pre_exec.run() which is safe to call in a fork context - unsafe { - tokio_command.pre_exec(move || { - if let Some(pre_exec) = pre_exec.as_ref() { - pre_exec.run()?; - } - Ok(()) - }); - } - - // tokio_command.spawn blocks while executing the `pre_exec` closure. - // Run it inside spawn_blocking to avoid blocking the tokio runtime, especially the supervisor loop, - // which needs to accept incoming connections while `pre_exec` is connecting to it. - let mut child = spawn_blocking(move || tokio_command.spawn()) - .await - .map_err(|err| SpawnError::OsSpawn(err.into()))? - .map_err(SpawnError::OsSpawn)?; - - Ok(TrackedChild { - stdin: child.stdin.take(), - stdout: child.stdout.take(), - stderr: child.stderr.take(), - // Keep polling for the child to exit in the background even if `wait_handle` is not awaited, - // because we need to stop the supervisor and lock the channel as soon as the child exits. - wait_handle: tokio::spawn(async move { - let status = tokio::select! { - status = child.wait() => status?, - () = cancellation_token.cancelled() => { - child.start_kill()?; - child.wait().await? - } - }; - - let arenas = std::iter::once(exec_resolve_accesses); - // Stop the supervisor and collect path accesses from it. - #[cfg(target_os = "linux")] - let arenas = arenas.chain( - supervisor - .stop() - .await? - .into_iter() - .map(syscall_handler::SyscallHandler::into_arena), - ); - let arenas = arenas.collect::>(); - - // Lock the ipc channel after the child has exited. - // We are not interested in path accesses from descendants after the main child has exited. - #[cfg(not(target_env = "musl"))] - let ipc_receiver_lock_guard = - OwnedReceiverLockGuard::lock_async(ipc_receiver).await?; - let path_accesses = PathAccessIterable { - arenas, - #[cfg(not(target_env = "musl"))] - ipc_receiver_lock_guard, - }; - - io::Result::Ok(ChildTermination { status, path_accesses }) - }) - .map(|f| f?) // flatten JoinError and io::Result - .boxed(), - }) - } -} - -pub struct PathAccessIterable { - arenas: Vec, - #[cfg(not(target_env = "musl"))] - ipc_receiver_lock_guard: OwnedReceiverLockGuard, -} - -impl PathAccessIterable { - pub fn iter(&self) -> impl Iterator> { - let accesses_in_arena = - self.arenas.iter().flat_map(|arena| arena.borrow_accesses().iter()).copied(); - - #[cfg(not(target_env = "musl"))] - { - let accesses_in_shm = self.ipc_receiver_lock_guard.iter_path_accesses(); - accesses_in_shm.chain(accesses_in_arena) - } - #[cfg(target_env = "musl")] - { - accesses_in_arena - } - } -} diff --git a/crates/fspy/src/unix/syscall_handler/execve.rs b/crates/fspy/src/unix/syscall_handler/execve.rs deleted file mode 100644 index d34ac8c2d..000000000 --- a/crates/fspy/src/unix/syscall_handler/execve.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::io; - -use fspy_seccomp_unotify::supervisor::handler::arg::{CStrPtr, Caller, Fd}; - -use super::SyscallHandler; - -impl SyscallHandler { - fn handle_execve(&mut self, caller: Caller, fd: Fd, path_ptr: CStrPtr) -> io::Result<()> { - // TODO: parse shebangs to track reading interpreters - self.handle_open(caller, fd, path_ptr, libc::O_RDONLY) - } - - pub(super) fn execveat( - &mut self, - caller: Caller, - (fd, path_ptr): (Fd, CStrPtr), - ) -> io::Result<()> { - self.handle_execve(caller, fd, path_ptr) - } - - pub(super) fn execve(&mut self, caller: Caller, (path_ptr,): (CStrPtr,)) -> io::Result<()> { - self.handle_execve(caller, Fd::cwd(), path_ptr) - } -} diff --git a/crates/fspy/src/unix/syscall_handler/getdents.rs b/crates/fspy/src/unix/syscall_handler/getdents.rs deleted file mode 100644 index 45eec5320..000000000 --- a/crates/fspy/src/unix/syscall_handler/getdents.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::io; - -use fspy_seccomp_unotify::supervisor::handler::arg::{Caller, Fd}; - -use super::SyscallHandler; - -impl SyscallHandler { - #[cfg(target_arch = "x86_64")] - pub(super) fn getdents(&mut self, caller: Caller, (fd,): (Fd,)) -> io::Result<()> { - self.handle_open_dir(caller, fd) - } - - pub(super) fn getdents64(&mut self, caller: Caller, (fd,): (Fd,)) -> io::Result<()> { - self.handle_open_dir(caller, fd) - } -} diff --git a/crates/fspy/src/unix/syscall_handler/mod.rs b/crates/fspy/src/unix/syscall_handler/mod.rs deleted file mode 100644 index 4b6f7947e..000000000 --- a/crates/fspy/src/unix/syscall_handler/mod.rs +++ /dev/null @@ -1,103 +0,0 @@ -mod execve; -mod getdents; -mod open; -mod stat; - -use std::{ - borrow::Cow, - ffi::{OsStr, c_int}, - io, - os::unix::ffi::OsStrExt, - path::{Path, PathBuf}, -}; - -use fspy_seccomp_unotify::{ - impl_handler, - supervisor::handler::arg::{CStrPtr, Caller, Fd}, -}; -use fspy_shared::ipc::{AccessMode, PathAccess}; - -use crate::arena::PathAccessArena; - -const PATH_MAX: usize = libc::PATH_MAX as usize; - -#[derive(Debug)] -pub struct SyscallHandler { - arena: PathAccessArena, - path_read_buf: [u8; PATH_MAX], -} - -impl Default for SyscallHandler { - fn default() -> Self { - Self { arena: PathAccessArena::default(), path_read_buf: [0; PATH_MAX] } - } -} - -impl SyscallHandler { - pub fn into_arena(self) -> PathAccessArena { - self.arena - } - - fn handle_open( - &mut self, - caller: Caller, - dir_fd: Fd, - path_ptr: CStrPtr, - flags: c_int, - ) -> io::Result<()> { - let Some(path_len) = path_ptr.read(caller, &mut self.path_read_buf)? else { - // Ignore paths that are too long to fit in PATH_MAX - return Ok(()); - }; - let mut path = Cow::Borrowed(Path::new(OsStr::from_bytes(&self.path_read_buf[..path_len]))); - if !path.is_absolute() { - let mut resolved_path = PathBuf::from(dir_fd.get_path(caller)?); - if !nix::NixPath::is_empty(path.as_ref()) { - resolved_path.push(&path); - } - path = Cow::Owned(resolved_path); - } - self.arena.add(PathAccess { - mode: match flags & libc::O_ACCMODE { - libc::O_RDWR => AccessMode::READ | AccessMode::WRITE, - libc::O_WRONLY => AccessMode::WRITE, - _ => AccessMode::READ, - }, - path: path.as_os_str().into(), - }); - Ok(()) - } - - fn handle_open_dir(&mut self, caller: Caller, fd: Fd) -> io::Result<()> { - let path = fd.get_path(caller)?; - self.arena.add(PathAccess { - mode: AccessMode::READ_DIR, - path: OsStr::from_bytes(path.as_bytes()).into(), - }); - Ok(()) - } -} - -impl_handler!( - SyscallHandler: - - #[cfg(target_arch = "x86_64")] open, - openat, - openat2, - - #[cfg(target_arch = "x86_64")] getdents, - getdents64, - - #[cfg(target_arch = "x86_64")] stat, - #[cfg(target_arch = "x86_64")] lstat, - #[cfg(target_arch = "x86_64")] newfstatat, - #[cfg(target_arch = "aarch64")] fstatat, - statx, - - #[cfg(target_arch = "x86_64")] access, - faccessat, - faccessat2, - - execve, - execveat, -); diff --git a/crates/fspy/src/unix/syscall_handler/open.rs b/crates/fspy/src/unix/syscall_handler/open.rs deleted file mode 100644 index be7ae157e..000000000 --- a/crates/fspy/src/unix/syscall_handler/open.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::{ffi::c_int, io}; - -use fspy_seccomp_unotify::supervisor::handler::arg::{CStrPtr, Caller, Fd, Ptr}; - -use super::SyscallHandler; - -impl SyscallHandler { - #[cfg(target_arch = "x86_64")] - pub(super) fn open( - &mut self, - caller: Caller, - (path, flags): (CStrPtr, c_int), - ) -> io::Result<()> { - self.handle_open(caller, Fd::cwd(), path, flags) - } - - pub(super) fn openat( - &mut self, - caller: Caller, - (dir_fd, path, flags): (Fd, CStrPtr, c_int), - ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path, flags) - } - - pub(super) fn openat2( - &mut self, - caller: Caller, - // open_how is a pointer to struct `open_how`, but we only care about flags here, so use `Ptr` - (dir_fd, path, open_how): (Fd, CStrPtr, Ptr), - ) -> io::Result<()> { - // SAFETY: open_how is a valid pointer to struct `open_how` in the target process, which has `flags` as the first field of type `u64` - let flags = unsafe { open_how.read(caller) }?; - self.handle_open(caller, dir_fd, path, c_int::try_from(flags).unwrap_or(libc::O_RDWR)) - } -} diff --git a/crates/fspy/src/unix/syscall_handler/stat.rs b/crates/fspy/src/unix/syscall_handler/stat.rs deleted file mode 100644 index 40d9f76f1..000000000 --- a/crates/fspy/src/unix/syscall_handler/stat.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::io; - -use fspy_seccomp_unotify::supervisor::handler::arg::{CStrPtr, Caller, Fd}; - -use super::SyscallHandler; - -impl SyscallHandler { - #[cfg(target_arch = "x86_64")] - pub(super) fn stat(&mut self, caller: Caller, (path,): (CStrPtr,)) -> io::Result<()> { - self.handle_open(caller, Fd::cwd(), path, libc::O_RDONLY) - } - - #[cfg(target_arch = "x86_64")] - pub(super) fn lstat(&mut self, caller: Caller, (path,): (CStrPtr,)) -> io::Result<()> { - self.handle_open(caller, Fd::cwd(), path, libc::O_RDONLY) - } - - #[cfg(target_arch = "aarch64")] - pub(super) fn fstatat( - &mut self, - caller: Caller, - (dir_fd, path_ptr): (Fd, CStrPtr), - ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY) - } - - #[cfg(target_arch = "x86_64")] - pub(super) fn newfstatat( - &mut self, - caller: Caller, - (dir_fd, path_ptr): (Fd, CStrPtr), - ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY) - } - - /// statx(2) — modern replacement for stat/fstatat used by newer glibc. - pub(super) fn statx( - &mut self, - caller: Caller, - (dir_fd, path_ptr): (Fd, CStrPtr), - ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY) - } - - /// access(2) — check file accessibility (e.g. existsSync in Node.js). - #[cfg(target_arch = "x86_64")] - pub(super) fn access(&mut self, caller: Caller, (path,): (CStrPtr,)) -> io::Result<()> { - self.handle_open(caller, Fd::cwd(), path, libc::O_RDONLY) - } - - /// faccessat(2) — check file accessibility relative to directory fd. - pub(super) fn faccessat( - &mut self, - caller: Caller, - (dir_fd, path_ptr): (Fd, CStrPtr), - ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY) - } - - /// faccessat2(2) — extended faccessat with flags parameter. - pub(super) fn faccessat2( - &mut self, - caller: Caller, - (dir_fd, path_ptr): (Fd, CStrPtr), - ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY) - } -} diff --git a/crates/fspy/src/windows/mod.rs b/crates/fspy/src/windows/mod.rs deleted file mode 100644 index 8081e1298..000000000 --- a/crates/fspy/src/windows/mod.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::{ - ffi::{CStr, c_char}, - io, - os::windows::{ffi::OsStrExt, io::AsRawHandle, process::ChildExt as _}, - path::Path, - sync::Arc, -}; - -use fspy_detours_sys::{DetourCopyPayloadToProcess, DetourUpdateProcessWithDll}; -use fspy_shared::{ - ipc::{PathAccess, channel::channel}, - windows::{PAYLOAD_ID, Payload}, -}; -use futures_util::FutureExt; -use materialized_artifact::{Artifact, artifact}; -use tokio_util::sync::CancellationToken; -use winapi::{ - shared::minwindef::TRUE, - um::{processthreadsapi::ResumeThread, winbase::CREATE_SUSPENDED}, -}; -use winsafe::co::{CP, WC}; - -use crate::{ - ChildTermination, TrackedChild, - command::Command, - error::SpawnError, - ipc::{OwnedReceiverLockGuard, SHM_CAPACITY}, -}; - -const INTERPOSE_CDYLIB: Artifact = artifact!("fspy_preload"); - -pub struct PathAccessIterable { - ipc_receiver_lock_guard: OwnedReceiverLockGuard, -} - -impl PathAccessIterable { - pub fn iter(&self) -> impl Iterator> { - self.ipc_receiver_lock_guard.iter_path_accesses() - } -} - -// pub struct TracedProcess { -// pub child: Child, -// pub path_access_stream: PathAccessIter, -// } - -#[derive(Debug, Clone)] -pub struct SpyImpl { - ansi_dll_path_with_nul: Arc, -} - -impl SpyImpl { - pub fn init_in(path: &Path) -> io::Result { - let dll_path = INTERPOSE_CDYLIB.materialize().suffix(".dll").at(path)?; - - let wide_dll_path = dll_path.as_os_str().encode_wide().collect::>(); - let mut ansi_dll_path = - winsafe::WideCharToMultiByte(CP::ACP, WC::NoValue, &wide_dll_path, None, None) - .map_err(|err| io::Error::from_raw_os_error(err.raw().cast_signed()))?; - - ansi_dll_path.push(0); - - // SAFETY: we just pushed a NUL byte, so the slice is NUL-terminated - let ansi_dll_path_with_nul = - unsafe { CStr::from_bytes_with_nul_unchecked(ansi_dll_path.as_slice()) }; - Ok(Self { ansi_dll_path_with_nul: ansi_dll_path_with_nul.into() }) - } - - #[expect(clippy::unused_async, reason = "async signature required by SpyImpl trait")] - pub(crate) async fn spawn( - &self, - mut command: Command, - cancellation_token: CancellationToken, - ) -> Result { - let ansi_dll_path_with_nul = Arc::clone(&self.ansi_dll_path_with_nul); - command.env("FSPY", "1"); - let mut command = command.into_tokio_command(); - - command.creation_flags(CREATE_SUSPENDED); - - let (channel_conf, receiver) = - channel(SHM_CAPACITY).map_err(SpawnError::ChannelCreation)?; - - let mut spawn_success = false; - let spawn_success = &mut spawn_success; - let mut child = command - .spawn_with(|std_command| { - let std_child = std_command.spawn()?; - *spawn_success = true; - - let mut dll_paths = ansi_dll_path_with_nul.as_ptr().cast::(); - let process_handle = std_child.as_raw_handle().cast::(); - // SAFETY: process_handle is a valid handle to the just-spawned child process, - // dll_paths points to a valid null-terminated ANSI string - let success = - unsafe { DetourUpdateProcessWithDll(process_handle, &raw mut dll_paths, 1) }; - if success != TRUE { - return Err(io::Error::last_os_error()); - } - - let payload = Payload { - channel_conf: channel_conf.clone(), - ansi_dll_path_with_nul: ansi_dll_path_with_nul.to_bytes(), - }; - let payload_bytes = wincode::serialize(&payload).unwrap(); - // SAFETY: process_handle is valid, PAYLOAD_ID is a static GUID, - // payload_bytes is a valid buffer with correct length - let success = unsafe { - DetourCopyPayloadToProcess( - process_handle, - &PAYLOAD_ID, - payload_bytes.as_ptr().cast(), - payload_bytes.len().try_into().unwrap(), - ) - }; - if success != TRUE { - return Err(io::Error::last_os_error()); - } - - let main_thread_handle = std_child.main_thread_handle(); - // SAFETY: main_thread_handle is a valid thread handle from the spawned child - let resume_thread_ret = - unsafe { ResumeThread(main_thread_handle.as_raw_handle().cast()) } - .cast_signed(); - - if resume_thread_ret == -1 { - return Err(io::Error::last_os_error()); - } - - Ok(std_child) - }) - .map_err(|err| { - if *spawn_success { SpawnError::OsSpawn(err) } else { SpawnError::Injection(err) } - })?; - - // Duplicate the process handle before the child is moved into the background - // task. The duplicate is independently owned (its own ref count), so it stays - // valid even after tokio closes its copy when the process exits. - let process_handle = { - use std::os::windows::io::BorrowedHandle; - // SAFETY: The child was just spawned and hasn't been moved yet, so its - // raw handle is valid. `borrow_raw` creates a temporary borrow. - let borrowed = unsafe { BorrowedHandle::borrow_raw(child.raw_handle().unwrap()) }; - borrowed.try_clone_to_owned().map_err(SpawnError::OsSpawn)? - }; - - Ok(TrackedChild { - stdin: child.stdin.take(), - stdout: child.stdout.take(), - stderr: child.stderr.take(), - process_handle, - // Keep polling for the child to exit in the background even if `wait_handle` is not awaited, - // because we need to stop the supervisor and lock the channel as soon as the child exits. - wait_handle: tokio::spawn(async move { - let status = tokio::select! { - status = child.wait() => status?, - () = cancellation_token.cancelled() => { - child.start_kill()?; - child.wait().await? - } - }; - // Lock the ipc channel after the child has exited. - // We are not interested in path accesses from descendants after the main child has exited. - let ipc_receiver_lock_guard = OwnedReceiverLockGuard::lock_async(receiver).await?; - let path_accesses = PathAccessIterable { ipc_receiver_lock_guard }; - - io::Result::Ok(ChildTermination { status, path_accesses }) - }) - .map(|f| f?) // flatten JoinError and io::Result - .boxed(), - }) - } -} diff --git a/crates/fspy/tests/cancellation.rs b/crates/fspy/tests/cancellation.rs deleted file mode 100644 index ff65cf2c0..000000000 --- a/crates/fspy/tests/cancellation.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::process::Stdio; - -use tokio::io::AsyncReadExt as _; -use tokio_util::sync::CancellationToken; - -#[test_log::test(tokio::test)] -async fn cancellation_kills_tracked_child() -> anyhow::Result<()> { - let cmd = subprocess_test::command_for_fn!((), |()| { - use std::io::Write as _; - // Signal readiness via stdout - std::io::stdout().write_all(b"ready\n").unwrap(); - std::io::stdout().flush().unwrap(); - // Block on stdin — will be killed by cancellation - let _ = std::io::stdin().read_line(&mut String::new()); - }); - let token = CancellationToken::new(); - let mut fspy_cmd = fspy::Command::from(cmd); - fspy_cmd.stdout(Stdio::piped()).stdin(Stdio::piped()); - let mut child = fspy_cmd.spawn(token.clone()).await?; - - // Wait for child to signal readiness - let mut stdout = child.stdout.take().unwrap(); - let mut buf = vec![0u8; 64]; - let n = stdout.read(&mut buf).await?; - assert!(std::str::from_utf8(&buf[..n])?.contains("ready")); - - // Cancel — fspy background task calls start_kill - token.cancel(); - let termination = child.wait_handle.await?; - assert!(!termination.status.success()); - Ok(()) -} diff --git a/crates/fspy/tests/node_fs.rs b/crates/fspy/tests/node_fs.rs deleted file mode 100644 index 96e951487..000000000 --- a/crates/fspy/tests/node_fs.rs +++ /dev/null @@ -1,178 +0,0 @@ -mod test_utils; - -use std::{ - env::{current_dir, join_paths, split_paths, var_os, vars_os}, - ffi::{OsStr, OsString}, - iter, - path::PathBuf, -}; - -use fspy::{AccessMode, PathAccessIterable}; -use ntest::test_case; -use test_utils::assert_contains; - -fn resolve_runtime(runtime: &str) -> anyhow::Result<(PathBuf, OsString)> { - let manifest_dir = PathBuf::from(var_os("CARGO_MANIFEST_DIR").unwrap()); - let tools_bin = - manifest_dir.parent().unwrap().parent().unwrap().join("packages/tools/node_modules/.bin"); - let path = - join_paths(iter::once(tools_bin).chain(var_os("PATH").iter().flat_map(split_paths)))?; - let program = which::which_in(runtime, Some(&path), current_dir()?)?; - Ok((program, path)) -} - -fn track_script( - runtime: &str, - script: &str, - args: &[&OsStr], -) -> anyhow::Result { - let (program, path) = resolve_runtime(runtime)?; - - let mut command = fspy::Command::new(program); - command - .envs(vars_os().filter(|(name, _)| !name.eq_ignore_ascii_case("PATH"))) - .env("PATH", path); // https://github.com/jdx/mise/discussions/5968 - let script = format!( - "const fs = require('node:fs'); \ - const child_process = require('node:child_process'); \ - {script}" - ); - if runtime == "deno" { - command.args(["eval", "--ext=cjs"]).arg(script).args(args); - } else { - command.arg("-e").arg(script).args(args); - } - - // `ntest::test_case` generates synchronous `#[test]` functions, so drive - // fspy's asynchronous process tracking from this shared helper. - tokio::runtime::Runtime::new()?.block_on(async { - let child = command.spawn(tokio_util::sync::CancellationToken::new()).await?; - let termination = child.wait_handle.await?; - assert!(termination.status.success()); - Ok(termination.path_accesses) - }) -} - -// Bun's Linux runtime uses direct syscalls, which require universal seccomp -// tracing instead of preload interception. Deno does not distribute a musl -// runtime. Node remains covered on every target. -#[test_case("node")] -#[ignore = "requires node"] -#[cfg_attr(not(target_os = "linux"), test_case("bun"))] -#[cfg_attr(not(target_os = "linux"), ignore = "requires node")] -#[cfg_attr(not(target_env = "musl"), test_case("deno"))] -#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] -fn read_sync(runtime: &str) -> anyhow::Result<()> { - let accesses = track_script(runtime, "try { fs.readFileSync('hello') } catch {}", &[])?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); - Ok(()) -} - -#[test_case("node")] -#[ignore = "requires node"] -#[cfg_attr(not(target_os = "linux"), test_case("bun"))] -#[cfg_attr(not(target_os = "linux"), ignore = "requires node")] -#[cfg_attr(not(target_env = "musl"), test_case("deno"))] -#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] -fn exist_sync(runtime: &str) -> anyhow::Result<()> { - let accesses = track_script(runtime, "try { fs.existsSync('hello') } catch {}", &[])?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); - Ok(()) -} - -#[test_case("node")] -#[ignore = "requires node"] -#[cfg_attr(not(target_os = "linux"), test_case("bun"))] -#[cfg_attr(not(target_os = "linux"), ignore = "requires node")] -#[cfg_attr(not(target_env = "musl"), test_case("deno"))] -#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] -fn stat_sync(runtime: &str) -> anyhow::Result<()> { - let accesses = track_script(runtime, "try { fs.statSync('hello') } catch {}", &[])?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); - Ok(()) -} - -#[test_case("node")] -#[ignore = "requires node"] -#[cfg_attr(not(target_os = "linux"), test_case("bun"))] -#[cfg_attr(not(target_os = "linux"), ignore = "requires node")] -#[cfg_attr(not(target_env = "musl"), test_case("deno"))] -#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] -fn create_read_stream(runtime: &str) -> anyhow::Result<()> { - let accesses = track_script( - runtime, - "try { fs.createReadStream('hello').on('error', () => {}) } catch {}", - &[], - )?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); - Ok(()) -} - -#[test_case("node")] -#[ignore = "requires node"] -#[cfg_attr(not(target_os = "linux"), test_case("bun"))] -#[cfg_attr(not(target_os = "linux"), ignore = "requires node")] -#[cfg_attr(not(target_env = "musl"), test_case("deno"))] -#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] -fn create_write_stream(runtime: &str) -> anyhow::Result<()> { - let tmpdir = tempfile::tempdir()?; - let file_path = tmpdir.path().join("hello"); - let accesses = track_script( - runtime, - "try { fs.createWriteStream(process.argv.at(-1)).on('error', () => {}) } catch {}", - &[file_path.as_os_str()], - )?; - assert_contains(&accesses, file_path.as_path(), AccessMode::WRITE); - Ok(()) -} - -#[test_case("node")] -#[ignore = "requires node"] -#[cfg_attr(not(target_os = "linux"), test_case("bun"))] -#[cfg_attr(not(target_os = "linux"), ignore = "requires node")] -#[cfg_attr(not(target_env = "musl"), test_case("deno"))] -#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] -fn write_sync(runtime: &str) -> anyhow::Result<()> { - let tmpdir = tempfile::tempdir()?; - let file_path = tmpdir.path().join("hello"); - let accesses = track_script( - runtime, - "try { fs.writeFileSync(process.argv.at(-1), '') } catch {}", - &[file_path.as_os_str()], - )?; - assert_contains(&accesses, &file_path, AccessMode::WRITE); - Ok(()) -} - -#[test_case("node")] -#[ignore = "requires node"] -#[cfg_attr(not(target_os = "linux"), test_case("bun"))] -#[cfg_attr(not(target_os = "linux"), ignore = "requires node")] -#[cfg_attr(not(target_env = "musl"), test_case("deno"))] -#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] -fn read_dir_sync(runtime: &str) -> anyhow::Result<()> { - let accesses = track_script(runtime, "try { fs.readdirSync('.') } catch {}", &[])?; - assert_contains(&accesses, ¤t_dir().unwrap(), AccessMode::READ_DIR); - Ok(()) -} - -#[test_case("node")] -#[ignore = "requires node"] -#[cfg_attr(not(target_os = "linux"), test_case("bun"))] -#[cfg_attr(not(target_os = "linux"), ignore = "requires node")] -#[cfg_attr(not(target_env = "musl"), test_case("deno"))] -#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] -fn subprocess(runtime: &str) -> anyhow::Result<()> { - let cmd = if cfg!(windows) { - r"'cmd', ['/c', 'type hello']" - } else { - r"'/bin/sh', ['-c', 'cat hello']" - }; - let accesses = track_script( - runtime, - &format!("try {{ child_process.spawnSync({cmd}, {{ stdio: 'ignore' }}) }} catch {{}}"), - &[], - )?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); - Ok(()) -} diff --git a/crates/fspy/tests/oxlint.rs b/crates/fspy/tests/oxlint.rs deleted file mode 100644 index fe4a96291..000000000 --- a/crates/fspy/tests/oxlint.rs +++ /dev/null @@ -1,124 +0,0 @@ -mod test_utils; - -use std::{env::vars_os, ffi::OsString}; - -use fspy::{AccessMode, PathAccessIterable}; -use test_log::test; - -/// Get the packages/tools/.bin directory path -fn tools_bin_dir() -> std::path::PathBuf { - // Resolve CARGO_MANIFEST_DIR at run time, not via `env!`: a compile-time - // path can point at a different checkout than the one executing the test. - let manifest_dir = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()); - manifest_dir - .parent() - .unwrap() - .parent() - .unwrap() - .join("packages") - .join("tools") - .join("node_modules") - .join(".bin") -} - -/// Find the oxlint executable in packages/tools -fn find_oxlint() -> std::path::PathBuf { - let tools_dir = tools_bin_dir(); - which::which_in("oxlint", Some(&tools_dir), std::env::current_dir().unwrap()) - .expect("oxlint not found in packages/tools/node_modules/.bin") -} - -async fn track_oxlint(dir: &std::path::Path, args: &[&str]) -> anyhow::Result { - let oxlint_path = find_oxlint(); - let mut command = fspy::Command::new(&oxlint_path); - - // Build PATH with packages/tools/.bin prepended so oxlint can find tsgolint - let tools_dir = tools_bin_dir(); - let new_path = if let Some(existing_path) = std::env::var_os("PATH") { - let mut paths = vec![tools_dir.as_os_str().to_owned()]; - paths.extend(std::env::split_paths(&existing_path).map(std::path::PathBuf::into_os_string)); - std::env::join_paths(paths)? - } else { - OsString::from(&tools_dir) - }; - - command - .args(args) - .envs(vars_os().filter(|(k, _)| !k.eq_ignore_ascii_case("PATH"))) - .env("PATH", new_path) - .current_dir(dir); - - let child = command.spawn(tokio_util::sync::CancellationToken::new()).await?; - let termination = child.wait_handle.await?; - // oxlint may return non-zero if it finds lint errors, that's OK - Ok(termination.path_accesses) -} - -#[test(tokio::test)] -#[ignore = "requires `pnpm install` (workspace root)"] -async fn oxlint_reads_js_file() -> anyhow::Result<()> { - let tmpdir = tempfile::tempdir()?; - // on macOS, tmpdir.path() may be a symlink, so we need to canonicalize it - let tmpdir_path = std::fs::canonicalize(tmpdir.path())?; - - let js_file = tmpdir_path.join("test.js"); - std::fs::write(&js_file, "console.log('hello');")?; - - let accesses = track_oxlint(&tmpdir_path, &[]).await?; - - // Check that oxlint read the JS file - test_utils::assert_contains(&accesses, &js_file, AccessMode::READ); - - Ok(()) -} - -#[test(tokio::test)] -#[ignore = "requires `pnpm install` (workspace root)"] -async fn oxlint_reads_directory() -> anyhow::Result<()> { - let tmpdir = tempfile::tempdir()?; - - // on macOS, tmpdir.path() may be a symlink, so we need to canonicalize it - let tmpdir_path = std::fs::canonicalize(tmpdir.path())?; - - let accesses = track_oxlint(&tmpdir_path, &[]).await?; - - // Check that oxlint read the directory to find JS files - // This is the key check - if READ_DIR is not tracked, cache won't detect new files - test_utils::assert_contains(&accesses, &tmpdir_path, AccessMode::READ_DIR); - Ok(()) -} - -#[test(tokio::test)] -#[ignore = "requires `pnpm install` (workspace root)"] -async fn oxlint_type_aware() -> anyhow::Result<()> { - let tmpdir = tempfile::tempdir()?; - // on macOS, tmpdir.path() may be a symlink, so we need to canonicalize it - let tmpdir_path = std::fs::canonicalize(tmpdir.path())?; - - // Create a simple TypeScript file - let ts_file = tmpdir_path.join("index.ts"); - std::fs::write( - &ts_file, - r" -import type { Foo } from './types'; -declare const _foo: Foo; -", - )?; - - // Run oxlint without --type-aware first - let accesses = track_oxlint(&tmpdir_path, &[""]).await?; - let access_to_types_ts = accesses.iter().find(|access| { - access - .path - .strip_path_prefix(&tmpdir_path, |result| result.is_ok_and(|p| p.ends_with("types.ts"))) - }); - assert_eq!(access_to_types_ts, None, "oxlint should not read types.ts without --type-aware"); - - // Run oxlint with --type-aware to enable type-aware linting - let accesses = track_oxlint(&tmpdir_path, &["--type-aware"]).await?; - - // Check that oxlint read types.ts - test_utils::assert_contains(&accesses, &tmpdir_path.join("types.ts"), AccessMode::READ); - - Ok(()) -} diff --git a/crates/fspy/tests/rust_std.rs b/crates/fspy/tests/rust_std.rs deleted file mode 100644 index 5bdff9df0..000000000 --- a/crates/fspy/tests/rust_std.rs +++ /dev/null @@ -1,120 +0,0 @@ -mod test_utils; - -use std::{ - env::current_dir, - fs::{self, File, OpenOptions}, - process::Stdio, -}; - -use fspy::AccessMode; -use test_log::test; -use test_utils::assert_contains; - -#[test(tokio::test)] -async fn open_read() -> anyhow::Result<()> { - let accesses = track_fn!((), |(): ()| { - let _ = File::open("hello"); - }) - .await?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); - - Ok(()) -} - -#[test(tokio::test)] -async fn open_write() -> anyhow::Result<()> { - let tmp_dir = tempfile::tempdir()?; - let tmp_path = tmp_dir.path().join("hello"); - let tmp_path_str = tmp_path.to_str().unwrap().to_owned(); - let accesses = track_fn!(tmp_path_str, |tmp_path_str: String| { - let _ = OpenOptions::new().write(true).open(tmp_path_str); - }) - .await?; - assert_contains(&accesses, tmp_path.as_path(), AccessMode::WRITE); - - Ok(()) -} - -#[test(tokio::test)] -async fn metadata() -> anyhow::Result<()> { - let tmp_dir = tempfile::tempdir()?; - let tmp_path = tmp_dir.path().join("hello"); - File::create(&tmp_path)?; - let tmp_path_str = tmp_path.to_str().unwrap().to_owned(); - - let accesses = track_fn!(tmp_path_str, |tmp_path_str: String| { - let _ = fs::metadata(tmp_path_str); - }) - .await?; - assert_contains(&accesses, tmp_path.as_path(), AccessMode::READ); - - Ok(()) -} - -#[test(tokio::test)] -async fn readdir() -> anyhow::Result<()> { - let tmpdir = tempfile::tempdir()?; - let tmpdir_path = std::fs::canonicalize(tmpdir.path())?; - // Reading a non-existent directory results in different tracked accesses on different platforms: - // - Windows: READ, because the NT APIs open the directory as handle just like files (NtCreateFile/NtOpenFile), - // and if that fails, not read dir call (NtQueryDirectoryFile/NtQueryDirectoryFileEx) is made. - // - macOS/Linux: - // - opendir results in a read_dir access. This call is directly made without trying to open the directory as a fd first. - // - open + fopendir results in READ access, because open would fail with ENOENT, and fopendir is not called. - // - // This difference is acceptable because both will result in a "not found" fingerprint in vite-task. - // To keep the test consistent across platforms, we create the directory first. - std::fs::create_dir(tmpdir.path().join("hello_dir"))?; - - let accesses = track_fn!(tmpdir_path.to_str().unwrap().to_owned(), |tmpdir_path: String| { - std::env::set_current_dir(tmpdir_path).unwrap(); - // Consume at least one entry so that getdents64 fires on seccomp targets. - let _ = std::fs::read_dir("hello_dir").unwrap().next(); - }) - .await?; - assert_contains(&accesses, tmpdir_path.join("hello_dir").as_path(), AccessMode::READ_DIR); - - Ok(()) -} - -#[test(tokio::test)] -async fn read_in_subprocess() -> anyhow::Result<()> { - let accesses = track_fn!((), |(): ()| { - let mut command = if cfg!(windows) { - let mut command = std::process::Command::new("cmd"); - command.arg("/c").arg("type hello"); - command - } else { - let mut command = std::process::Command::new("/bin/sh"); - command.arg("-c").arg("cat hello"); - command - }; - command - .stdout(Stdio::null()) - .stdin(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .unwrap() - .wait() - .unwrap(); - }) - .await?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); - - Ok(()) -} - -#[test(tokio::test)] -async fn read_program() -> anyhow::Result<()> { - let accesses = track_fn!((), |(): ()| { - let _ = std::process::Command::new("./not_exist.exe").spawn(); - }) - .await?; - assert_contains( - &accesses, - current_dir().unwrap().join("not_exist.exe").as_path(), - AccessMode::READ, - ); - - Ok(()) -} diff --git a/crates/fspy/tests/rust_tokio.rs b/crates/fspy/tests/rust_tokio.rs deleted file mode 100644 index 4c68526f9..000000000 --- a/crates/fspy/tests/rust_tokio.rs +++ /dev/null @@ -1,94 +0,0 @@ -mod test_utils; - -use std::{env::current_dir, process::Stdio}; - -use fspy::AccessMode; -use test_log::test; -use test_utils::assert_contains; -use tokio::fs::OpenOptions; - -#[test(tokio::test)] -async fn open_read() -> anyhow::Result<()> { - let accesses = track_fn!((), |(): ()| { - tokio::runtime::Builder::new_current_thread().enable_io().build().unwrap().block_on( - async { - let _ = tokio::fs::File::open("hello").await; - }, - ); - }) - .await?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); - - Ok(()) -} - -#[test(tokio::test)] -async fn open_write() -> anyhow::Result<()> { - let tmp_dir = tempfile::tempdir()?; - let tmp_path = tmp_dir.path().join("hello"); - let tmp_path_str = tmp_path.to_str().unwrap().to_owned(); - let accesses = track_fn!(tmp_path_str, |tmp_path_str: String| { - tokio::runtime::Builder::new_current_thread().enable_io().build().unwrap().block_on( - async { - let _ = OpenOptions::new().write(true).open(tmp_path_str).await; - }, - ); - }) - .await?; - assert_contains(&accesses, tmp_path.as_path(), AccessMode::WRITE); - - Ok(()) -} - -#[test(tokio::test)] -async fn readdir() -> anyhow::Result<()> { - let tmpdir = tempfile::tempdir()?; - let tmpdir_path = std::fs::canonicalize(tmpdir.path())?; - - std::fs::create_dir(tmpdir.path().join("hello_dir"))?; - - let accesses = track_fn!(tmpdir_path.to_str().unwrap().to_owned(), |tmpdir_path: String| { - std::env::set_current_dir(tmpdir_path).unwrap(); - tokio::runtime::Builder::new_current_thread().enable_io().build().unwrap().block_on( - async { - let _ = tokio::fs::read_dir("hello_dir").await; - }, - ); - }) - .await?; - assert_contains(&accesses, tmpdir_path.join("hello_dir").as_path(), AccessMode::READ_DIR); - - Ok(()) -} - -#[test(tokio::test)] -async fn subprocess() -> anyhow::Result<()> { - let accesses = track_fn!((), |(): ()| { - tokio::runtime::Builder::new_current_thread().enable_io().build().unwrap().block_on( - async { - let mut command = if cfg!(windows) { - let mut command = tokio::process::Command::new("cmd"); - command.arg("/c").arg("type hello"); - command - } else { - let mut command = tokio::process::Command::new("/bin/sh"); - command.arg("-c").arg("cat hello"); - command - }; - command - .stdout(Stdio::null()) - .stdin(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .unwrap() - .wait() - .await - .unwrap(); - }, - ); - }) - .await?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); - - Ok(()) -} diff --git a/crates/fspy/tests/shebang.rs b/crates/fspy/tests/shebang.rs deleted file mode 100644 index 9195b5a84..000000000 --- a/crates/fspy/tests/shebang.rs +++ /dev/null @@ -1,43 +0,0 @@ -#![cfg(unix)] -mod test_utils; - -use std::{ - os::unix::fs::PermissionsExt, - path::Path, - process::{Command, Stdio}, -}; - -use fspy::AccessMode; -use test_log::test; -use test_utils::assert_contains; -use tokio::fs; - -#[test(tokio::test)] -async fn spawn_sh_shebang() -> anyhow::Result<()> { - let tmp_dir = tempfile::TempDir::new()?; - - let shebang_script_path = tmp_dir.path().join("fspy_test_shebang_script.sh"); - let shebang_script_path = shebang_script_path.into_os_string().into_string().unwrap(); - - fs::write(&shebang_script_path, "#!/bin/sh\ncat hello\n").await?; - - let mut perms = fs::metadata(&shebang_script_path).await?.permissions(); - perms.set_mode(0o755); - fs::set_permissions(&shebang_script_path, perms).await?; - - let accesses = track_fn!(shebang_script_path.clone(), |shebang_script_path: String| { - let _ignored = Command::new(&shebang_script_path) - .current_dir("/") - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .status() - .expect("Failed to execute shebang script"); - }) - .await?; - - assert_contains(&accesses, Path::new(&shebang_script_path), AccessMode::READ); - assert_contains(&accesses, Path::new("/hello"), AccessMode::READ); - - Ok(()) -} diff --git a/crates/fspy/tests/static_executable.rs b/crates/fspy/tests/static_executable.rs deleted file mode 100644 index ae3c27169..000000000 --- a/crates/fspy/tests/static_executable.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! Tests for fspy tracing of statically-linked executables (seccomp path). -//! Skipped on musl: the test binary is an artifact dep targeting musl, and when -//! the CI builds with `-crt-static` the binary becomes dynamically linked, -//! defeating the purpose of these tests. -#![cfg(all(target_os = "linux", not(target_env = "musl")))] -use std::{ - fs::{self, Permissions}, - os::unix::fs::PermissionsExt as _, - path::{Path, PathBuf}, - sync::LazyLock, -}; - -use fspy::PathAccessIterable; -use fspy_shared_unix::is_dynamically_linked_to_libc; -use test_log::test; - -use crate::test_utils::assert_contains; - -mod test_utils; - -const TEST_BIN_CONTENT: &[u8] = include_bytes!(env!("CARGO_BIN_FILE_FSPY_TEST_BIN")); - -fn test_bin_path() -> &'static Path { - static TEST_BIN_PATH: LazyLock = LazyLock::new(|| { - assert_eq!( - is_dynamically_linked_to_libc(TEST_BIN_CONTENT), - Ok(false), - "Test binary is not a static executable" - ); - - let tmp_dir = env!("CARGO_TARGET_TMPDIR"); - let test_bin_path = PathBuf::from(tmp_dir).join("fspy-test-bin"); - fs::write(&test_bin_path, TEST_BIN_CONTENT).expect("failed to write test binary"); - fs::set_permissions(&test_bin_path, Permissions::from_mode(0o755)) - .expect("failed to set permissions on test binary"); - - test_bin_path - }); - TEST_BIN_PATH.as_path() -} - -async fn track_test_bin(args: &[&str], cwd: Option<&str>) -> PathAccessIterable { - let mut cmd = fspy::Command::new(test_bin_path()); - if let Some(cwd) = cwd { - cmd.current_dir(cwd); - } - cmd.args(args); - let tracked_child = cmd.spawn(tokio_util::sync::CancellationToken::new()).await.unwrap(); - - let termination = tracked_child.wait_handle.await.unwrap(); - assert!(termination.status.success()); - - termination.path_accesses -} - -#[test(tokio::test)] -async fn open_read() { - let accesses = track_test_bin(&["open_read", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::READ); -} - -#[test(tokio::test)] -async fn open_write() { - let accesses = track_test_bin(&["open_write", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::WRITE); -} - -#[test(tokio::test)] -async fn open_readwrite() { - let accesses = track_test_bin(&["open_readwrite", "/hello"], None).await; - assert_contains( - &accesses, - Path::new("/hello"), - fspy::AccessMode::READ | fspy::AccessMode::WRITE, - ); -} - -#[test(tokio::test)] -async fn openat2_read() { - let accesses = track_test_bin(&["openat2_read", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::READ); -} - -#[test(tokio::test)] -async fn openat2_write() { - let accesses = track_test_bin(&["openat2_write", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::WRITE); -} - -#[test(tokio::test)] -async fn openat2_readwrite() { - let accesses = track_test_bin(&["openat2_readwrite", "/hello"], None).await; - assert_contains( - &accesses, - Path::new("/hello"), - fspy::AccessMode::READ | fspy::AccessMode::WRITE, - ); -} - -#[test(tokio::test)] -async fn open_relative() { - let accesses = track_test_bin(&["open_read", "hello"], Some("/home")).await; - assert_contains(&accesses, Path::new("/home/hello"), fspy::AccessMode::READ); -} - -#[test(tokio::test)] -async fn readdir() { - let accesses = track_test_bin(&["readdir", "/home"], None).await; - assert_contains(&accesses, Path::new("/home"), fspy::AccessMode::READ_DIR); -} - -#[test(tokio::test)] -async fn stat() { - let accesses = track_test_bin(&["stat", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::READ); -} - -#[test(tokio::test)] -async fn execve() { - let accesses = track_test_bin(&["execve", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::READ); -} diff --git a/crates/fspy/tests/test_utils/mod.rs b/crates/fspy/tests/test_utils/mod.rs deleted file mode 100644 index cfa46c4a9..000000000 --- a/crates/fspy/tests/test_utils/mod.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::path::{Path, PathBuf, StripPrefixError}; - -use fspy::{AccessMode, PathAccessIterable}; -// Used by the track_child! macro; not all test files use this macro -#[doc(hidden)] -#[expect( - clippy::useless_attribute, - reason = "allow attribute on re-export required for macro usage" -)] -#[expect( - clippy::allow_attributes, - reason = "allow attribute on re-export required for macro usage" -)] -#[allow( - unused_imports, - reason = "used by track_child! macro; not all test files use this macro" -)] -pub use subprocess_test::command_for_fn; - -/// # Panics -/// -/// Panics if the expected path access is not found or has the wrong mode. -#[track_caller] -pub fn assert_contains( - accesses: &PathAccessIterable, - expected_path: &Path, - expected_mode: AccessMode, -) { - let mut actual_mode: AccessMode = AccessMode::empty(); - for access in accesses.iter() { - let Ok(stripped) = - access.path.strip_path_prefix::<_, Result, _>( - expected_path, - |strip_result| strip_result.map(Path::to_path_buf), - ) - else { - continue; - }; - if stripped.as_os_str().is_empty() { - actual_mode.insert(access.mode); - } - } - - if actual_mode.contains(AccessMode::READ_DIR) { - // READ_DIR already implies READ. - actual_mode.remove(AccessMode::READ); - } - - assert_eq!( - expected_mode, - actual_mode, - "Expected to find access to path {} with mode {:?}, but it was not found in: {:?}", - expected_path.display(), - expected_mode, - accesses.iter().collect::>() - ); -} - -/// Spawns a subprocess that executes the given function with file access tracking. -/// -/// - $arg: The argument to pass to the function -/// - $body: The function to run in the subprocess -/// -/// Returns the tracked file accesses from the subprocess. -#[macro_export] -macro_rules! track_fn { - ($arg: expr, $body: expr) => {{ - let cmd = $crate::test_utils::command_for_fn!($arg, $body); - $crate::test_utils::spawn_command(cmd) - }}; -} - -// Used by the track_child! macro; not all test files use this macro -#[doc(hidden)] -#[expect( - clippy::allow_attributes, - reason = "allow attribute required for conditionally-used helper" -)] -#[allow(dead_code, reason = "used by track_fn! macro; not all test files use this macro")] -pub async fn spawn_command(cmd: subprocess_test::Command) -> anyhow::Result { - let termination = fspy::Command::from(cmd) - .spawn(tokio_util::sync::CancellationToken::new()) - .await? - .wait_handle - .await?; - assert!(termination.status.success()); - Ok(termination.path_accesses) -} diff --git a/crates/fspy/tests/winapi.rs b/crates/fspy/tests/winapi.rs deleted file mode 100644 index 1bf9b4d9e..000000000 --- a/crates/fspy/tests/winapi.rs +++ /dev/null @@ -1,75 +0,0 @@ -#![cfg(windows)] -mod test_utils; - -use std::{ffi::OsStr, os::windows::ffi::OsStrExt, path::Path, ptr::null_mut}; - -use fspy::AccessMode; -use test_log::test; -use test_utils::assert_contains; -use winapi::um::processthreadsapi::{ - CreateProcessA, CreateProcessW, PROCESS_INFORMATION, STARTUPINFOA, STARTUPINFOW, -}; - -#[test(tokio::test)] -async fn create_process_a() -> anyhow::Result<()> { - let accesses = track_fn!((), |(): ()| { - // SAFETY: zeroing STARTUPINFOA is valid for the Windows API - let mut si: STARTUPINFOA = unsafe { std::mem::zeroed() }; - // CreateProcess requires cb to identify the STARTUPINFO layout. - si.cb = std::mem::size_of::().try_into().unwrap(); - // SAFETY: zeroing PROCESS_INFORMATION is valid for the Windows API - let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() }; - // SAFETY: all pointers are valid or null_mut as required by CreateProcessA - unsafe { - CreateProcessA( - c"C:\\fspy_test_not_exist_program.exe".as_ptr().cast(), - null_mut(), - null_mut(), - null_mut(), - 0, - 0, - null_mut(), - null_mut(), - &raw mut si, - &raw mut pi, - ) - }; - }) - .await?; - assert_contains(&accesses, Path::new("C:\\fspy_test_not_exist_program.exe"), AccessMode::READ); - - Ok(()) -} - -#[test(tokio::test)] -async fn create_process_w() -> anyhow::Result<()> { - let accesses = track_fn!((), |(): ()| { - // SAFETY: zeroing STARTUPINFOW is valid for the Windows API - let mut si: STARTUPINFOW = unsafe { std::mem::zeroed() }; - // CreateProcess requires cb to identify the STARTUPINFO layout. - si.cb = std::mem::size_of::().try_into().unwrap(); - // SAFETY: zeroing PROCESS_INFORMATION is valid for the Windows API - let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() }; - let program = - OsStr::new("C:\\fspy_test_not_exist_program.exe\0").encode_wide().collect::>(); - // SAFETY: all pointers are valid or null_mut as required by CreateProcessW - unsafe { - CreateProcessW( - program.as_ptr(), - null_mut(), - null_mut(), - null_mut(), - 0, - 0, - null_mut(), - null_mut(), - &raw mut si, - &raw mut pi, - ) - }; - }) - .await?; - assert_contains(&accesses, Path::new("C:\\fspy_test_not_exist_program.exe"), AccessMode::READ); - - Ok(()) -} diff --git a/crates/fspy_detours_sys/.clippy.toml b/crates/fspy_detours_sys/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/fspy_detours_sys/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/fspy_detours_sys/Cargo.toml b/crates/fspy_detours_sys/Cargo.toml deleted file mode 100644 index dc2a35dc4..000000000 --- a/crates/fspy_detours_sys/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "fspy_detours_sys" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[build-dependencies] -cc = { workspace = true } - -[dependencies] -winapi = { workspace = true, features = [ - "minwindef", - "libloaderapi", - "processthreadsapi", - "windef", -] } - -[lints] -workspace = true - -[dev-dependencies] -bindgen = { workspace = true } -cow-utils = { workspace = true } - -[lib] -test = false -doctest = false diff --git a/crates/fspy_detours_sys/README.md b/crates/fspy_detours_sys/README.md deleted file mode 100644 index 2b7d1dc76..000000000 --- a/crates/fspy_detours_sys/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# fspy_detours_sys - -Raw FFI bindings to [Detours](https://github.com/Microsoft/Detours) diff --git a/crates/fspy_detours_sys/build.rs b/crates/fspy_detours_sys/build.rs deleted file mode 100644 index 121aac552..000000000 --- a/crates/fspy_detours_sys/build.rs +++ /dev/null @@ -1,18 +0,0 @@ -fn main() { - if std::env::var_os("CARGO_CFG_TARGET_OS").unwrap() != "windows" { - return; - } - println!("cargo:rerun-if-changed=detours/src"); - // https://github.com/Berrysoft/detours/blob/c9bc2ad6e9cd8f5f7b74cfa65365d61ecc45203f/detours-sys/build.rs - cc::Build::new() - .include("detours/src") - .define("WIN32_LEAN_AND_MEAN", "1") - .define("_WIN32_WINNT", "0x501") - .file("detours/src/detours.cpp") - .file("detours/src/modules.cpp") - .file("detours/src/disasm.cpp") - .file("detours/src/image.cpp") - .file("detours/src/creatwth.cpp") - .cpp(true) - .compile("detours"); -} diff --git a/crates/fspy_detours_sys/detours b/crates/fspy_detours_sys/detours deleted file mode 160000 index 9764cebcb..000000000 --- a/crates/fspy_detours_sys/detours +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9764cebcb1a75940e68fa83d6730ffaf0f669401 diff --git a/crates/fspy_detours_sys/src/generated_bindings.rs b/crates/fspy_detours_sys/src/generated_bindings.rs deleted file mode 100644 index daab4d09c..000000000 --- a/crates/fspy_detours_sys/src/generated_bindings.rs +++ /dev/null @@ -1,463 +0,0 @@ -use winapi::shared::minwindef::*; -use winapi::um::winnt::*; -use winapi::um::winnt::INT; -use winapi::um::minwinbase::*; -use winapi::um::processthreadsapi::*; -use winapi::shared::guiddef::*; -use winapi::shared::windef::*; - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct _DETOUR_TRAMPOLINE { - _unused: [u8; 0], -} -pub type PDETOUR_TRAMPOLINE = *mut _DETOUR_TRAMPOLINE; -/// Binary Typedefs. -pub type PF_DETOUR_BINARY_BYWAY_CALLBACK = ::std::option::Option< - unsafe extern "system" fn( - pContext: PVOID, - pszFile: LPCSTR, - ppszOutFile: *mut LPCSTR, - ) -> BOOL, ->; -pub type PF_DETOUR_BINARY_FILE_CALLBACK = ::std::option::Option< - unsafe extern "system" fn( - pContext: PVOID, - pszOrigFile: LPCSTR, - pszFile: LPCSTR, - ppszOutFile: *mut LPCSTR, - ) -> BOOL, ->; -pub type PF_DETOUR_BINARY_SYMBOL_CALLBACK = ::std::option::Option< - unsafe extern "system" fn( - pContext: PVOID, - nOrigOrdinal: ULONG, - nOrdinal: ULONG, - pnOutOrdinal: *mut ULONG, - pszOrigSymbol: LPCSTR, - pszSymbol: LPCSTR, - ppszOutSymbol: *mut LPCSTR, - ) -> BOOL, ->; -pub type PF_DETOUR_BINARY_COMMIT_CALLBACK = ::std::option::Option< - unsafe extern "system" fn(pContext: PVOID) -> BOOL, ->; -pub type PF_DETOUR_ENUMERATE_EXPORT_CALLBACK = ::std::option::Option< - unsafe extern "system" fn( - pContext: PVOID, - nOrdinal: ULONG, - pszName: LPCSTR, - pCode: PVOID, - ) -> BOOL, ->; -pub type PF_DETOUR_IMPORT_FILE_CALLBACK = ::std::option::Option< - unsafe extern "system" fn(pContext: PVOID, hModule: HMODULE, pszFile: LPCSTR) -> BOOL, ->; -pub type PF_DETOUR_IMPORT_FUNC_CALLBACK = ::std::option::Option< - unsafe extern "system" fn( - pContext: PVOID, - nOrdinal: DWORD, - pszFunc: LPCSTR, - pvFunc: PVOID, - ) -> BOOL, ->; -pub type PF_DETOUR_IMPORT_FUNC_CALLBACK_EX = ::std::option::Option< - unsafe extern "system" fn( - pContext: PVOID, - nOrdinal: DWORD, - pszFunc: LPCSTR, - ppvFunc: *mut PVOID, - ) -> BOOL, ->; -pub type PDETOUR_BINARY = *mut ::std::os::raw::c_void; -unsafe extern "system" { - /// Transaction APIs. - pub fn DetourTransactionBegin() -> LONG; -} -unsafe extern "system" { - pub fn DetourTransactionAbort() -> LONG; -} -unsafe extern "system" { - pub fn DetourTransactionCommit() -> LONG; -} -unsafe extern "system" { - pub fn DetourTransactionCommitEx(pppFailedPointer: *mut *mut PVOID) -> LONG; -} -unsafe extern "system" { - pub fn DetourUpdateThread(hThread: HANDLE) -> LONG; -} -unsafe extern "system" { - pub fn DetourAttach(ppPointer: *mut PVOID, pDetour: PVOID) -> LONG; -} -unsafe extern "system" { - pub fn DetourAttachEx( - ppPointer: *mut PVOID, - pDetour: PVOID, - ppRealTrampoline: *mut PDETOUR_TRAMPOLINE, - ppRealTarget: *mut PVOID, - ppRealDetour: *mut PVOID, - ) -> LONG; -} -unsafe extern "system" { - pub fn DetourDetach(ppPointer: *mut PVOID, pDetour: PVOID) -> LONG; -} -unsafe extern "system" { - pub fn DetourSetIgnoreTooSmall(fIgnore: BOOL) -> BOOL; -} -unsafe extern "system" { - pub fn DetourSetRetainRegions(fRetain: BOOL) -> BOOL; -} -unsafe extern "system" { - pub fn DetourSetSystemRegionLowerBound(pSystemRegionLowerBound: PVOID) -> PVOID; -} -unsafe extern "system" { - pub fn DetourSetSystemRegionUpperBound(pSystemRegionUpperBound: PVOID) -> PVOID; -} -unsafe extern "system" { - /// Code Functions. - pub fn DetourFindFunction(pszModule: LPCSTR, pszFunction: LPCSTR) -> PVOID; -} -unsafe extern "system" { - pub fn DetourCodeFromPointer(pPointer: PVOID, ppGlobals: *mut PVOID) -> PVOID; -} -unsafe extern "system" { - pub fn DetourCopyInstruction( - pDst: PVOID, - ppDstPool: *mut PVOID, - pSrc: PVOID, - ppTarget: *mut PVOID, - plExtra: *mut LONG, - ) -> PVOID; -} -unsafe extern "system" { - pub fn DetourSetCodeModule(hModule: HMODULE, fLimitReferencesToModule: BOOL) -> BOOL; -} -unsafe extern "system" { - pub fn DetourAllocateRegionWithinJumpBounds( - pbTarget: LPCVOID, - pcbAllocatedSize: PDWORD, - ) -> PVOID; -} -unsafe extern "system" { - pub fn DetourIsFunctionImported(pbCode: PBYTE, pbAddress: PBYTE) -> BOOL; -} -unsafe extern "system" { - /// Loaded Binary Functions. - pub fn DetourGetContainingModule(pvAddr: PVOID) -> HMODULE; -} -unsafe extern "system" { - pub fn DetourEnumerateModules(hModuleLast: HMODULE) -> HMODULE; -} -unsafe extern "system" { - pub fn DetourGetEntryPoint(hModule: HMODULE) -> PVOID; -} -unsafe extern "system" { - pub fn DetourGetModuleSize(hModule: HMODULE) -> ULONG; -} -unsafe extern "system" { - pub fn DetourEnumerateExports( - hModule: HMODULE, - pContext: PVOID, - pfExport: PF_DETOUR_ENUMERATE_EXPORT_CALLBACK, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourEnumerateImports( - hModule: HMODULE, - pContext: PVOID, - pfImportFile: PF_DETOUR_IMPORT_FILE_CALLBACK, - pfImportFunc: PF_DETOUR_IMPORT_FUNC_CALLBACK, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourEnumerateImportsEx( - hModule: HMODULE, - pContext: PVOID, - pfImportFile: PF_DETOUR_IMPORT_FILE_CALLBACK, - pfImportFuncEx: PF_DETOUR_IMPORT_FUNC_CALLBACK_EX, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourFindPayload( - hModule: HMODULE, - rguid: *const GUID, - pcbData: *mut DWORD, - ) -> PVOID; -} -unsafe extern "system" { - pub fn DetourFindPayloadEx(rguid: *const GUID, pcbData: *mut DWORD) -> PVOID; -} -unsafe extern "system" { - pub fn DetourGetSizeOfPayloads(hModule: HMODULE) -> DWORD; -} -unsafe extern "system" { - pub fn DetourFreePayload(pvData: PVOID) -> BOOL; -} -unsafe extern "system" { - /// Persistent Binary Functions. - pub fn DetourBinaryOpen(hFile: HANDLE) -> PDETOUR_BINARY; -} -unsafe extern "system" { - pub fn DetourBinaryEnumeratePayloads( - pBinary: PDETOUR_BINARY, - pGuid: *mut GUID, - pcbData: *mut DWORD, - pnIterator: *mut DWORD, - ) -> PVOID; -} -unsafe extern "system" { - pub fn DetourBinaryFindPayload( - pBinary: PDETOUR_BINARY, - rguid: *const GUID, - pcbData: *mut DWORD, - ) -> PVOID; -} -unsafe extern "system" { - pub fn DetourBinarySetPayload( - pBinary: PDETOUR_BINARY, - rguid: *const GUID, - pData: PVOID, - cbData: DWORD, - ) -> PVOID; -} -unsafe extern "system" { - pub fn DetourBinaryDeletePayload( - pBinary: PDETOUR_BINARY, - rguid: *const GUID, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourBinaryPurgePayloads(pBinary: PDETOUR_BINARY) -> BOOL; -} -unsafe extern "system" { - pub fn DetourBinaryResetImports(pBinary: PDETOUR_BINARY) -> BOOL; -} -unsafe extern "system" { - pub fn DetourBinaryEditImports( - pBinary: PDETOUR_BINARY, - pContext: PVOID, - pfByway: PF_DETOUR_BINARY_BYWAY_CALLBACK, - pfFile: PF_DETOUR_BINARY_FILE_CALLBACK, - pfSymbol: PF_DETOUR_BINARY_SYMBOL_CALLBACK, - pfCommit: PF_DETOUR_BINARY_COMMIT_CALLBACK, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourBinaryWrite(pBinary: PDETOUR_BINARY, hFile: HANDLE) -> BOOL; -} -unsafe extern "system" { - pub fn DetourBinaryClose(pBinary: PDETOUR_BINARY) -> BOOL; -} -unsafe extern "system" { - /// Create Process & Load Dll. - pub fn DetourFindRemotePayload( - hProcess: HANDLE, - rguid: *const GUID, - pcbData: *mut DWORD, - ) -> PVOID; -} -pub type PDETOUR_CREATE_PROCESS_ROUTINEA = ::std::option::Option< - unsafe extern "system" fn( - lpApplicationName: LPCSTR, - lpCommandLine: LPSTR, - lpProcessAttributes: LPSECURITY_ATTRIBUTES, - lpThreadAttributes: LPSECURITY_ATTRIBUTES, - bInheritHandles: BOOL, - dwCreationFlags: DWORD, - lpEnvironment: LPVOID, - lpCurrentDirectory: LPCSTR, - lpStartupInfo: LPSTARTUPINFOA, - lpProcessInformation: LPPROCESS_INFORMATION, - ) -> BOOL, ->; -pub type PDETOUR_CREATE_PROCESS_ROUTINEW = ::std::option::Option< - unsafe extern "system" fn( - lpApplicationName: LPCWSTR, - lpCommandLine: LPWSTR, - lpProcessAttributes: LPSECURITY_ATTRIBUTES, - lpThreadAttributes: LPSECURITY_ATTRIBUTES, - bInheritHandles: BOOL, - dwCreationFlags: DWORD, - lpEnvironment: LPVOID, - lpCurrentDirectory: LPCWSTR, - lpStartupInfo: LPSTARTUPINFOW, - lpProcessInformation: LPPROCESS_INFORMATION, - ) -> BOOL, ->; -unsafe extern "system" { - pub fn DetourCreateProcessWithDllA( - lpApplicationName: LPCSTR, - lpCommandLine: LPSTR, - lpProcessAttributes: LPSECURITY_ATTRIBUTES, - lpThreadAttributes: LPSECURITY_ATTRIBUTES, - bInheritHandles: BOOL, - dwCreationFlags: DWORD, - lpEnvironment: LPVOID, - lpCurrentDirectory: LPCSTR, - lpStartupInfo: LPSTARTUPINFOA, - lpProcessInformation: LPPROCESS_INFORMATION, - lpDllName: LPCSTR, - pfCreateProcessA: PDETOUR_CREATE_PROCESS_ROUTINEA, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourCreateProcessWithDllW( - lpApplicationName: LPCWSTR, - lpCommandLine: LPWSTR, - lpProcessAttributes: LPSECURITY_ATTRIBUTES, - lpThreadAttributes: LPSECURITY_ATTRIBUTES, - bInheritHandles: BOOL, - dwCreationFlags: DWORD, - lpEnvironment: LPVOID, - lpCurrentDirectory: LPCWSTR, - lpStartupInfo: LPSTARTUPINFOW, - lpProcessInformation: LPPROCESS_INFORMATION, - lpDllName: LPCSTR, - pfCreateProcessW: PDETOUR_CREATE_PROCESS_ROUTINEW, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourCreateProcessWithDllExA( - lpApplicationName: LPCSTR, - lpCommandLine: LPSTR, - lpProcessAttributes: LPSECURITY_ATTRIBUTES, - lpThreadAttributes: LPSECURITY_ATTRIBUTES, - bInheritHandles: BOOL, - dwCreationFlags: DWORD, - lpEnvironment: LPVOID, - lpCurrentDirectory: LPCSTR, - lpStartupInfo: LPSTARTUPINFOA, - lpProcessInformation: LPPROCESS_INFORMATION, - lpDllName: LPCSTR, - pfCreateProcessA: PDETOUR_CREATE_PROCESS_ROUTINEA, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourCreateProcessWithDllExW( - lpApplicationName: LPCWSTR, - lpCommandLine: LPWSTR, - lpProcessAttributes: LPSECURITY_ATTRIBUTES, - lpThreadAttributes: LPSECURITY_ATTRIBUTES, - bInheritHandles: BOOL, - dwCreationFlags: DWORD, - lpEnvironment: LPVOID, - lpCurrentDirectory: LPCWSTR, - lpStartupInfo: LPSTARTUPINFOW, - lpProcessInformation: LPPROCESS_INFORMATION, - lpDllName: LPCSTR, - pfCreateProcessW: PDETOUR_CREATE_PROCESS_ROUTINEW, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourCreateProcessWithDllsA( - lpApplicationName: LPCSTR, - lpCommandLine: LPSTR, - lpProcessAttributes: LPSECURITY_ATTRIBUTES, - lpThreadAttributes: LPSECURITY_ATTRIBUTES, - bInheritHandles: BOOL, - dwCreationFlags: DWORD, - lpEnvironment: LPVOID, - lpCurrentDirectory: LPCSTR, - lpStartupInfo: LPSTARTUPINFOA, - lpProcessInformation: LPPROCESS_INFORMATION, - nDlls: DWORD, - rlpDlls: *mut LPCSTR, - pfCreateProcessA: PDETOUR_CREATE_PROCESS_ROUTINEA, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourCreateProcessWithDllsW( - lpApplicationName: LPCWSTR, - lpCommandLine: LPWSTR, - lpProcessAttributes: LPSECURITY_ATTRIBUTES, - lpThreadAttributes: LPSECURITY_ATTRIBUTES, - bInheritHandles: BOOL, - dwCreationFlags: DWORD, - lpEnvironment: LPVOID, - lpCurrentDirectory: LPCWSTR, - lpStartupInfo: LPSTARTUPINFOW, - lpProcessInformation: LPPROCESS_INFORMATION, - nDlls: DWORD, - rlpDlls: *mut LPCSTR, - pfCreateProcessW: PDETOUR_CREATE_PROCESS_ROUTINEW, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourProcessViaHelperA( - dwTargetPid: DWORD, - lpDllName: LPCSTR, - pfCreateProcessA: PDETOUR_CREATE_PROCESS_ROUTINEA, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourProcessViaHelperW( - dwTargetPid: DWORD, - lpDllName: LPCSTR, - pfCreateProcessW: PDETOUR_CREATE_PROCESS_ROUTINEW, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourProcessViaHelperDllsA( - dwTargetPid: DWORD, - nDlls: DWORD, - rlpDlls: *mut LPCSTR, - pfCreateProcessA: PDETOUR_CREATE_PROCESS_ROUTINEA, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourProcessViaHelperDllsW( - dwTargetPid: DWORD, - nDlls: DWORD, - rlpDlls: *mut LPCSTR, - pfCreateProcessW: PDETOUR_CREATE_PROCESS_ROUTINEW, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourUpdateProcessWithDll( - hProcess: HANDLE, - rlpDlls: *mut LPCSTR, - nDlls: DWORD, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourUpdateProcessWithDllEx( - hProcess: HANDLE, - hImage: HMODULE, - bIs32Bit: BOOL, - rlpDlls: *mut LPCSTR, - nDlls: DWORD, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourCopyPayloadToProcess( - hProcess: HANDLE, - rguid: *const GUID, - pvData: LPCVOID, - cbData: DWORD, - ) -> BOOL; -} -unsafe extern "system" { - pub fn DetourCopyPayloadToProcessEx( - hProcess: HANDLE, - rguid: *const GUID, - pvData: LPCVOID, - cbData: DWORD, - ) -> PVOID; -} -unsafe extern "system" { - pub fn DetourRestoreAfterWith() -> BOOL; -} -unsafe extern "system" { - pub fn DetourRestoreAfterWithEx(pvData: PVOID, cbData: DWORD) -> BOOL; -} -unsafe extern "system" { - pub fn DetourIsHelperProcess() -> BOOL; -} -unsafe extern "system" { - pub fn DetourFinishHelperProcess( - arg1: HWND, - arg2: HINSTANCE, - arg3: LPSTR, - arg4: INT, - ); -} diff --git a/crates/fspy_detours_sys/src/lib.rs b/crates/fspy_detours_sys/src/lib.rs deleted file mode 100644 index f147d4f90..000000000 --- a/crates/fspy_detours_sys/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![cfg(windows)] - -#[expect(non_camel_case_types, non_snake_case, reason = "generated FFI bindings")] -#[expect( - clippy::allow_attributes, - reason = "can't use expect: wildcard_imports lint is unfulfilled in lib test mode" -)] -#[allow(clippy::wildcard_imports, reason = "generated FFI bindings use wildcard imports")] -#[rustfmt::skip] // generated code is formatted by prettyplease, not rustfmt -mod generated_bindings; - -pub use generated_bindings::*; diff --git a/crates/fspy_detours_sys/tests/bindings.rs b/crates/fspy_detours_sys/tests/bindings.rs deleted file mode 100644 index 362024a42..000000000 --- a/crates/fspy_detours_sys/tests/bindings.rs +++ /dev/null @@ -1,68 +0,0 @@ -#![cfg(windows)] -use std::{env, fs}; - -use cow_utils::CowUtils; - -#[test] -fn detours_bindings() { - let bindings = bindgen::Builder::default() - .clang_args(["-Idetours/src", "-DWIN32_LEAN_AND_MEAN"]) - .header_contents("wrapper.h", "#include \n#include \n") - .allowlist_function("Detour.*") - .blocklist_type("LP.*") - .blocklist_type("_GUID") - .blocklist_type("GUID") - .blocklist_type("ULONG") - .blocklist_type("PVOID") - .blocklist_type("DWORD") - .blocklist_type("wchar_t") - .blocklist_type("BOOL") - .blocklist_type("BYTE") - .blocklist_type("WORD") - .blocklist_type("PBYTE") - .blocklist_type("PDWORD") - .blocklist_type("INT") - .blocklist_type("CHAR") - .blocklist_type("LONG") - .blocklist_type("WCHAR") - .blocklist_type("HANDLE") - .blocklist_type("HMODULE") - .blocklist_type("HINSTANCE.*") - .blocklist_type("HWND.*") - .blocklist_type("_SECURITY_ATTRIBUTES") - .blocklist_type("_PROCESS_INFORMATION") - .blocklist_type("_STARTUPINFOA") - .blocklist_type("_STARTUPINFOW") - .disable_header_comment() - .raw_line("use winapi::shared::minwindef::*;") - .raw_line("use winapi::um::winnt::*;") - .raw_line("use winapi::um::winnt::INT;") - .raw_line("use winapi::um::minwinbase::*;") - .raw_line("use winapi::um::processthreadsapi::*;") - .raw_line("use winapi::shared::guiddef::*;") - .raw_line("use winapi::shared::windef::*;") - .layout_tests(false) - .formatter(bindgen::Formatter::Prettyplease) - // Detour functions are stdcall on 32-bit Windows - .override_abi(bindgen::Abi::System, ".*") - .generate() - .expect("Unable to generate bindings"); - - // bindgen produces raw_lines with \r\n line endings on Windows; - // Git on Windows may check out files using CRLF line endings, depending on user config. - // To avoid unnecessary diffs, normalize all line endings to \n. - let bindings_string = bindings.to_string(); - let bindings_content = bindings_string.cow_replace("\r\n", "\n"); - let bindings_path = "src/generated_bindings.rs"; - - if env::var("FSPY_DETOURS_WRITE_BINDINGS").as_deref() == Ok("1") { - fs::write(bindings_path, bindings_content.as_bytes()).unwrap(); - } else { - let existing_string = fs::read_to_string(bindings_path).unwrap_or_default(); - let existing_bindings_content = existing_string.cow_replace("\r\n", "\n"); - assert_eq!( - existing_bindings_content, bindings_content, - "Bindings are out of date. Run this test with FSPY_DETOURS_WRITE_BINDINGS=1 to update them." - ); - } -} diff --git a/crates/fspy_e2e/.clippy.toml b/crates/fspy_e2e/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/fspy_e2e/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/fspy_e2e/Cargo.toml b/crates/fspy_e2e/Cargo.toml deleted file mode 100644 index 3ccbcc87b..000000000 --- a/crates/fspy_e2e/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "fspy_e2e" -version = "0.0.0" -edition.workspace = true -license.workspace = true -publish = false - -[dependencies] -fspy = { workspace = true } -rustc-hash = { workspace = true } -serde = { workspace = true, features = ["derive"] } -tokio = { workspace = true, features = ["full"] } -tokio-util = { workspace = true } -toml = { workspace = true } - -[lints] -workspace = true diff --git a/crates/fspy_e2e/e2e_config.toml b/crates/fspy_e2e/e2e_config.toml deleted file mode 100644 index 7cb36a819..000000000 --- a/crates/fspy_e2e/e2e_config.toml +++ /dev/null @@ -1,7 +0,0 @@ -[cases.outline-oxlint] -dir = "repos/outline" -cmd = ["yarn", "lint"] - -[cases.outline-rolldown-vite-build] -dir = "repos/outline" -cmd = ["yarn", "vite:build"] diff --git a/crates/fspy_e2e/snaps/outline-oxlint.txt b/crates/fspy_e2e/snaps/outline-oxlint.txt deleted file mode 100644 index 78bc26038..000000000 --- a/crates/fspy_e2e/snaps/outline-oxlint.txt +++ /dev/null @@ -1,1918 +0,0 @@ -: ReadDir -.corepack.env: Read -.eslintignore: Read -.git/info/exclude: Read -.gitignore: Read -.oxlintrc.json: Read -.yarnrc: Read -.yarnrc.yml: Read -LICENSE: Read -README.md: Read -app: ReadDir -app/../.oxlintrc.json: Read -app/.oxlintrc.json: Read -app/actions: ReadDir -app/actions/definitions: ReadDir -app/actions/definitions/apiKeys.tsx: Read -app/actions/definitions/collections.tsx: Read -app/actions/definitions/comments.tsx: Read -app/actions/definitions/developer.tsx: Read -app/actions/definitions/documents.tsx: Read -app/actions/definitions/integrations.tsx: Read -app/actions/definitions/navigation.tsx: Read -app/actions/definitions/notifications.tsx: Read -app/actions/definitions/oauthClients.tsx: Read -app/actions/definitions/revisions.tsx: Read -app/actions/definitions/settings.tsx: Read -app/actions/definitions/shares.tsx: Read -app/actions/definitions/teams.tsx: Read -app/actions/definitions/users.tsx: Read -app/actions/index.ts: Read -app/actions/root.ts: Read -app/actions/sections.ts: Read -app/components: ReadDir -app/components/ActionButton.tsx: Read -app/components/Actions.ts: Read -app/components/Analytics.tsx: Read -app/components/Arrow.tsx: Read -app/components/ArrowKeyNavigation.tsx: Read -app/components/Authenticated.tsx: Read -app/components/AuthenticatedLayout.tsx: Read -app/components/Avatar: ReadDir -app/components/Avatar/Avatar.tsx: Read -app/components/Avatar/AvatarWithPresence.tsx: Read -app/components/Avatar/GroupAvatar.tsx: Read -app/components/Avatar/Initials.tsx: Read -app/components/Avatar/index.ts: Read -app/components/Badge.ts: Read -app/components/Branding.tsx: Read -app/components/Breadcrumb.tsx: Read -app/components/Button.tsx: Read -app/components/ButtonLarge.ts: Read -app/components/ButtonLink.tsx: Read -app/components/ButtonSmall.ts: Read -app/components/CenteredContent.tsx: Read -app/components/ChangeLanguage.tsx: Read -app/components/CircularProgressBar.tsx: Read -app/components/ClickablePadding.ts: Read -app/components/Collaborators.tsx: Read -app/components/Collection: ReadDir -app/components/Collection/CollectionEdit.tsx: Read -app/components/Collection/CollectionForm.tsx: Read -app/components/Collection/CollectionNew.tsx: Read -app/components/CollectionBreadcrumb.tsx: Read -app/components/CollectionDeleteDialog.tsx: Read -app/components/CommandBar: ReadDir -app/components/CommandBar/CommandBar.tsx: Read -app/components/CommandBar/CommandBarItem.tsx: Read -app/components/CommandBar/CommandBarResults.tsx: Read -app/components/CommandBar/index.ts: Read -app/components/CommandBar/useRecentDocumentActions.tsx: Read -app/components/CommandBar/useSettingsAction.tsx: Read -app/components/CommandBar/useTemplatesAction.tsx: Read -app/components/CommentDeleteDialog.tsx: Read -app/components/ConfirmMoveDialog.tsx: Read -app/components/ConfirmationDialog.tsx: Read -app/components/ContentEditable.tsx: Read -app/components/ContextMenu: ReadDir -app/components/ContextMenu/Header.ts: Read -app/components/ContextMenu/MenuIconWrapper.ts: Read -app/components/ContextMenu/MenuItem.tsx: Read -app/components/ContextMenu/MouseSafeArea.tsx: Read -app/components/ContextMenu/OverflowMenuButton.tsx: Read -app/components/ContextMenu/Separator.tsx: Read -app/components/ContextMenu/Template.tsx: Read -app/components/ContextMenu/index.tsx: Read -app/components/CopyToClipboard.ts: Read -app/components/DefaultCollectionInputSelect.tsx: Read -app/components/DelayedMount.ts: Read -app/components/DesktopEventHandler.tsx: Read -app/components/Dialogs.tsx: Read -app/components/DisconnectAnalyticsDialog.tsx: Read -app/components/Divider.ts: Read -app/components/DocumentBreadcrumb.tsx: Read -app/components/DocumentCard.tsx: Read -app/components/DocumentContext.tsx: Read -app/components/DocumentCopy.tsx: Read -app/components/DocumentExplorer.tsx: Read -app/components/DocumentExplorerNode.tsx: Read -app/components/DocumentExplorerSearchResult.tsx: Read -app/components/DocumentListItem.tsx: Read -app/components/DocumentMeta.tsx: Read -app/components/DocumentTasks.tsx: Read -app/components/DocumentViews.tsx: Read -app/components/DocumentsLoader.tsx: Read -app/components/EditableTitle.tsx: Read -app/components/Editor.tsx: Read -app/components/Emoji.tsx: Read -app/components/Empty.ts: Read -app/components/ErrorBoundary.tsx: Read -app/components/EventListItem.tsx: Read -app/components/ExportDialog.tsx: Read -app/components/Facepile.tsx: Read -app/components/Fade.tsx: Read -app/components/FilterOptions.tsx: Read -app/components/Flex.tsx: Read -app/components/FullscreenLoading.tsx: Read -app/components/Guide.tsx: Read -app/components/Header.tsx: Read -app/components/Heading.ts: Read -app/components/Highlight.tsx: Read -app/components/HoverPreview: ReadDir -app/components/HoverPreview/Components.tsx: Read -app/components/HoverPreview/HoverPreview.tsx: Read -app/components/HoverPreview/HoverPreviewDocument.tsx: Read -app/components/HoverPreview/HoverPreviewIssue.tsx: Read -app/components/HoverPreview/HoverPreviewLink.tsx: Read -app/components/HoverPreview/HoverPreviewMention.tsx: Read -app/components/HoverPreview/HoverPreviewPullRequest.tsx: Read -app/components/HoverPreview/index.ts: Read -app/components/IconPicker: ReadDir -app/components/IconPicker/components: ReadDir -app/components/IconPicker/components/ColorPicker.tsx: Read -app/components/IconPicker/components/EmojiPanel.tsx: Read -app/components/IconPicker/components/Grid.tsx: Read -app/components/IconPicker/components/GridTemplate.tsx: Read -app/components/IconPicker/components/IconButton.tsx: Read -app/components/IconPicker/components/IconPanel.tsx: Read -app/components/IconPicker/components/PopoverButton.tsx: Read -app/components/IconPicker/components/SkinTonePicker.tsx: Read -app/components/IconPicker/index.tsx: Read -app/components/IconPicker/utils.ts: Read -app/components/Icons: ReadDir -app/components/Icons/CircleIcon.tsx: Read -app/components/Icons/CollectionIcon.tsx: Read -app/components/Icons/GoogleIcon.tsx: Read -app/components/Icons/LanguageIcon.tsx: Read -app/components/Icons/MarkdownIcon.tsx: Read -app/components/Icons/OutlineIcon.tsx: Read -app/components/Input.tsx: Read -app/components/InputColor.tsx: Read -app/components/InputLarge.ts: Read -app/components/InputMemberPermissionSelect.tsx: Read -app/components/InputSearch.tsx: Read -app/components/InputSearchPage.tsx: Read -app/components/InputSelect.tsx: Read -app/components/InputSelectPermission.tsx: Read -app/components/Key.ts: Read -app/components/LanguagePrompt.tsx: Read -app/components/Layout.tsx: Read -app/components/LazyLoad.ts: Read -app/components/LazyPolyfills.tsx: Read -app/components/List: ReadDir -app/components/List/Error.tsx: Read -app/components/List/Item.tsx: Read -app/components/List/List.ts: Read -app/components/List/Placeholder.tsx: Read -app/components/List/index.ts: Read -app/components/LoadingIndicator: ReadDir -app/components/LoadingIndicator/LoadingIndicator.ts: Read -app/components/LoadingIndicator/LoadingIndicatorBar.tsx: Read -app/components/LoadingIndicator/index.ts: Read -app/components/LocaleTime.tsx: Read -app/components/Menu: ReadDir -app/components/Menu/DropdownMenu.tsx: Read -app/components/Menu/OverflowMenuButton.tsx: Read -app/components/Menu/transformer.tsx: Read -app/components/Modal.tsx: Read -app/components/NavLink.tsx: Read -app/components/Notice.tsx: Read -app/components/Notifications: ReadDir -app/components/Notifications/NotificationIcon.tsx: Read -app/components/Notifications/NotificationListItem.tsx: Read -app/components/Notifications/Notifications.tsx: Read -app/components/Notifications/NotificationsPopover.tsx: Read -app/components/NudeButton.tsx: Read -app/components/OAuthClient: ReadDir -app/components/OAuthClient/OAuthClientForm.tsx: Read -app/components/OAuthClient/OAuthClientNew.tsx: Read -app/components/OneTimePasswordInput.tsx: Read -app/components/PageScroll.tsx: Read -app/components/PageTheme.ts: Read -app/components/PageTitle.tsx: Read -app/components/PaginatedDocumentList.tsx: Read -app/components/PaginatedEventList.tsx: Read -app/components/PaginatedList.test.tsx: Read -app/components/PaginatedList.tsx: Read -app/components/PinnedDocuments.tsx: Read -app/components/PlaceholderDocument.tsx: Read -app/components/PlaceholderText.tsx: Read -app/components/PluginIcon.tsx: Read -app/components/Portal.tsx: Read -app/components/ProfiledRoute.ts: Read -app/components/Reactions: ReadDir -app/components/Reactions/Reaction.tsx: Read -app/components/Reactions/ReactionList.tsx: Read -app/components/Reactions/ReactionPicker.tsx: Read -app/components/Reactions/ViewReactionsDialog.tsx: Read -app/components/RegisterKeyDown.ts: Read -app/components/ResizingHeightContainer.tsx: Read -app/components/RevisionListItem.tsx: Read -app/components/Scene.tsx: Read -app/components/ScrollContext.ts: Read -app/components/ScrollToTop.ts: Read -app/components/Scrollable.tsx: Read -app/components/SearchActions.ts: Read -app/components/SearchListItem.tsx: Read -app/components/SearchPopover.tsx: Read -app/components/Sharing: ReadDir -app/components/Sharing/Collection: ReadDir -app/components/Sharing/Collection/AccessControlList.tsx: Read -app/components/Sharing/Collection/PublicAccess.tsx: Read -app/components/Sharing/Collection/SharePopover.tsx: Read -app/components/Sharing/Document: ReadDir -app/components/Sharing/Document/AccessControlList.tsx: Read -app/components/Sharing/Document/DocumentMemberList.tsx: Read -app/components/Sharing/Document/DocumentMemberListItem.tsx: Read -app/components/Sharing/Document/PublicAccess.tsx: Read -app/components/Sharing/Document/SharePopover.tsx: Read -app/components/Sharing/Document/index.tsx: Read -app/components/Sharing/components: ReadDir -app/components/Sharing/components/Actions.tsx: Read -app/components/Sharing/components/CopyLinkButton.tsx: Read -app/components/Sharing/components/ListItem.tsx: Read -app/components/Sharing/components/PermissionAction.tsx: Read -app/components/Sharing/components/Placeholder.tsx: Read -app/components/Sharing/components/SearchInput.tsx: Read -app/components/Sharing/components/Suggestions.tsx: Read -app/components/Sharing/components/index.tsx: Read -app/components/Sidebar: ReadDir -app/components/Sidebar/App.tsx: Read -app/components/Sidebar/Right.tsx: Read -app/components/Sidebar/Settings.tsx: Read -app/components/Sidebar/Shared.tsx: Read -app/components/Sidebar/Sidebar.tsx: Read -app/components/Sidebar/components: ReadDir -app/components/Sidebar/components/ArchiveLink.tsx: Read -app/components/Sidebar/components/ArchivedCollectionLink.tsx: Read -app/components/Sidebar/components/CollectionLink.tsx: Read -app/components/Sidebar/components/CollectionLinkChildren.tsx: Read -app/components/Sidebar/components/Collections.tsx: Read -app/components/Sidebar/components/Disclosure.tsx: Read -app/components/Sidebar/components/DocumentLink.tsx: Read -app/components/Sidebar/components/DraftsLink.tsx: Read -app/components/Sidebar/components/DragPlaceholder.tsx: Read -app/components/Sidebar/components/DraggableCollectionLink.tsx: Read -app/components/Sidebar/components/DropCursor.tsx: Read -app/components/Sidebar/components/DropToImport.tsx: Read -app/components/Sidebar/components/Folder.tsx: Read -app/components/Sidebar/components/GroupLink.tsx: Read -app/components/Sidebar/components/Header.tsx: Read -app/components/Sidebar/components/HistoryNavigation.tsx: Read -app/components/Sidebar/components/NavLink.tsx: Read -app/components/Sidebar/components/PlaceholderCollections.tsx: Read -app/components/Sidebar/components/Relative.tsx: Read -app/components/Sidebar/components/ResizeBorder.ts: Read -app/components/Sidebar/components/Section.ts: Read -app/components/Sidebar/components/SharedCollectionLink.tsx: Read -app/components/Sidebar/components/SharedDocumentLink.tsx: Read -app/components/Sidebar/components/SharedWithMe.tsx: Read -app/components/Sidebar/components/SharedWithMeLink.tsx: Read -app/components/Sidebar/components/SidebarAction.tsx: Read -app/components/Sidebar/components/SidebarButton.tsx: Read -app/components/Sidebar/components/SidebarContext.ts: Read -app/components/Sidebar/components/SidebarLink.tsx: Read -app/components/Sidebar/components/Starred.tsx: Read -app/components/Sidebar/components/StarredLink.tsx: Read -app/components/Sidebar/components/ToggleButton.tsx: Read -app/components/Sidebar/components/TrashLink.tsx: Read -app/components/Sidebar/components/Version.tsx: Read -app/components/Sidebar/hooks: ReadDir -app/components/Sidebar/hooks/useCollectionDocuments.ts: Read -app/components/Sidebar/hooks/useDragAndDrop.tsx: Read -app/components/Sidebar/hooks/useSidebarLabelAndIcon.tsx: Read -app/components/Sidebar/index.ts: Read -app/components/SkipNavContent.tsx: Read -app/components/SkipNavLink.tsx: Read -app/components/SortableTable.tsx: Read -app/components/Star.tsx: Read -app/components/Subheading.tsx: Read -app/components/Switch.tsx: Read -app/components/Tab.tsx: Read -app/components/Table.tsx: Read -app/components/Tabs.tsx: Read -app/components/TeamContext.ts: Read -app/components/TeamLogo.ts: Read -app/components/TemplatizeDialog: ReadDir -app/components/TemplatizeDialog/Label.tsx: Read -app/components/TemplatizeDialog/SelectLocation.tsx: Read -app/components/TemplatizeDialog/index.tsx: Read -app/components/Text.ts: Read -app/components/Theme.tsx: Read -app/components/Time.tsx: Read -app/components/Toasts.tsx: Read -app/components/Tooltip.tsx: Read -app/components/TooltipContext.tsx: Read -app/components/UnreadBadge.tsx: Read -app/components/UserDialogs.tsx: Read -app/components/WebsocketProvider.tsx: Read -app/components/primitives: ReadDir -app/components/primitives/Drawer.tsx: Read -app/components/primitives/DropdownMenu.tsx: Read -app/components/primitives/InputSelect.tsx: Read -app/components/primitives/Popover.tsx: Read -app/components/primitives/components: ReadDir -app/components/primitives/components/InputSelect.tsx: Read -app/components/primitives/components/Menu.tsx: Read -app/components/primitives/components/Overlay.tsx: Read -app/components/withStores.tsx: Read -app/editor: ReadDir -app/editor/components: ReadDir -app/editor/components/BlockMenu.tsx: Read -app/editor/components/ComponentView.tsx: Read -app/editor/components/EditorContext.tsx: Read -app/editor/components/EmbedLinkEditor.tsx: Read -app/editor/components/EmojiMenu.tsx: Read -app/editor/components/EmojiMenuItem.tsx: Read -app/editor/components/FindAndReplace.tsx: Read -app/editor/components/FloatingToolbar.tsx: Read -app/editor/components/Input.tsx: Read -app/editor/components/LinkEditor.tsx: Read -app/editor/components/MediaDimension.tsx: Read -app/editor/components/MentionMenu.tsx: Read -app/editor/components/NodeViewRenderer.tsx: Read -app/editor/components/PasteMenu.tsx: Read -app/editor/components/SelectionToolbar.tsx: Read -app/editor/components/SuggestionsMenu.tsx: Read -app/editor/components/SuggestionsMenuItem.tsx: Read -app/editor/components/ToolbarButton.tsx: Read -app/editor/components/ToolbarMenu.tsx: Read -app/editor/components/ToolbarSeparator.tsx: Read -app/editor/components/Tooltip.tsx: Read -app/editor/components/WithTheme.tsx: Read -app/editor/extensions: ReadDir -app/editor/extensions/BlockMenu.tsx: Read -app/editor/extensions/ClipboardTextSerializer.ts: Read -app/editor/extensions/EmojiMenu.tsx: Read -app/editor/extensions/FindAndReplace.tsx: Read -app/editor/extensions/HoverPreviews.tsx: Read -app/editor/extensions/Keys.ts: Read -app/editor/extensions/MentionMenu.tsx: Read -app/editor/extensions/Multiplayer.ts: Read -app/editor/extensions/PasteHandler.tsx: Read -app/editor/extensions/PreventTab.ts: Read -app/editor/extensions/SmartText.ts: Read -app/editor/extensions/Suggestion.ts: Read -app/editor/extensions/UpArrowAtStart.ts: Read -app/editor/extensions/index.ts: Read -app/editor/index.tsx: Read -app/editor/menus: ReadDir -app/editor/menus/attachment.tsx: Read -app/editor/menus/block.tsx: Read -app/editor/menus/code.tsx: Read -app/editor/menus/divider.tsx: Read -app/editor/menus/formatting.tsx: Read -app/editor/menus/image.tsx: Read -app/editor/menus/notice.tsx: Read -app/editor/menus/readOnly.tsx: Read -app/editor/menus/table.tsx: Read -app/editor/menus/tableCell.tsx: Read -app/editor/menus/tableCol.tsx: Read -app/editor/menus/tableRow.tsx: Read -app/env.ts: Read -app/hooks: ReadDir -app/hooks/useActionContext.ts: Read -app/hooks/useAutoRefresh.ts: Read -app/hooks/useBoolean.ts: Read -app/hooks/useBuildTheme.ts: Read -app/hooks/useClickIntent.ts: Read -app/hooks/useCollectionTrees.ts: Read -app/hooks/useCommandBarActions.ts: Read -app/hooks/useComputed.ts: Read -app/hooks/useCurrentTeam.ts: Read -app/hooks/useCurrentUser.ts: Read -app/hooks/useDesktopTitlebar.ts: Read -app/hooks/useDictionary.ts: Read -app/hooks/useEditingFocus.ts: Read -app/hooks/useEditorClickHandlers.ts: Read -app/hooks/useEmbeds.ts: Read -app/hooks/useEventListener.ts: Read -app/hooks/useFocusedComment.ts: Read -app/hooks/useHover.ts: Read -app/hooks/useIdle.ts: Read -app/hooks/useImportDocument.ts: Read -app/hooks/useInterval.ts: Read -app/hooks/useIsMounted.ts: Read -app/hooks/useKeyDown.ts: Read -app/hooks/useLastVisitedPath.tsx: Read -app/hooks/useLocaleTime.ts: Read -app/hooks/useLocationSidebarContext.ts: Read -app/hooks/useLoggedInSessions.ts: Read -app/hooks/useMaxHeight.ts: Read -app/hooks/useMediaQuery.ts: Read -app/hooks/useMenuAction.ts: Read -app/hooks/useMenuContext.tsx: Read -app/hooks/useMenuHeight.ts: Read -app/hooks/useMenuState.tsx: Read -app/hooks/useMobile.ts: Read -app/hooks/useMousePosition.ts: Read -app/hooks/useOnClickOutside.ts: Read -app/hooks/useOnScreen.ts: Read -app/hooks/usePageVisibility.ts: Read -app/hooks/usePaginatedRequest.ts: Read -app/hooks/usePersistedState.ts: Read -app/hooks/usePinnedDocuments.ts: Read -app/hooks/usePolicy.ts: Read -app/hooks/usePrevious.ts: Read -app/hooks/useQuery.ts: Read -app/hooks/useQueryNotices.ts: Read -app/hooks/useRequest.ts: Read -app/hooks/useSettingsConfig.ts: Read -app/hooks/useStores.ts: Read -app/hooks/useTableRequest.ts: Read -app/hooks/useTemplateMenuActions.tsx: Read -app/hooks/useTextSelection.ts: Read -app/hooks/useTextStats.ts: Read -app/hooks/useThrottledCallback.ts: Read -app/hooks/useUnmount.ts: Read -app/hooks/useUserLocale.ts: Read -app/hooks/useViewportHeight.ts: Read -app/hooks/useWindowScrollPosition.ts: Read -app/hooks/useWindowSize.ts: Read -app/index.tsx: Read -app/menus: ReadDir -app/menus/AccountMenu.tsx: Read -app/menus/ApiKeyMenu.tsx: Read -app/menus/BreadcrumbMenu.tsx: Read -app/menus/CollectionMenu.tsx: Read -app/menus/CommentMenu.tsx: Read -app/menus/DocumentMenu.tsx: Read -app/menus/FileOperationMenu.tsx: Read -app/menus/GroupMemberMenu.tsx: Read -app/menus/GroupMenu.tsx: Read -app/menus/ImportMenu.tsx: Read -app/menus/InsightsMenu.tsx: Read -app/menus/NewChildDocumentMenu.tsx: Read -app/menus/NewDocumentMenu.tsx: Read -app/menus/NewTemplateMenu.tsx: Read -app/menus/NotificationMenu.tsx: Read -app/menus/OAuthAuthenticationMenu.tsx: Read -app/menus/OAuthClientMenu.tsx: Read -app/menus/RevisionMenu.tsx: Read -app/menus/ShareMenu.tsx: Read -app/menus/TableOfContentsMenu.tsx: Read -app/menus/TeamMenu.tsx: Read -app/menus/TemplatesMenu.tsx: Read -app/menus/UserMenu.tsx: Read -app/menus/separator.ts: Read -app/models: ReadDir -app/models/ApiKey.ts: Read -app/models/AuthenticationProvider.ts: Read -app/models/Collection.test.ts: Read -app/models/Collection.ts: Read -app/models/Comment.ts: Read -app/models/Document.ts: Read -app/models/Event.ts: Read -app/models/FileOperation.ts: Read -app/models/Group.ts: Read -app/models/GroupMembership.ts: Read -app/models/GroupUser.ts: Read -app/models/Import.ts: Read -app/models/Integration.ts: Read -app/models/Membership.ts: Read -app/models/Notification.ts: Read -app/models/Pin.ts: Read -app/models/Policy.ts: Read -app/models/Revision.ts: Read -app/models/SearchQuery.ts: Read -app/models/Share.ts: Read -app/models/Star.ts: Read -app/models/Subscription.ts: Read -app/models/Team.ts: Read -app/models/Unfurl.ts: Read -app/models/User.ts: Read -app/models/UserMembership.ts: Read -app/models/View.ts: Read -app/models/WebhookSubscription.ts: Read -app/models/base: ReadDir -app/models/base/ArchivableModel.ts: Read -app/models/base/Model.ts: Read -app/models/base/ParanoidModel.ts: Read -app/models/decorators: ReadDir -app/models/decorators/Field.ts: Read -app/models/decorators/Lifecycle.ts: Read -app/models/decorators/Relation.ts: Read -app/models/interfaces: ReadDir -app/models/interfaces/Searchable.ts: Read -app/models/oauth: ReadDir -app/models/oauth/OAuthAuthentication.ts: Read -app/models/oauth/OAuthClient.ts: Read -app/routes: ReadDir -app/routes/authenticated.tsx: Read -app/routes/index.tsx: Read -app/routes/settings.tsx: Read -app/scenes: ReadDir -app/scenes/ApiKeyNew: ReadDir -app/scenes/ApiKeyNew/components: ReadDir -app/scenes/ApiKeyNew/components/ExpiryDatePicker.tsx: Read -app/scenes/ApiKeyNew/index.tsx: Read -app/scenes/ApiKeyNew/utils.ts: Read -app/scenes/Archive.tsx: Read -app/scenes/Collection: ReadDir -app/scenes/Collection/components: ReadDir -app/scenes/Collection/components/Actions.tsx: Read -app/scenes/Collection/components/DropToImport.tsx: Read -app/scenes/Collection/components/Empty.tsx: Read -app/scenes/Collection/components/MembershipPreview.tsx: Read -app/scenes/Collection/components/Notices.tsx: Read -app/scenes/Collection/components/Overview.tsx: Read -app/scenes/Collection/components/ShareButton.tsx: Read -app/scenes/Collection/index.tsx: Read -app/scenes/DesktopRedirect.tsx: Read -app/scenes/Document: ReadDir -app/scenes/Document/components: ReadDir -app/scenes/Document/components/AsyncMultiplayerEditor.ts: Read -app/scenes/Document/components/CommentEditor.tsx: Read -app/scenes/Document/components/CommentForm.tsx: Read -app/scenes/Document/components/CommentSortMenu.tsx: Read -app/scenes/Document/components/CommentThread.tsx: Read -app/scenes/Document/components/CommentThreadItem.tsx: Read -app/scenes/Document/components/Comments.tsx: Read -app/scenes/Document/components/ConnectionStatus.tsx: Read -app/scenes/Document/components/Container.ts: Read -app/scenes/Document/components/Contents.tsx: Read -app/scenes/Document/components/DataLoader.tsx: Read -app/scenes/Document/components/Document.tsx: Read -app/scenes/Document/components/DocumentMeta.tsx: Read -app/scenes/Document/components/DocumentTitle.tsx: Read -app/scenes/Document/components/Editor.tsx: Read -app/scenes/Document/components/Header.tsx: Read -app/scenes/Document/components/HighlightText.ts: Read -app/scenes/Document/components/History.tsx: Read -app/scenes/Document/components/Insights.tsx: Read -app/scenes/Document/components/KeyboardShortcutsButton.tsx: Read -app/scenes/Document/components/Loading.tsx: Read -app/scenes/Document/components/MarkAsViewed.ts: Read -app/scenes/Document/components/MeasuredContainer.tsx: Read -app/scenes/Document/components/MultiplayerEditor.tsx: Read -app/scenes/Document/components/Notices.tsx: Read -app/scenes/Document/components/ObservingBanner.tsx: Read -app/scenes/Document/components/PublicBreadcrumb.tsx: Read -app/scenes/Document/components/PublicReferences.tsx: Read -app/scenes/Document/components/ReferenceListItem.tsx: Read -app/scenes/Document/components/References.tsx: Read -app/scenes/Document/components/RevisionViewer.tsx: Read -app/scenes/Document/components/ShareButton.tsx: Read -app/scenes/Document/components/SidebarLayout.tsx: Read -app/scenes/Document/components/SizeWarning.tsx: Read -app/scenes/Document/index.tsx: Read -app/scenes/DocumentDelete.tsx: Read -app/scenes/DocumentMove.tsx: Read -app/scenes/DocumentNew.tsx: Read -app/scenes/DocumentPermanentDelete.tsx: Read -app/scenes/DocumentPublish.tsx: Read -app/scenes/Drafts.tsx: Read -app/scenes/Errors: ReadDir -app/scenes/Errors/Error402.tsx: Read -app/scenes/Errors/Error403.tsx: Read -app/scenes/Errors/Error404.tsx: Read -app/scenes/Errors/ErrorOffline.tsx: Read -app/scenes/Errors/ErrorSuspended.tsx: Read -app/scenes/Errors/ErrorUnknown.tsx: Read -app/scenes/Home.tsx: Read -app/scenes/Invite.tsx: Read -app/scenes/KeyboardShortcuts.tsx: Read -app/scenes/Login: ReadDir -app/scenes/Login/Login.tsx: Read -app/scenes/Login/OAuthAuthorize.tsx: Read -app/scenes/Login/OAuthScopeHelper.ts: Read -app/scenes/Login/components: ReadDir -app/scenes/Login/components/AuthenticationProvider.tsx: Read -app/scenes/Login/components/BackButton.tsx: Read -app/scenes/Login/components/Background.tsx: Read -app/scenes/Login/components/Centered.tsx: Read -app/scenes/Login/components/ConnectHeader.tsx: Read -app/scenes/Login/components/LoginDialog.tsx: Read -app/scenes/Login/components/Notices.tsx: Read -app/scenes/Login/components/TeamSwitcher.tsx: Read -app/scenes/Login/components/WorkspaceSetup.tsx: Read -app/scenes/Login/index.ts: Read -app/scenes/Login/urls.ts: Read -app/scenes/Logout.tsx: Read -app/scenes/Search: ReadDir -app/scenes/Search/Search.tsx: Read -app/scenes/Search/components: ReadDir -app/scenes/Search/components/CollectionFilter.tsx: Read -app/scenes/Search/components/DateFilter.tsx: Read -app/scenes/Search/components/DocumentFilter.tsx: Read -app/scenes/Search/components/DocumentTypeFilter.tsx: Read -app/scenes/Search/components/RecentSearchListItem.tsx: Read -app/scenes/Search/components/RecentSearches.tsx: Read -app/scenes/Search/components/SearchInput.tsx: Read -app/scenes/Search/components/UserFilter.tsx: Read -app/scenes/Search/index.ts: Read -app/scenes/Settings: ReadDir -app/scenes/Settings/APIAndApps.tsx: Read -app/scenes/Settings/ApiKeys.tsx: Read -app/scenes/Settings/Application.tsx: Read -app/scenes/Settings/Applications.tsx: Read -app/scenes/Settings/Details.tsx: Read -app/scenes/Settings/Export.tsx: Read -app/scenes/Settings/Features.tsx: Read -app/scenes/Settings/Groups.tsx: Read -app/scenes/Settings/Import.tsx: Read -app/scenes/Settings/Integrations.tsx: Read -app/scenes/Settings/Members.tsx: Read -app/scenes/Settings/Notifications.tsx: Read -app/scenes/Settings/Preferences.tsx: Read -app/scenes/Settings/Profile.tsx: Read -app/scenes/Settings/Security.tsx: Read -app/scenes/Settings/Shares.tsx: Read -app/scenes/Settings/Templates.tsx: Read -app/scenes/Settings/components: ReadDir -app/scenes/Settings/components/ActionRow.tsx: Read -app/scenes/Settings/components/ApiKeyListItem.tsx: Read -app/scenes/Settings/components/ApiKeyRevokeDialog.tsx: Read -app/scenes/Settings/components/ConnectedButton.tsx: Read -app/scenes/Settings/components/CopyButton.tsx: Read -app/scenes/Settings/components/DomainManagement.tsx: Read -app/scenes/Settings/components/DropToImport.tsx: Read -app/scenes/Settings/components/FileOperationListItem.tsx: Read -app/scenes/Settings/components/GroupDialogs.tsx: Read -app/scenes/Settings/components/GroupsTable.tsx: Read -app/scenes/Settings/components/HelpDisclosure.tsx: Read -app/scenes/Settings/components/ImageInput.tsx: Read -app/scenes/Settings/components/ImageUpload.tsx: Read -app/scenes/Settings/components/ImportJSONDialog.tsx: Read -app/scenes/Settings/components/ImportListItem.tsx: Read -app/scenes/Settings/components/ImportMarkdownDialog.tsx: Read -app/scenes/Settings/components/IntegrationCard.tsx: Read -app/scenes/Settings/components/IntegrationScene.tsx: Read -app/scenes/Settings/components/MembersTable.tsx: Read -app/scenes/Settings/components/OAuthAuthenticationListItem.tsx: Read -app/scenes/Settings/components/OAuthClientDeleteDialog.tsx: Read -app/scenes/Settings/components/OAuthClientListItem.tsx: Read -app/scenes/Settings/components/SettingRow.tsx: Read -app/scenes/Settings/components/SharesTable.tsx: Read -app/scenes/Settings/components/StickyFilters.tsx: Read -app/scenes/Settings/components/UserRoleFilter.tsx: Read -app/scenes/Settings/components/UserStatusFilter.tsx: Read -app/scenes/Shared: ReadDir -app/scenes/Shared/Collection.tsx: Read -app/scenes/Shared/Document.tsx: Read -app/scenes/Shared/index.tsx: Read -app/scenes/TeamDelete.tsx: Read -app/scenes/TeamNew.tsx: Read -app/scenes/Trash: ReadDir -app/scenes/Trash/components: ReadDir -app/scenes/Trash/components/DeleteDocumentsInTrash.tsx: Read -app/scenes/Trash/index.tsx: Read -app/scenes/UserDelete.tsx: Read -app/stores: ReadDir -app/stores/ApiKeysStore.ts: Read -app/stores/AuthStore.ts: Read -app/stores/AuthenticationProvidersStore.ts: Read -app/stores/CollectionsStore.ts: Read -app/stores/CommentsStore.ts: Read -app/stores/DialogsStore.ts: Read -app/stores/DocumentPresenceStore.ts: Read -app/stores/DocumentsStore.ts: Read -app/stores/EventsStore.ts: Read -app/stores/FileOperationsStore.ts: Read -app/stores/GroupMembershipsStore.ts: Read -app/stores/GroupUsersStore.ts: Read -app/stores/GroupsStore.ts: Read -app/stores/ImportsStore.ts: Read -app/stores/IntegrationsStore.ts: Read -app/stores/MembershipsStore.ts: Read -app/stores/NotificationsStore.ts: Read -app/stores/OAuthAuthenticationsStore.ts: Read -app/stores/OAuthClientsStore.ts: Read -app/stores/PinsStore.ts: Read -app/stores/PoliciesStore.ts: Read -app/stores/RevisionsStore.ts: Read -app/stores/RootStore.ts: Read -app/stores/SearchesStore.ts: Read -app/stores/SharesStore.ts: Read -app/stores/StarsStore.ts: Read -app/stores/SubscriptionsStore.ts: Read -app/stores/UiStore.ts: Read -app/stores/UnfurlsStore.ts: Read -app/stores/UserMembershipsStore.ts: Read -app/stores/UsersStore.ts: Read -app/stores/ViewsStore.ts: Read -app/stores/WebhookSubscriptionStore.ts: Read -app/stores/base: ReadDir -app/stores/base/Store.ts: Read -app/stores/index.ts: Read -app/styles: ReadDir -app/styles/animations.ts: Read -app/styles/index.ts: Read -app/test: ReadDir -app/test/setup.ts: Read -app/test/support.ts: Read -app/types.ts: Read -app/typings: ReadDir -app/utils: ReadDir -app/utils/Analytics.ts: Read -app/utils/ApiClient.ts: Read -app/utils/Desktop.ts: Read -app/utils/FeatureFlags.ts: Read -app/utils/Logger.ts: Read -app/utils/PluginManager.ts: Read -app/utils/__mocks__: ReadDir -app/utils/__mocks__/ApiClient.ts: Read -app/utils/compressImage.ts: Read -app/utils/date.ts: Read -app/utils/developer.ts: Read -app/utils/download.ts: Read -app/utils/emoji.ts: Read -app/utils/errors.ts: Read -app/utils/files.test.ts: Read -app/utils/files.ts: Read -app/utils/history.ts: Read -app/utils/i18n.test.ts: Read -app/utils/i18n.ts: Read -app/utils/isCloudHosted.ts: Read -app/utils/isTextInput.ts: Read -app/utils/language.ts: Read -app/utils/lazyWithRetry.ts: Read -app/utils/mention.ts: Read -app/utils/motion.ts: Read -app/utils/pageVisibility.ts: Read -app/utils/polyfills.ts: Read -app/utils/routeHelpers.test.ts: Read -app/utils/routeHelpers.ts: Read -app/utils/sentry.ts: Read -app/utils/tree.ts: Read -app/utils/urls.test.ts: Read -app/utils/urls.ts: Read -app/utils/viewTransition.ts: Read -node_modules/.bin: ReadDir -node_modules/.bin/oxlint: Read -node_modules/@oxlint/linux-arm64-gnu/oxlint: Read -node_modules/@oxlint/linux-arm64-gnu/package.json: Read -node_modules/oxlint/bin/oxlint: Read -node_modules/oxlint/bin/package.json: Read -node_modules/oxlint/package.json: Read -package.json: Read -plugins: ReadDir -plugins/azure: ReadDir -plugins/azure/client: ReadDir -plugins/azure/client/Icon.tsx: Read -plugins/azure/client/index.tsx: Read -plugins/azure/server: ReadDir -plugins/azure/server/auth: ReadDir -plugins/azure/server/auth/azure.ts: Read -plugins/azure/server/azure.ts: Read -plugins/azure/server/env.ts: Read -plugins/azure/server/index.ts: Read -plugins/discord: ReadDir -plugins/discord/client: ReadDir -plugins/discord/client/Icon.tsx: Read -plugins/discord/client/index.tsx: Read -plugins/discord/server: ReadDir -plugins/discord/server/auth: ReadDir -plugins/discord/server/auth/discord.ts: Read -plugins/discord/server/discord.ts: Read -plugins/discord/server/env.ts: Read -plugins/discord/server/errors.ts: Read -plugins/discord/server/index.ts: Read -plugins/email: ReadDir -plugins/email/server: ReadDir -plugins/email/server/auth: ReadDir -plugins/email/server/auth/email.test.ts: Read -plugins/email/server/auth/email.ts: Read -plugins/email/server/auth/schema.ts: Read -plugins/email/server/index.ts: Read -plugins/github: ReadDir -plugins/github/client: ReadDir -plugins/github/client/Icon.tsx: Read -plugins/github/client/Settings.tsx: Read -plugins/github/client/components: ReadDir -plugins/github/client/components/GitHubButton.tsx: Read -plugins/github/client/index.tsx: Read -plugins/github/server: ReadDir -plugins/github/server/GitHubIssueProvider.ts: Read -plugins/github/server/api: ReadDir -plugins/github/server/api/github.ts: Read -plugins/github/server/api/schema.ts: Read -plugins/github/server/env.ts: Read -plugins/github/server/github.ts: Read -plugins/github/server/index.ts: Read -plugins/github/server/tasks: ReadDir -plugins/github/server/tasks/GitHubWebhookTask.ts: Read -plugins/github/server/uninstall.ts: Read -plugins/github/shared: ReadDir -plugins/github/shared/GitHubUtils.ts: Read -plugins/google: ReadDir -plugins/google/client: ReadDir -plugins/google/client/Icon.tsx: Read -plugins/google/client/index.tsx: Read -plugins/google/server: ReadDir -plugins/google/server/auth: ReadDir -plugins/google/server/auth/google.ts: Read -plugins/google/server/env.ts: Read -plugins/google/server/google.ts: Read -plugins/google/server/index.ts: Read -plugins/googleanalytics: ReadDir -plugins/googleanalytics/client: ReadDir -plugins/googleanalytics/client/Icon.tsx: Read -plugins/googleanalytics/client/Settings.tsx: Read -plugins/googleanalytics/client/index.tsx: Read -plugins/iframely: ReadDir -plugins/iframely/server: ReadDir -plugins/iframely/server/env.ts: Read -plugins/iframely/server/iframely.ts: Read -plugins/iframely/server/index.ts: Read -plugins/linear: ReadDir -plugins/linear/client: ReadDir -plugins/linear/client/Icon.tsx: Read -plugins/linear/client/Settings.tsx: Read -plugins/linear/client/components: ReadDir -plugins/linear/client/components/LinearButton.tsx: Read -plugins/linear/client/index.tsx: Read -plugins/linear/server: ReadDir -plugins/linear/server/api: ReadDir -plugins/linear/server/api/linear.ts: Read -plugins/linear/server/api/schema.ts: Read -plugins/linear/server/env.ts: Read -plugins/linear/server/index.ts: Read -plugins/linear/server/linear.ts: Read -plugins/linear/server/tasks: ReadDir -plugins/linear/server/tasks/UploadLinearWorkspaceLogoTask.ts: Read -plugins/linear/server/uninstall.ts: Read -plugins/linear/shared: ReadDir -plugins/linear/shared/LinearUtils.ts: Read -plugins/matomo: ReadDir -plugins/matomo/client: ReadDir -plugins/matomo/client/Icon.tsx: Read -plugins/matomo/client/Settings.tsx: Read -plugins/matomo/client/index.tsx: Read -plugins/notion: ReadDir -plugins/notion/client: ReadDir -plugins/notion/client/Imports.tsx: Read -plugins/notion/client/components: ReadDir -plugins/notion/client/components/ImportDialog.tsx: Read -plugins/notion/client/index.tsx: Read -plugins/notion/server: ReadDir -plugins/notion/server/api: ReadDir -plugins/notion/server/api/notion.ts: Read -plugins/notion/server/api/schema.ts: Read -plugins/notion/server/env.ts: Read -plugins/notion/server/index.ts: Read -plugins/notion/server/notion.ts: Read -plugins/notion/server/processors: ReadDir -plugins/notion/server/processors/NotionImportsProcessor.ts: Read -plugins/notion/server/tasks: ReadDir -plugins/notion/server/tasks/NotionAPIImportTask.ts: Read -plugins/notion/server/utils: ReadDir -plugins/notion/server/utils/NotionConverter.test.ts: Read -plugins/notion/server/utils/NotionConverter.ts: Read -plugins/notion/server/utils/__snapshots__: ReadDir -plugins/notion/shared: ReadDir -plugins/notion/shared/NotionUtils.ts: Read -plugins/notion/shared/types.ts: Read -plugins/oidc: ReadDir -plugins/oidc/server: ReadDir -plugins/oidc/server/auth: ReadDir -plugins/oidc/server/auth/OIDCStrategy.ts: Read -plugins/oidc/server/auth/oidc.test.ts: Read -plugins/oidc/server/auth/oidc.ts: Read -plugins/oidc/server/auth/oidcRouter.ts: Read -plugins/oidc/server/env.ts: Read -plugins/oidc/server/index.ts: Read -plugins/oidc/server/oidc.ts: Read -plugins/oidc/server/oidcDiscovery.test.ts: Read -plugins/oidc/server/oidcDiscovery.ts: Read -plugins/slack: ReadDir -plugins/slack/client: ReadDir -plugins/slack/client/Icon.tsx: Read -plugins/slack/client/Settings.tsx: Read -plugins/slack/client/components: ReadDir -plugins/slack/client/components/SlackButton.tsx: Read -plugins/slack/client/components/SlackListItem.tsx: Read -plugins/slack/client/index.tsx: Read -plugins/slack/server: ReadDir -plugins/slack/server/api: ReadDir -plugins/slack/server/api/hooks.test.ts: Read -plugins/slack/server/api/hooks.ts: Read -plugins/slack/server/api/schema.ts: Read -plugins/slack/server/auth: ReadDir -plugins/slack/server/auth/schema.ts: Read -plugins/slack/server/auth/slack.test.ts: Read -plugins/slack/server/auth/slack.ts: Read -plugins/slack/server/env.ts: Read -plugins/slack/server/index.ts: Read -plugins/slack/server/presenters: ReadDir -plugins/slack/server/presenters/messageAttachment.ts: Read -plugins/slack/server/presenters/userNotLinkedBlocks.ts: Read -plugins/slack/server/processors: ReadDir -plugins/slack/server/processors/SlackProcessor.ts: Read -plugins/slack/server/slack.ts: Read -plugins/slack/shared: ReadDir -plugins/slack/shared/SlackUtils.ts: Read -plugins/storage: ReadDir -plugins/storage/server: ReadDir -plugins/storage/server/api: ReadDir -plugins/storage/server/api/files.test.ts: Read -plugins/storage/server/api/files.ts: Read -plugins/storage/server/api/schema.ts: Read -plugins/storage/server/index.ts: Read -plugins/storage/server/test: ReadDir -plugins/storage/server/test/fixtures: ReadDir -plugins/umami: ReadDir -plugins/umami/client: ReadDir -plugins/umami/client/Icon.tsx: Read -plugins/umami/client/Settings.tsx: Read -plugins/umami/client/index.tsx: Read -plugins/webhooks: ReadDir -plugins/webhooks/client: ReadDir -plugins/webhooks/client/Icon.tsx: Read -plugins/webhooks/client/Settings.tsx: Read -plugins/webhooks/client/components: ReadDir -plugins/webhooks/client/components/WebhookSubscriptionDeleteDialog.tsx: Read -plugins/webhooks/client/components/WebhookSubscriptionEdit.tsx: Read -plugins/webhooks/client/components/WebhookSubscriptionForm.tsx: Read -plugins/webhooks/client/components/WebhookSubscriptionListItem.tsx: Read -plugins/webhooks/client/components/WebhookSubscriptionNew.tsx: Read -plugins/webhooks/client/index.tsx: Read -plugins/webhooks/server: ReadDir -plugins/webhooks/server/api: ReadDir -plugins/webhooks/server/api/schema.ts: Read -plugins/webhooks/server/api/webhookSubscriptions.test.ts: Read -plugins/webhooks/server/api/webhookSubscriptions.ts: Read -plugins/webhooks/server/index.ts: Read -plugins/webhooks/server/presenters: ReadDir -plugins/webhooks/server/presenters/webhook.ts: Read -plugins/webhooks/server/presenters/webhookSubscription.ts: Read -plugins/webhooks/server/processors: ReadDir -plugins/webhooks/server/processors/WebhookProcessor.test.ts: Read -plugins/webhooks/server/processors/WebhookProcessor.ts: Read -plugins/webhooks/server/tasks: ReadDir -plugins/webhooks/server/tasks/CleanupWebhookDeliveriesTask.test.ts: Read -plugins/webhooks/server/tasks/CleanupWebhookDeliveriesTask.ts: Read -plugins/webhooks/server/tasks/DeliverWebhookTask.test.ts: Read -plugins/webhooks/server/tasks/DeliverWebhookTask.ts: Read -plugins/zapier: ReadDir -plugins/zapier/client: ReadDir -plugins/zapier/client/Icon.tsx: Read -plugins/zapier/client/Settings.tsx: Read -plugins/zapier/client/index.tsx: Read -server: ReadDir -server/../.oxlintrc.json: Read -server/.oxlintrc.json: Read -server/__mocks__: ReadDir -server/__mocks__/bull.ts: Read -server/__mocks__/dd-trace.ts: Read -server/__mocks__/events.ts: Read -server/__mocks__/fetch-with-proxy.ts: Read -server/collaboration: ReadDir -server/collaboration/AuthenticationExtension.ts: Read -server/collaboration/ConnectionLimitExtension.test.ts: Read -server/collaboration/ConnectionLimitExtension.ts: Read -server/collaboration/EditorVersionExtension.ts: Read -server/collaboration/LoggerExtension.ts: Read -server/collaboration/MetricsExtension.ts: Read -server/collaboration/PersistenceExtension.ts: Read -server/collaboration/ViewsExtension.ts: Read -server/collaboration/types.ts: Read -server/commands: ReadDir -server/commands/accountProvisioner.test.ts: Read -server/commands/accountProvisioner.ts: Read -server/commands/attachmentCreator.ts: Read -server/commands/collectionExporter.ts: Read -server/commands/documentCollaborativeUpdater.ts: Read -server/commands/documentCreator.test.ts: Read -server/commands/documentCreator.ts: Read -server/commands/documentDuplicator.test.ts: Read -server/commands/documentDuplicator.ts: Read -server/commands/documentImporter.test.ts: Read -server/commands/documentImporter.ts: Read -server/commands/documentLoader.ts: Read -server/commands/documentMover.test.ts: Read -server/commands/documentMover.ts: Read -server/commands/documentPermanentDeleter.test.ts: Read -server/commands/documentPermanentDeleter.ts: Read -server/commands/documentUpdater.test.ts: Read -server/commands/documentUpdater.ts: Read -server/commands/pinCreator.test.ts: Read -server/commands/pinCreator.ts: Read -server/commands/revisionCreator.test.ts: Read -server/commands/revisionCreator.ts: Read -server/commands/shareLoader.test.ts: Read -server/commands/shareLoader.ts: Read -server/commands/starCreator.test.ts: Read -server/commands/starCreator.ts: Read -server/commands/subscriptionCreator.test.ts: Read -server/commands/subscriptionCreator.ts: Read -server/commands/teamCreator.ts: Read -server/commands/teamPermanentDeleter.test.ts: Read -server/commands/teamPermanentDeleter.ts: Read -server/commands/teamProvisioner.test.ts: Read -server/commands/teamProvisioner.ts: Read -server/commands/teamUpdater.ts: Read -server/commands/userInviter.test.ts: Read -server/commands/userInviter.ts: Read -server/commands/userProvisioner.test.ts: Read -server/commands/userProvisioner.ts: Read -server/config: ReadDir -server/config/certs: ReadDir -server/context.ts: Read -server/editor: ReadDir -server/editor/index.test.ts: Read -server/editor/index.ts: Read -server/emails: ReadDir -server/emails/mailer.tsx: Read -server/emails/templates: ReadDir -server/emails/templates/BaseEmail.tsx: Read -server/emails/templates/CollectionCreatedEmail.tsx: Read -server/emails/templates/CollectionSharedEmail.tsx: Read -server/emails/templates/CommentCreatedEmail.tsx: Read -server/emails/templates/CommentMentionedEmail.tsx: Read -server/emails/templates/CommentResolvedEmail.tsx: Read -server/emails/templates/ConfirmTeamDeleteEmail.tsx: Read -server/emails/templates/ConfirmUpdateEmail.tsx: Read -server/emails/templates/ConfirmUserDeleteEmail.tsx: Read -server/emails/templates/DocumentMentionedEmail.tsx: Read -server/emails/templates/DocumentPublishedOrUpdatedEmail.tsx: Read -server/emails/templates/DocumentSharedEmail.tsx: Read -server/emails/templates/ExportFailureEmail.tsx: Read -server/emails/templates/ExportSuccessEmail.tsx: Read -server/emails/templates/InviteAcceptedEmail.tsx: Read -server/emails/templates/InviteEmail.tsx: Read -server/emails/templates/InviteReminderEmail.tsx: Read -server/emails/templates/SigninEmail.tsx: Read -server/emails/templates/WebhookDisabledEmail.tsx: Read -server/emails/templates/WelcomeEmail.tsx: Read -server/emails/templates/components: ReadDir -server/emails/templates/components/Body.tsx: Read -server/emails/templates/components/Button.tsx: Read -server/emails/templates/components/CopyableCode.tsx: Read -server/emails/templates/components/Diff.tsx: Read -server/emails/templates/components/EmailLayout.tsx: Read -server/emails/templates/components/EmptySpace.tsx: Read -server/emails/templates/components/Footer.tsx: Read -server/emails/templates/components/Header.tsx: Read -server/emails/templates/components/Heading.tsx: Read -server/emails/templates/index.ts: Read -server/env.ts: Read -server/errors.ts: Read -server/index.ts: Read -server/logging: ReadDir -server/logging/Logger.ts: Read -server/logging/Metrics.ts: Read -server/logging/sentry.ts: Read -server/logging/tracer.ts: Read -server/logging/tracing.ts: Read -server/middlewares: ReadDir -server/middlewares/apexAuthRedirect.ts: Read -server/middlewares/apexRedirect.ts: Read -server/middlewares/authentication.test.ts: Read -server/middlewares/authentication.ts: Read -server/middlewares/coaleseBody.ts: Read -server/middlewares/csp.ts: Read -server/middlewares/feature.ts: Read -server/middlewares/multipart.ts: Read -server/middlewares/passport.ts: Read -server/middlewares/rateLimiter.ts: Read -server/middlewares/requestTracer.ts: Read -server/middlewares/shareDomains.ts: Read -server/middlewares/transaction.ts: Read -server/middlewares/validate.ts: Read -server/middlewares/validateWebhook.ts: Read -server/migrations: ReadDir -server/models: ReadDir -server/models/ApiKey.test.ts: Read -server/models/ApiKey.ts: Read -server/models/Attachment.ts: Read -server/models/AuthenticationProvider.ts: Read -server/models/Collection.test.ts: Read -server/models/Collection.ts: Read -server/models/Comment.ts: Read -server/models/Document.test.ts: Read -server/models/Document.ts: Read -server/models/Event.ts: Read -server/models/FileOperation.ts: Read -server/models/Group.ts: Read -server/models/GroupMembership.test.ts: Read -server/models/GroupMembership.ts: Read -server/models/GroupUser.ts: Read -server/models/Import.ts: Read -server/models/ImportTask.ts: Read -server/models/Integration.ts: Read -server/models/IntegrationAuthentication.ts: Read -server/models/Notification.test.ts: Read -server/models/Notification.ts: Read -server/models/Pin.ts: Read -server/models/Reaction.ts: Read -server/models/Relationship.ts: Read -server/models/Revision.test.ts: Read -server/models/Revision.ts: Read -server/models/SearchQuery.ts: Read -server/models/Share.ts: Read -server/models/Star.ts: Read -server/models/Subscription.ts: Read -server/models/Team.test.ts: Read -server/models/Team.ts: Read -server/models/TeamDomain.test.ts: Read -server/models/TeamDomain.ts: Read -server/models/User.test.ts: Read -server/models/User.ts: Read -server/models/UserAuthentication.ts: Read -server/models/UserMembership.test.ts: Read -server/models/UserMembership.ts: Read -server/models/View.test.ts: Read -server/models/View.ts: Read -server/models/WebhookDelivery.ts: Read -server/models/WebhookSubscription.ts: Read -server/models/base: ReadDir -server/models/base/ArchivableModel.ts: Read -server/models/base/IdModel.ts: Read -server/models/base/Model.test.ts: Read -server/models/base/Model.ts: Read -server/models/base/ParanoidModel.ts: Read -server/models/decorators: ReadDir -server/models/decorators/Changeset.ts: Read -server/models/decorators/CounterCache.ts: Read -server/models/decorators/Deprecated.ts: Read -server/models/decorators/Encrypted.ts: Read -server/models/decorators/Fix.ts: Read -server/models/helpers: ReadDir -server/models/helpers/AttachmentHelper.test.ts: Read -server/models/helpers/AttachmentHelper.ts: Read -server/models/helpers/AuthenticationHelper.test.ts: Read -server/models/helpers/AuthenticationHelper.ts: Read -server/models/helpers/DocumentHelper.test.ts: Read -server/models/helpers/DocumentHelper.tsx: Read -server/models/helpers/HTMLHelper.test.ts: Read -server/models/helpers/HTMLHelper.ts: Read -server/models/helpers/NotificationHelper.test.ts: Read -server/models/helpers/NotificationHelper.ts: Read -server/models/helpers/NotificationSettingsHelper.ts: Read -server/models/helpers/ProseMirrorHelper.test.ts: Read -server/models/helpers/ProsemirrorHelper.tsx: Read -server/models/helpers/SearchHelper.test.ts: Read -server/models/helpers/SearchHelper.ts: Read -server/models/helpers/SubscriptionHelper.test.ts: Read -server/models/helpers/SubscriptionHelper.ts: Read -server/models/helpers/TextHelper.ts: Read -server/models/index.ts: Read -server/models/oauth: ReadDir -server/models/oauth/OAuthAuthentication.ts: Read -server/models/oauth/OAuthAuthorizationCode.ts: Read -server/models/oauth/OAuthClient.ts: Read -server/models/validators: ReadDir -server/models/validators/IsFQDN.ts: Read -server/models/validators/IsHexColor.ts: Read -server/models/validators/IsUrlOrRelativePath.ts: Read -server/models/validators/Length.ts: Read -server/models/validators/NotContainsUrl.ts: Read -server/models/validators/TextLength.ts: Read -server/onboarding: ReadDir -server/onerror.ts: Read -server/policies: ReadDir -server/policies/apiKey.ts: Read -server/policies/attachment.ts: Read -server/policies/authenticationProvider.ts: Read -server/policies/cancan.ts: Read -server/policies/collection.test.ts: Read -server/policies/collection.ts: Read -server/policies/comment.ts: Read -server/policies/document.test.ts: Read -server/policies/document.ts: Read -server/policies/fileOperation.ts: Read -server/policies/group.ts: Read -server/policies/import.ts: Read -server/policies/index.test.ts: Read -server/policies/index.ts: Read -server/policies/integration.ts: Read -server/policies/notification.ts: Read -server/policies/oauthAuthentication.ts: Read -server/policies/oauthClient.ts: Read -server/policies/pins.ts: Read -server/policies/reaction.ts: Read -server/policies/revision.ts: Read -server/policies/searchQuery.ts: Read -server/policies/share.ts: Read -server/policies/star.ts: Read -server/policies/subscription.ts: Read -server/policies/team.test.ts: Read -server/policies/team.ts: Read -server/policies/user.ts: Read -server/policies/userMembership.ts: Read -server/policies/utils.ts: Read -server/policies/webhookSubscription.ts: Read -server/presenters: ReadDir -server/presenters/__snapshots__: ReadDir -server/presenters/apiKey.ts: Read -server/presenters/attachment.ts: Read -server/presenters/authenticationProvider.ts: Read -server/presenters/availableTeam.ts: Read -server/presenters/collection.ts: Read -server/presenters/comment.ts: Read -server/presenters/document.ts: Read -server/presenters/env.ts: Read -server/presenters/event.ts: Read -server/presenters/fileOperation.ts: Read -server/presenters/group.ts: Read -server/presenters/groupMembership.ts: Read -server/presenters/groupUser.ts: Read -server/presenters/import.ts: Read -server/presenters/index.ts: Read -server/presenters/integration.ts: Read -server/presenters/membership.ts: Read -server/presenters/notification.ts: Read -server/presenters/oauthAuthentication.ts: Read -server/presenters/oauthClient.ts: Read -server/presenters/pin.ts: Read -server/presenters/policy.ts: Read -server/presenters/providerConfig.ts: Read -server/presenters/publicTeam.ts: Read -server/presenters/reaction.ts: Read -server/presenters/revision.ts: Read -server/presenters/searchQuery.ts: Read -server/presenters/share.ts: Read -server/presenters/star.ts: Read -server/presenters/subscription.ts: Read -server/presenters/team.ts: Read -server/presenters/unfurl.ts: Read -server/presenters/user.test.ts: Read -server/presenters/user.ts: Read -server/presenters/view.ts: Read -server/queues: ReadDir -server/queues/HealthMonitor.ts: Read -server/queues/index.ts: Read -server/queues/processors: ReadDir -server/queues/processors/ApiKeyCleanupProcessor.ts: Read -server/queues/processors/AvatarProcessor.ts: Read -server/queues/processors/BacklinksProcessor.test.ts: Read -server/queues/processors/BacklinksProcessor.ts: Read -server/queues/processors/BaseProcessor.ts: Read -server/queues/processors/CollectionsProcessor.ts: Read -server/queues/processors/DebounceProcessor.ts: Read -server/queues/processors/DocumentMovedProcessor.ts: Read -server/queues/processors/DocumentSubscriptionProcessor.ts: Read -server/queues/processors/EmailsProcessor.ts: Read -server/queues/processors/FileOperationCreatedProcessor.ts: Read -server/queues/processors/FileOperationDeletedProcessor.ts: Read -server/queues/processors/ImportsProcessor.ts: Read -server/queues/processors/IntegrationCreatedProcessor.ts: Read -server/queues/processors/IntegrationDeletedProcessor.ts: Read -server/queues/processors/NotificationsProcessor.ts: Read -server/queues/processors/OAuthClientDeletedProcessor.ts: Read -server/queues/processors/OAuthClientUnpublishedProcessor.ts: Read -server/queues/processors/RevisionsProcessor.test.ts: Read -server/queues/processors/RevisionsProcessor.ts: Read -server/queues/processors/UserDeletedProcessor.test.ts: Read -server/queues/processors/UserDeletedProcessor.ts: Read -server/queues/processors/UserDemotedProcessor.ts: Read -server/queues/processors/UserSuspendedProcessor.ts: Read -server/queues/processors/WebsocketsProcessor.ts: Read -server/queues/processors/index.ts: Read -server/queues/queue.ts: Read -server/queues/tasks: ReadDir -server/queues/tasks/APIImportTask.ts: Read -server/queues/tasks/BaseTask.ts: Read -server/queues/tasks/CacheIssueSourcesTask.ts: Read -server/queues/tasks/CleanupDeletedDocumentsTask.test.ts: Read -server/queues/tasks/CleanupDeletedDocumentsTask.ts: Read -server/queues/tasks/CleanupDeletedTeamTask.ts: Read -server/queues/tasks/CleanupDeletedTeamsTask.ts: Read -server/queues/tasks/CleanupDemotedUserTask.test.ts: Read -server/queues/tasks/CleanupDemotedUserTask.ts: Read -server/queues/tasks/CleanupExpiredAttachmentsTask.ts: Read -server/queues/tasks/CleanupExpiredFileOperationsTask.test.ts: Read -server/queues/tasks/CleanupExpiredFileOperationsTask.ts: Read -server/queues/tasks/CleanupOAuthAuthorizationCodeTask.test.ts: Read -server/queues/tasks/CleanupOAuthAuthorizationCodeTask.ts: Read -server/queues/tasks/CleanupOldEventsTask.ts: Read -server/queues/tasks/CleanupOldImportsTask.ts: Read -server/queues/tasks/CleanupOldNotificationsTask.ts: Read -server/queues/tasks/CollectionAddUserNotificationsTask.ts: Read -server/queues/tasks/CollectionCreatedNotificationsTask.ts: Read -server/queues/tasks/CollectionSubscriptionRemoveUserTask.ts: Read -server/queues/tasks/CommentCreatedNotificationsTask.ts: Read -server/queues/tasks/CommentUpdatedNotificationsTask.ts: Read -server/queues/tasks/DeleteAttachmentTask.ts: Read -server/queues/tasks/DetachDraftsFromCollectionTask.test.ts: Read -server/queues/tasks/DetachDraftsFromCollectionTask.ts: Read -server/queues/tasks/DocumentAddGroupNotificationsTask.ts: Read -server/queues/tasks/DocumentAddUserNotificationsTask.ts: Read -server/queues/tasks/DocumentImportTask.ts: Read -server/queues/tasks/DocumentPublishedNotificationsTask.test.ts: Read -server/queues/tasks/DocumentPublishedNotificationsTask.ts: Read -server/queues/tasks/DocumentSubscriptionRemoveUserTask.ts: Read -server/queues/tasks/DocumentUpdateTextTask.ts: Read -server/queues/tasks/EmailTask.ts: Read -server/queues/tasks/EmptyTrashTask.ts: Read -server/queues/tasks/ErrorTimedOutFileOperationsTask.test.ts: Read -server/queues/tasks/ErrorTimedOutFileOperationsTask.ts: Read -server/queues/tasks/ErrorTimedOutImportsTask.ts: Read -server/queues/tasks/ExportDocumentTreeTask.ts: Read -server/queues/tasks/ExportHTMLZipTask.ts: Read -server/queues/tasks/ExportJSONTask.ts: Read -server/queues/tasks/ExportMarkdownZipTask.ts: Read -server/queues/tasks/ExportTask.ts: Read -server/queues/tasks/ImportJSONTask.test.ts: Read -server/queues/tasks/ImportJSONTask.ts: Read -server/queues/tasks/ImportMarkdownZipTask.test.ts: Read -server/queues/tasks/ImportMarkdownZipTask.ts: Read -server/queues/tasks/ImportTask.ts: Read -server/queues/tasks/InviteReminderTask.test.ts: Read -server/queues/tasks/InviteReminderTask.ts: Read -server/queues/tasks/RevisionCreatedNotificationsTask.test.ts: Read -server/queues/tasks/RevisionCreatedNotificationsTask.ts: Read -server/queues/tasks/UpdateTeamAttachmentsSizeTask.ts: Read -server/queues/tasks/UpdateTeamsAttachmentsSizeTask.ts: Read -server/queues/tasks/UploadAttachmentFromUrlTask.ts: Read -server/queues/tasks/UploadAttachmentsForImportTask.ts: Read -server/queues/tasks/UploadTeamAvatarTask.ts: Read -server/queues/tasks/UploadUserAvatarTask.ts: Read -server/queues/tasks/ValidateSSOAccessTask.ts: Read -server/queues/tasks/index.ts: Read -server/routes: ReadDir -server/routes/api: ReadDir -server/routes/api/apiKeys: ReadDir -server/routes/api/apiKeys/apiKeys.test.ts: Read -server/routes/api/apiKeys/apiKeys.ts: Read -server/routes/api/apiKeys/index.ts: Read -server/routes/api/apiKeys/schema.ts: Read -server/routes/api/attachments: ReadDir -server/routes/api/attachments/attachments.test.ts: Read -server/routes/api/attachments/attachments.ts: Read -server/routes/api/attachments/index.ts: Read -server/routes/api/attachments/schema.ts: Read -server/routes/api/auth: ReadDir -server/routes/api/auth/auth.test.ts: Read -server/routes/api/auth/auth.ts: Read -server/routes/api/auth/index.ts: Read -server/routes/api/auth/schema.ts: Read -server/routes/api/authenticationProviders: ReadDir -server/routes/api/authenticationProviders/authenticationProviders.test.ts: Read -server/routes/api/authenticationProviders/authenticationProviders.ts: Read -server/routes/api/authenticationProviders/index.ts: Read -server/routes/api/authenticationProviders/schema.ts: Read -server/routes/api/collections: ReadDir -server/routes/api/collections/__snapshots__: ReadDir -server/routes/api/collections/collections.test.ts: Read -server/routes/api/collections/collections.ts: Read -server/routes/api/collections/index.ts: Read -server/routes/api/collections/schema.ts: Read -server/routes/api/comments: ReadDir -server/routes/api/comments/__snapshots__: ReadDir -server/routes/api/comments/comments.test.ts: Read -server/routes/api/comments/comments.ts: Read -server/routes/api/comments/index.ts: Read -server/routes/api/comments/schema.ts: Read -server/routes/api/cron: ReadDir -server/routes/api/cron/cron.test.ts: Read -server/routes/api/cron/cron.ts: Read -server/routes/api/cron/index.ts: Read -server/routes/api/cron/schema.ts: Read -server/routes/api/developer: ReadDir -server/routes/api/developer/developer.ts: Read -server/routes/api/developer/index.ts: Read -server/routes/api/developer/schema.ts: Read -server/routes/api/documents: ReadDir -server/routes/api/documents/__snapshots__: ReadDir -server/routes/api/documents/documents.test.ts: Read -server/routes/api/documents/documents.ts: Read -server/routes/api/documents/index.ts: Read -server/routes/api/documents/schema.ts: Read -server/routes/api/events: ReadDir -server/routes/api/events/__snapshots__: ReadDir -server/routes/api/events/events.test.ts: Read -server/routes/api/events/events.ts: Read -server/routes/api/events/index.ts: Read -server/routes/api/events/schema.ts: Read -server/routes/api/fileOperations: ReadDir -server/routes/api/fileOperations/fileOperations.test.ts: Read -server/routes/api/fileOperations/fileOperations.ts: Read -server/routes/api/fileOperations/index.ts: Read -server/routes/api/fileOperations/schema.ts: Read -server/routes/api/groupMemberships: ReadDir -server/routes/api/groupMemberships/groupMemberships.test.ts: Read -server/routes/api/groupMemberships/groupMemberships.ts: Read -server/routes/api/groupMemberships/index.ts: Read -server/routes/api/groupMemberships/schema.ts: Read -server/routes/api/groups: ReadDir -server/routes/api/groups/__snapshots__: ReadDir -server/routes/api/groups/groups.test.ts: Read -server/routes/api/groups/groups.ts: Read -server/routes/api/groups/index.ts: Read -server/routes/api/groups/schema.ts: Read -server/routes/api/imports: ReadDir -server/routes/api/imports/imports.test.ts: Read -server/routes/api/imports/imports.ts: Read -server/routes/api/imports/index.ts: Read -server/routes/api/imports/schema.ts: Read -server/routes/api/index.test.ts: Read -server/routes/api/index.ts: Read -server/routes/api/installation: ReadDir -server/routes/api/installation/index.ts: Read -server/routes/api/installation/installation.test.ts: Read -server/routes/api/installation/installation.ts: Read -server/routes/api/installation/schema.ts: Read -server/routes/api/integrations: ReadDir -server/routes/api/integrations/index.ts: Read -server/routes/api/integrations/integrations.test.ts: Read -server/routes/api/integrations/integrations.ts: Read -server/routes/api/integrations/schema.ts: Read -server/routes/api/middlewares: ReadDir -server/routes/api/middlewares/apiErrorHandler.ts: Read -server/routes/api/middlewares/apiResponse.ts: Read -server/routes/api/middlewares/editor.ts: Read -server/routes/api/middlewares/pagination.test.ts: Read -server/routes/api/middlewares/pagination.ts: Read -server/routes/api/notifications: ReadDir -server/routes/api/notifications/index.ts: Read -server/routes/api/notifications/notifications.test.ts: Read -server/routes/api/notifications/notifications.ts: Read -server/routes/api/notifications/schema.ts: Read -server/routes/api/oauthAuthentications: ReadDir -server/routes/api/oauthAuthentications/__snapshots__: ReadDir -server/routes/api/oauthAuthentications/index.ts: Read -server/routes/api/oauthAuthentications/oauthAuthentications.test.ts: Read -server/routes/api/oauthAuthentications/oauthAuthentications.ts: Read -server/routes/api/oauthAuthentications/schema.ts: Read -server/routes/api/oauthClients: ReadDir -server/routes/api/oauthClients/__snapshots__: ReadDir -server/routes/api/oauthClients/index.ts: Read -server/routes/api/oauthClients/oauthClients.test.ts: Read -server/routes/api/oauthClients/oauthClients.ts: Read -server/routes/api/oauthClients/schema.ts: Read -server/routes/api/pins: ReadDir -server/routes/api/pins/index.ts: Read -server/routes/api/pins/pins.test.ts: Read -server/routes/api/pins/pins.ts: Read -server/routes/api/pins/schema.ts: Read -server/routes/api/reactions: ReadDir -server/routes/api/reactions/__snapshots__: ReadDir -server/routes/api/reactions/index.ts: Read -server/routes/api/reactions/reactions.test.ts: Read -server/routes/api/reactions/reactions.ts: Read -server/routes/api/reactions/schema.ts: Read -server/routes/api/revisions: ReadDir -server/routes/api/revisions/index.ts: Read -server/routes/api/revisions/revisions.test.ts: Read -server/routes/api/revisions/revisions.ts: Read -server/routes/api/revisions/schema.ts: Read -server/routes/api/schema.ts: Read -server/routes/api/searches: ReadDir -server/routes/api/searches/index.ts: Read -server/routes/api/searches/schema.ts: Read -server/routes/api/searches/searches.test.ts: Read -server/routes/api/searches/searches.ts: Read -server/routes/api/shares: ReadDir -server/routes/api/shares/__snapshots__: ReadDir -server/routes/api/shares/index.ts: Read -server/routes/api/shares/schema.ts: Read -server/routes/api/shares/shares.test.ts: Read -server/routes/api/shares/shares.ts: Read -server/routes/api/stars: ReadDir -server/routes/api/stars/index.ts: Read -server/routes/api/stars/schema.ts: Read -server/routes/api/stars/stars.test.ts: Read -server/routes/api/stars/stars.ts: Read -server/routes/api/subscriptions: ReadDir -server/routes/api/subscriptions/index.ts: Read -server/routes/api/subscriptions/schema.ts: Read -server/routes/api/subscriptions/subscriptions.test.ts: Read -server/routes/api/subscriptions/subscriptions.ts: Read -server/routes/api/suggestions: ReadDir -server/routes/api/suggestions/index.ts: Read -server/routes/api/suggestions/schema.ts: Read -server/routes/api/suggestions/suggestions.ts: Read -server/routes/api/teams: ReadDir -server/routes/api/teams/index.ts: Read -server/routes/api/teams/schema.ts: Read -server/routes/api/teams/teams.test.ts: Read -server/routes/api/teams/teams.ts: Read -server/routes/api/urls: ReadDir -server/routes/api/urls/index.ts: Read -server/routes/api/urls/schema.ts: Read -server/routes/api/urls/urls.test.ts: Read -server/routes/api/urls/urls.ts: Read -server/routes/api/userMemberships: ReadDir -server/routes/api/userMemberships/index.ts: Read -server/routes/api/userMemberships/schema.ts: Read -server/routes/api/userMemberships/userMemberships.test.ts: Read -server/routes/api/userMemberships/userMemberships.ts: Read -server/routes/api/users: ReadDir -server/routes/api/users/__snapshots__: ReadDir -server/routes/api/users/index.ts: Read -server/routes/api/users/schema.ts: Read -server/routes/api/users/users.test.ts: Read -server/routes/api/users/users.ts: Read -server/routes/api/views: ReadDir -server/routes/api/views/__snapshots__: ReadDir -server/routes/api/views/index.ts: Read -server/routes/api/views/schema.ts: Read -server/routes/api/views/views.test.ts: Read -server/routes/api/views/views.ts: Read -server/routes/app.ts: Read -server/routes/auth: ReadDir -server/routes/auth/index.test.ts: Read -server/routes/auth/index.ts: Read -server/routes/embeds.ts: Read -server/routes/errors.test.ts: Read -server/routes/errors.ts: Read -server/routes/index.test.ts: Read -server/routes/index.ts: Read -server/routes/oauth: ReadDir -server/routes/oauth/index.test.ts: Read -server/routes/oauth/index.ts: Read -server/routes/oauth/middlewares: ReadDir -server/routes/oauth/middlewares/oauthErrorHandler.ts: Read -server/routes/oauth/schema.ts: Read -server/scripts: ReadDir -server/services: ReadDir -server/services/admin.ts: Read -server/services/collaboration.ts: Read -server/services/cron.ts: Read -server/services/index.ts: Read -server/services/web.ts: Read -server/services/websockets.ts: Read -server/services/worker.ts: Read -server/static: ReadDir -server/storage: ReadDir -server/storage/__mocks__: ReadDir -server/storage/__mocks__/redis.ts: Read -server/storage/database.ts: Read -server/storage/files: ReadDir -server/storage/files/BaseStorage.ts: Read -server/storage/files/LocalStorage.ts: Read -server/storage/files/S3Storage.ts: Read -server/storage/files/__mocks__: ReadDir -server/storage/files/__mocks__/index.ts: Read -server/storage/files/index.ts: Read -server/storage/redis.ts: Read -server/storage/vaults.ts: Read -server/test: ReadDir -server/test/TestServer.ts: Read -server/test/factories.ts: Read -server/test/fixtures: ReadDir -server/test/globalSetup.js: Read -server/test/globalTeardown.js: Read -server/test/setup.ts: Read -server/test/support.ts: Read -server/types.ts: Read -server/typings: ReadDir -server/utils: ReadDir -server/utils/BaseIssueProvider.ts: Read -server/utils/CacheHelper.ts: Read -server/utils/DocumentConverter.test.ts: Read -server/utils/DocumentConverter.ts: Read -server/utils/ImportHelper.test.ts: Read -server/utils/ImportHelper.ts: Read -server/utils/MutexLock.ts: Read -server/utils/PluginManager.ts: Read -server/utils/ProsemirrorHelper.test.ts: Read -server/utils/RateLimiter.ts: Read -server/utils/ShutdownHelper.ts: Read -server/utils/VerificationCode.ts: Read -server/utils/ZipHelper.ts: Read -server/utils/__mocks__: ReadDir -server/utils/__mocks__/CacheHelper.ts: Read -server/utils/__mocks__/MutexLock.ts: Read -server/utils/args.ts: Read -server/utils/authentication.ts: Read -server/utils/crypto.ts: Read -server/utils/decorators: ReadDir -server/utils/decorators/Public.ts: Read -server/utils/diff.ts: Read -server/utils/environment.ts: Read -server/utils/fetch.ts: Read -server/utils/fs.test.ts: Read -server/utils/fs.ts: Read -server/utils/getInstallationInfo.ts: Read -server/utils/i18n.ts: Read -server/utils/indexing.test.ts: Read -server/utils/indexing.ts: Read -server/utils/jwt.ts: Read -server/utils/koa.ts: Read -server/utils/oauth: ReadDir -server/utils/oauth.ts: Read -server/utils/oauth/OAuthInterface.test.ts: Read -server/utils/oauth/OAuthInterface.ts: Read -server/utils/opensearch.ts: Read -server/utils/parseAttachmentIds.test.ts: Read -server/utils/parseAttachmentIds.ts: Read -server/utils/parseImages.test.ts: Read -server/utils/parseImages.ts: Read -server/utils/passport.ts: Read -server/utils/permissions.test.ts: Read -server/utils/permissions.ts: Read -server/utils/prefetchTags.tsx: Read -server/utils/readManifestFile.ts: Read -server/utils/removeIndexCollision.test.ts: Read -server/utils/removeIndexCollision.ts: Read -server/utils/robots.ts: Read -server/utils/sitemap.ts: Read -server/utils/ssl.ts: Read -server/utils/startup.ts: Read -server/utils/timers.ts: Read -server/utils/turndown: ReadDir -server/utils/turndown/breaks.ts: Read -server/utils/turndown/emptyLists.ts: Read -server/utils/turndown/emptyParagraph.ts: Read -server/utils/turndown/frames.ts: Read -server/utils/turndown/images.ts: Read -server/utils/turndown/index.ts: Read -server/utils/turndown/inlineLink.ts: Read -server/utils/turndown/sanitizeLists.ts: Read -server/utils/turndown/sanitizeTables.ts: Read -server/utils/turndown/tables.ts: Read -server/utils/turndown/underlines.ts: Read -server/utils/turndown/utils.ts: Read -server/utils/updates.ts: Read -server/utils/url.ts: Read -server/utils/validators.ts: Read -server/utils/zod.ts: Read -server/validation.test.ts: Read -server/validation.ts: Read -shared: ReadDir -shared/../.oxlintrc.json: Read -shared/.oxlintrc.json: Read -shared/collaboration: ReadDir -shared/collaboration/CloseEvents.ts: Read -shared/components: ReadDir -shared/components/Backticks.tsx: Read -shared/components/EmojiIcon.tsx: Read -shared/components/EventBoundary.tsx: Read -shared/components/Flex.tsx: Read -shared/components/Icon.tsx: Read -shared/components/IssueStatusIcon: ReadDir -shared/components/IssueStatusIcon/GitHubIssueStatusIcon.tsx: Read -shared/components/IssueStatusIcon/LinearIssueStatusIcon.tsx: Read -shared/components/IssueStatusIcon/index.tsx: Read -shared/components/LetterIcon.tsx: Read -shared/components/PullRequestIcon.tsx: Read -shared/components/Spinner.tsx: Read -shared/components/Squircle.tsx: Read -shared/components/Text.tsx: Read -shared/constants.ts: Read -shared/editor: ReadDir -shared/editor/commands: ReadDir -shared/editor/commands/addMark.ts: Read -shared/editor/commands/backspaceToParagraph.ts: Read -shared/editor/commands/clearNodes.ts: Read -shared/editor/commands/codeFence.ts: Read -shared/editor/commands/collapseSelection.ts: Read -shared/editor/commands/deleteEmptyFirstParagraph.ts: Read -shared/editor/commands/insertFiles.ts: Read -shared/editor/commands/selectAll.ts: Read -shared/editor/commands/splitHeading.ts: Read -shared/editor/commands/table.ts: Read -shared/editor/commands/toggleBlockType.ts: Read -shared/editor/commands/toggleCheckboxItem.ts: Read -shared/editor/commands/toggleList.ts: Read -shared/editor/commands/toggleMark.ts: Read -shared/editor/commands/toggleWrap.ts: Read -shared/editor/components: ReadDir -shared/editor/components/Caption.tsx: Read -shared/editor/components/DisabledEmbed.tsx: Read -shared/editor/components/Embed.tsx: Read -shared/editor/components/FileExtension.tsx: Read -shared/editor/components/Frame.tsx: Read -shared/editor/components/Image.tsx: Read -shared/editor/components/ImageZoom.tsx: Read -shared/editor/components/Img.tsx: Read -shared/editor/components/Mentions.tsx: Read -shared/editor/components/ResizeHandle.tsx: Read -shared/editor/components/Styles.ts: Read -shared/editor/components/Video.tsx: Read -shared/editor/components/Widget.tsx: Read -shared/editor/components/hooks: ReadDir -shared/editor/components/hooks/useDragResize.ts: Read -shared/editor/embeds: ReadDir -shared/editor/embeds/Berrycast.tsx: Read -shared/editor/embeds/Diagrams.tsx: Read -shared/editor/embeds/Dropbox.tsx: Read -shared/editor/embeds/Gist.tsx: Read -shared/editor/embeds/GitLabSnippet.tsx: Read -shared/editor/embeds/InVision.tsx: Read -shared/editor/embeds/JSFiddle.tsx: Read -shared/editor/embeds/Linkedin.tsx: Read -shared/editor/embeds/Pinterest.tsx: Read -shared/editor/embeds/Spotify.tsx: Read -shared/editor/embeds/Trello.tsx: Read -shared/editor/embeds/Vimeo.tsx: Read -shared/editor/embeds/YouTube.tsx: Read -shared/editor/embeds/index.tsx: Read -shared/editor/extensions: ReadDir -shared/editor/extensions/CodeHighlighting.ts: Read -shared/editor/extensions/DateTime.ts: Read -shared/editor/extensions/History.ts: Read -shared/editor/extensions/Math.ts: Read -shared/editor/extensions/MaxLength.ts: Read -shared/editor/extensions/Mermaid.ts: Read -shared/editor/extensions/TrailingNode.ts: Read -shared/editor/lib: ReadDir -shared/editor/lib/Extension.ts: Read -shared/editor/lib/ExtensionManager.ts: Read -shared/editor/lib/FileHelper.test.ts: Read -shared/editor/lib/FileHelper.ts: Read -shared/editor/lib/InputRule.ts: Read -shared/editor/lib/chainTransactions.ts: Read -shared/editor/lib/changedDescendants.ts: Read -shared/editor/lib/code.test.ts: Read -shared/editor/lib/code.ts: Read -shared/editor/lib/embeds.ts: Read -shared/editor/lib/emoji.test.ts: Read -shared/editor/lib/emoji.ts: Read -shared/editor/lib/filterExcessSeparators.test.ts: Read -shared/editor/lib/filterExcessSeparators.ts: Read -shared/editor/lib/headingToSlug.ts: Read -shared/editor/lib/isCode.ts: Read -shared/editor/lib/isMarkdown.test.ts: Read -shared/editor/lib/isMarkdown.ts: Read -shared/editor/lib/markInputRule.ts: Read -shared/editor/lib/markdown: ReadDir -shared/editor/lib/markdown/normalize.ts: Read -shared/editor/lib/markdown/rules.ts: Read -shared/editor/lib/markdown/serializer.ts: Read -shared/editor/lib/mention.ts: Read -shared/editor/lib/multiplayer.ts: Read -shared/editor/lib/prosemirror-recreate-transform: ReadDir -shared/editor/lib/prosemirror-recreate-transform/copy.ts: Read -shared/editor/lib/prosemirror-recreate-transform/getFromPath.ts: Read -shared/editor/lib/prosemirror-recreate-transform/getReplaceStep.ts: Read -shared/editor/lib/prosemirror-recreate-transform/index.ts: Read -shared/editor/lib/prosemirror-recreate-transform/recreateTransform.ts: Read -shared/editor/lib/prosemirror-recreate-transform/removeMarks.ts: Read -shared/editor/lib/prosemirror-recreate-transform/simplifyTransform.ts: Read -shared/editor/lib/prosemirror-recreate-transform/types.ts: Read -shared/editor/lib/table.ts: Read -shared/editor/lib/textBetween.ts: Read -shared/editor/lib/textSerializers.ts: Read -shared/editor/lib/uploadPlaceholder.tsx: Read -shared/editor/marks: ReadDir -shared/editor/marks/Bold.ts: Read -shared/editor/marks/Code.ts: Read -shared/editor/marks/Comment.ts: Read -shared/editor/marks/Highlight.ts: Read -shared/editor/marks/Italic.ts: Read -shared/editor/marks/Link.tsx: Read -shared/editor/marks/Mark.ts: Read -shared/editor/marks/Placeholder.ts: Read -shared/editor/marks/Strikethrough.ts: Read -shared/editor/marks/Underline.ts: Read -shared/editor/nodes: ReadDir -shared/editor/nodes/Attachment.tsx: Read -shared/editor/nodes/Blockquote.ts: Read -shared/editor/nodes/BulletList.ts: Read -shared/editor/nodes/CheckboxItem.ts: Read -shared/editor/nodes/CheckboxList.ts: Read -shared/editor/nodes/CodeBlock.ts: Read -shared/editor/nodes/CodeFence.ts: Read -shared/editor/nodes/Doc.ts: Read -shared/editor/nodes/Embed.tsx: Read -shared/editor/nodes/Emoji.tsx: Read -shared/editor/nodes/HardBreak.ts: Read -shared/editor/nodes/Heading.ts: Read -shared/editor/nodes/HorizontalRule.ts: Read -shared/editor/nodes/Image.tsx: Read -shared/editor/nodes/ListItem.ts: Read -shared/editor/nodes/Math.ts: Read -shared/editor/nodes/MathBlock.ts: Read -shared/editor/nodes/Mention.tsx: Read -shared/editor/nodes/Node.ts: Read -shared/editor/nodes/Notice.tsx: Read -shared/editor/nodes/OrderedList.ts: Read -shared/editor/nodes/Paragraph.ts: Read -shared/editor/nodes/ReactNode.ts: Read -shared/editor/nodes/SimpleImage.tsx: Read -shared/editor/nodes/Table.ts: Read -shared/editor/nodes/TableCell.ts: Read -shared/editor/nodes/TableHeader.ts: Read -shared/editor/nodes/TableRow.ts: Read -shared/editor/nodes/TableView.ts: Read -shared/editor/nodes/Text.ts: Read -shared/editor/nodes/Video.tsx: Read -shared/editor/nodes/index.ts: Read -shared/editor/plugins: ReadDir -shared/editor/plugins/CodeWordDecorations.ts: Read -shared/editor/plugins/FixTables.ts: Read -shared/editor/plugins/PlaceholderPlugin.ts: Read -shared/editor/plugins/Suggestions.ts: Read -shared/editor/plugins/TableLayoutPlugin.ts: Read -shared/editor/plugins/UploadPlugin.ts: Read -shared/editor/queries: ReadDir -shared/editor/queries/findChildren.ts: Read -shared/editor/queries/findCollapsedNodes.ts: Read -shared/editor/queries/findNewlines.ts: Read -shared/editor/queries/findParentNode.ts: Read -shared/editor/queries/getCurrentBlock.ts: Read -shared/editor/queries/getMarkRange.ts: Read -shared/editor/queries/getMarksBetween.ts: Read -shared/editor/queries/getParentListItem.ts: Read -shared/editor/queries/isInCode.ts: Read -shared/editor/queries/isInList.ts: Read -shared/editor/queries/isInNotice.ts: Read -shared/editor/queries/isList.ts: Read -shared/editor/queries/isMarkActive.ts: Read -shared/editor/queries/isNodeActive.ts: Read -shared/editor/queries/table.ts: Read -shared/editor/rules: ReadDir -shared/editor/rules/breaks.ts: Read -shared/editor/rules/checkboxes.ts: Read -shared/editor/rules/embeds.ts: Read -shared/editor/rules/emoji.ts: Read -shared/editor/rules/links.ts: Read -shared/editor/rules/mark.ts: Read -shared/editor/rules/math.ts: Read -shared/editor/rules/mention.ts: Read -shared/editor/rules/notices.ts: Read -shared/editor/rules/tables.ts: Read -shared/editor/rules/underlines.ts: Read -shared/editor/styles: ReadDir -shared/editor/styles/EditorStyleHelper.ts: Read -shared/editor/styles/utils.ts: Read -shared/editor/types: ReadDir -shared/editor/types/index.ts: Read -shared/editor/version.ts: Read -shared/env.ts: Read -shared/hooks: ReadDir -shared/hooks/useIsMounted.ts: Read -shared/hooks/useStores.ts: Read -shared/i18n: ReadDir -shared/i18n/index.ts: Read -shared/i18n/locales: ReadDir -shared/i18n/locales/cs_CZ: ReadDir -shared/i18n/locales/da_DK: ReadDir -shared/i18n/locales/de_DE: ReadDir -shared/i18n/locales/en_GB: ReadDir -shared/i18n/locales/en_US: ReadDir -shared/i18n/locales/es_ES: ReadDir -shared/i18n/locales/fa_IR: ReadDir -shared/i18n/locales/fr_FR: ReadDir -shared/i18n/locales/he_IL: ReadDir -shared/i18n/locales/hu_HU: ReadDir -shared/i18n/locales/id_ID: ReadDir -shared/i18n/locales/it_IT: ReadDir -shared/i18n/locales/ja_JP: ReadDir -shared/i18n/locales/ko_KR: ReadDir -shared/i18n/locales/nb_NO: ReadDir -shared/i18n/locales/nl_NL: ReadDir -shared/i18n/locales/pl_PL: ReadDir -shared/i18n/locales/pt_BR: ReadDir -shared/i18n/locales/pt_PT: ReadDir -shared/i18n/locales/ro_RO: ReadDir -shared/i18n/locales/sv_SE: ReadDir -shared/i18n/locales/th_TH: ReadDir -shared/i18n/locales/tr_TR: ReadDir -shared/i18n/locales/uk_UA: ReadDir -shared/i18n/locales/vi_VN: ReadDir -shared/i18n/locales/zh_CN: ReadDir -shared/i18n/locales/zh_TW: ReadDir -shared/random.test.ts: Read -shared/random.ts: Read -shared/schema.ts: Read -shared/styles: ReadDir -shared/styles/breakpoints.ts: Read -shared/styles/depths.ts: Read -shared/styles/globals.ts: Read -shared/styles/index.ts: Read -shared/styles/theme.ts: Read -shared/test: ReadDir -shared/test/setup.ts: Read -shared/types.ts: Read -shared/typings: ReadDir -shared/utils: ReadDir -shared/utils/EventHelper.ts: Read -shared/utils/IconLibrary.tsx: Read -shared/utils/ProsemirrorHelper.test.ts: Read -shared/utils/ProsemirrorHelper.ts: Read -shared/utils/RevisionHelper.ts: Read -shared/utils/Storage.ts: Read -shared/utils/TextHelper.test.ts: Read -shared/utils/TextHelper.ts: Read -shared/utils/UrlHelper.ts: Read -shared/utils/UserRoleHelper.ts: Read -shared/utils/browser.ts: Read -shared/utils/collections.ts: Read -shared/utils/color.ts: Read -shared/utils/csv.test.ts: Read -shared/utils/csv.ts: Read -shared/utils/date.ts: Read -shared/utils/domains.test.ts: Read -shared/utils/domains.ts: Read -shared/utils/email.test.ts: Read -shared/utils/email.ts: Read -shared/utils/emoji.ts: Read -shared/utils/events.ts: Read -shared/utils/files.test.ts: Read -shared/utils/files.ts: Read -shared/utils/icon.ts: Read -shared/utils/indexCharacters.ts: Read -shared/utils/keyboard.ts: Read -shared/utils/markdown.ts: Read -shared/utils/naturalSort.test.ts: Read -shared/utils/naturalSort.ts: Read -shared/utils/parseCollectionSlug.test.ts: Read -shared/utils/parseCollectionSlug.ts: Read -shared/utils/parseDocumentSlug.test.ts: Read -shared/utils/parseDocumentSlug.ts: Read -shared/utils/parseMentionUrl.ts: Read -shared/utils/parseTitle.test.ts: Read -shared/utils/parseTitle.ts: Read -shared/utils/routeHelpers.ts: Read -shared/utils/rtl.ts: Read -shared/utils/slugify.ts: Read -shared/utils/string.ts: Read -shared/utils/time.ts: Read -shared/utils/urls.test.ts: Read -shared/utils/urls.ts: Read -shared/validations.ts: Read diff --git a/crates/fspy_e2e/snaps/outline-rolldown-vite-build.txt b/crates/fspy_e2e/snaps/outline-rolldown-vite-build.txt deleted file mode 100644 index e603b1f82..000000000 --- a/crates/fspy_e2e/snaps/outline-rolldown-vite-build.txt +++ /dev/null @@ -1,9953 +0,0 @@ -: ReadDir -.corepack.env: Read -.yarnrc: Read -.yarnrc.yml: Read -LICENSE: Read -README.md: Read -app/actions/definitions/apiKeys.tsx: Read -app/actions/definitions/collections.tsx: Read -app/actions/definitions/comments.tsx: Read -app/actions/definitions/developer.tsx: Read -app/actions/definitions/documents.tsx: Read -app/actions/definitions/integrations.tsx: Read -app/actions/definitions/navigation.tsx: Read -app/actions/definitions/notifications.tsx: Read -app/actions/definitions/oauthClients.tsx: Read -app/actions/definitions/package.json: Read -app/actions/definitions/revisions.tsx: Read -app/actions/definitions/settings.tsx: Read -app/actions/definitions/shares.tsx: Read -app/actions/definitions/teams.tsx: Read -app/actions/definitions/users.tsx: Read -app/actions/index.ts: Read -app/actions/package.json: Read -app/actions/root.ts: Read -app/actions/sections.ts: Read -app/components/ActionButton.tsx: Read -app/components/Actions.ts: Read -app/components/Analytics.tsx: Read -app/components/ArrowKeyNavigation.tsx: Read -app/components/Authenticated.tsx: Read -app/components/AuthenticatedLayout.tsx: Read -app/components/Avatar/Avatar.tsx: Read -app/components/Avatar/AvatarWithPresence.tsx: Read -app/components/Avatar/GroupAvatar.tsx: Read -app/components/Avatar/Initials.tsx: Read -app/components/Avatar/index.ts: Read -app/components/Avatar/package.json: Read -app/components/Badge.ts: Read -app/components/Branding.tsx: Read -app/components/Breadcrumb.tsx: Read -app/components/Button.tsx: Read -app/components/ButtonLarge.ts: Read -app/components/ButtonLink.tsx: Read -app/components/ButtonSmall.ts: Read -app/components/CenteredContent.tsx: Read -app/components/ChangeLanguage.tsx: Read -app/components/CircularProgressBar.tsx: Read -app/components/ClickablePadding.ts: Read -app/components/Collaborators.tsx: Read -app/components/Collection/CollectionEdit.tsx: Read -app/components/Collection/CollectionForm.tsx: Read -app/components/Collection/CollectionNew.tsx: Read -app/components/Collection/package.json: Read -app/components/CollectionBreadcrumb.tsx: Read -app/components/CollectionDeleteDialog.tsx: Read -app/components/CommandBar/CommandBar.tsx: Read -app/components/CommandBar/CommandBarItem.tsx: Read -app/components/CommandBar/CommandBarResults.tsx: Read -app/components/CommandBar/index.ts: Read -app/components/CommandBar/package.json: Read -app/components/CommandBar/useRecentDocumentActions.tsx: Read -app/components/CommandBar/useSettingsAction.tsx: Read -app/components/CommandBar/useTemplatesAction.tsx: Read -app/components/CommentDeleteDialog.tsx: Read -app/components/ConfirmMoveDialog.tsx: Read -app/components/ConfirmationDialog.tsx: Read -app/components/ContentEditable.tsx: Read -app/components/ContextMenu/Header.ts: Read -app/components/ContextMenu/MenuIconWrapper.ts: Read -app/components/ContextMenu/MenuItem.tsx: Read -app/components/ContextMenu/MouseSafeArea.tsx: Read -app/components/ContextMenu/Separator.tsx: Read -app/components/ContextMenu/Template.tsx: Read -app/components/ContextMenu/index.tsx: Read -app/components/ContextMenu/package.json: Read -app/components/CopyToClipboard.ts: Read -app/components/DefaultCollectionInputSelect.tsx: Read -app/components/DelayedMount.ts: Read -app/components/DesktopEventHandler.tsx: Read -app/components/Dialogs.tsx: Read -app/components/DisconnectAnalyticsDialog.tsx: Read -app/components/DocumentBreadcrumb.tsx: Read -app/components/DocumentCard.tsx: Read -app/components/DocumentContext.tsx: Read -app/components/DocumentCopy.tsx: Read -app/components/DocumentExplorer.tsx: Read -app/components/DocumentExplorerNode.tsx: Read -app/components/DocumentExplorerSearchResult.tsx: Read -app/components/DocumentListItem.tsx: Read -app/components/DocumentMeta.tsx: Read -app/components/DocumentTasks.tsx: Read -app/components/DocumentViews.tsx: Read -app/components/DocumentsLoader.tsx: Read -app/components/EditableTitle.tsx: Read -app/components/Editor.tsx: Read -app/components/Emoji.tsx: Read -app/components/Empty.ts: Read -app/components/ErrorBoundary.tsx: Read -app/components/EventListItem.tsx: Read -app/components/ExportDialog.tsx: Read -app/components/Facepile.tsx: Read -app/components/Fade.tsx: Read -app/components/FilterOptions.tsx: Read -app/components/Flex.tsx: Read -app/components/FullscreenLoading.tsx: Read -app/components/Guide.tsx: Read -app/components/Header.tsx: Read -app/components/Heading.ts: Read -app/components/Highlight.tsx: Read -app/components/HoverPreview/Components.tsx: Read -app/components/HoverPreview/HoverPreview.tsx: Read -app/components/HoverPreview/HoverPreviewDocument.tsx: Read -app/components/HoverPreview/HoverPreviewIssue.tsx: Read -app/components/HoverPreview/HoverPreviewLink.tsx: Read -app/components/HoverPreview/HoverPreviewMention.tsx: Read -app/components/HoverPreview/HoverPreviewPullRequest.tsx: Read -app/components/HoverPreview/index.ts: Read -app/components/HoverPreview/package.json: Read -app/components/IconPicker/components/ColorPicker.tsx: Read -app/components/IconPicker/components/EmojiPanel.tsx: Read -app/components/IconPicker/components/Grid.tsx: Read -app/components/IconPicker/components/GridTemplate.tsx: Read -app/components/IconPicker/components/IconButton.tsx: Read -app/components/IconPicker/components/IconPanel.tsx: Read -app/components/IconPicker/components/PopoverButton.tsx: Read -app/components/IconPicker/components/SkinTonePicker.tsx: Read -app/components/IconPicker/components/package.json: Read -app/components/IconPicker/index.tsx: Read -app/components/IconPicker/package.json: Read -app/components/IconPicker/utils.ts: Read -app/components/Icons/CircleIcon.tsx: Read -app/components/Icons/CollectionIcon.tsx: Read -app/components/Icons/GoogleIcon.tsx: Read -app/components/Icons/LanguageIcon.tsx: Read -app/components/Icons/MarkdownIcon.tsx: Read -app/components/Icons/OutlineIcon.tsx: Read -app/components/Icons/package.json: Read -app/components/Input.tsx: Read -app/components/InputColor.tsx: Read -app/components/InputLarge.ts: Read -app/components/InputMemberPermissionSelect.tsx: Read -app/components/InputSearch.tsx: Read -app/components/InputSearchPage.tsx: Read -app/components/InputSelect.tsx: Read -app/components/InputSelectPermission.tsx: Read -app/components/Key.ts: Read -app/components/LanguagePrompt.tsx: Read -app/components/Layout.tsx: Read -app/components/LazyLoad.ts: Read -app/components/LazyPolyfills.tsx: Read -app/components/List/Error.tsx: Read -app/components/List/Item.tsx: Read -app/components/List/List.ts: Read -app/components/List/Placeholder.tsx: Read -app/components/List/index.ts: Read -app/components/List/package.json: Read -app/components/LoadingIndicator/LoadingIndicator.ts: Read -app/components/LoadingIndicator/LoadingIndicatorBar.tsx: Read -app/components/LoadingIndicator/index.ts: Read -app/components/LoadingIndicator/package.json: Read -app/components/LocaleTime.tsx: Read -app/components/Menu/DropdownMenu.tsx: Read -app/components/Menu/OverflowMenuButton.tsx: Read -app/components/Menu/package.json: Read -app/components/Menu/transformer.tsx: Read -app/components/Modal.tsx: Read -app/components/NavLink.tsx: Read -app/components/Notice.tsx: Read -app/components/Notifications/NotificationIcon.tsx: Read -app/components/Notifications/NotificationListItem.tsx: Read -app/components/Notifications/Notifications.tsx: Read -app/components/Notifications/NotificationsPopover.tsx: Read -app/components/Notifications/package.json: Read -app/components/NudeButton.tsx: Read -app/components/OAuthClient/OAuthClientForm.tsx: Read -app/components/OAuthClient/OAuthClientNew.tsx: Read -app/components/OAuthClient/package.json: Read -app/components/OneTimePasswordInput.tsx: Read -app/components/PageScroll.tsx: Read -app/components/PageTheme.ts: Read -app/components/PageTitle.tsx: Read -app/components/PaginatedDocumentList.tsx: Read -app/components/PaginatedEventList.tsx: Read -app/components/PaginatedList.tsx: Read -app/components/PinnedDocuments.tsx: Read -app/components/PlaceholderDocument.tsx: Read -app/components/PlaceholderText.tsx: Read -app/components/PluginIcon.tsx: Read -app/components/Portal.tsx: Read -app/components/ProfiledRoute.ts: Read -app/components/Reactions/Reaction.tsx: Read -app/components/Reactions/ReactionList.tsx: Read -app/components/Reactions/ReactionPicker.tsx: Read -app/components/Reactions/ViewReactionsDialog.tsx: Read -app/components/Reactions/package.json: Read -app/components/RegisterKeyDown.ts: Read -app/components/ResizingHeightContainer.tsx: Read -app/components/RevisionListItem.tsx: Read -app/components/Scene.tsx: Read -app/components/ScrollContext.ts: Read -app/components/ScrollToTop.ts: Read -app/components/Scrollable.tsx: Read -app/components/SearchActions.ts: Read -app/components/SearchListItem.tsx: Read -app/components/SearchPopover.tsx: Read -app/components/Sharing/Collection/AccessControlList.tsx: Read -app/components/Sharing/Collection/PublicAccess.tsx: Read -app/components/Sharing/Collection/SharePopover.tsx: Read -app/components/Sharing/Collection/package.json: Read -app/components/Sharing/Document/AccessControlList.tsx: Read -app/components/Sharing/Document/DocumentMemberList.tsx: Read -app/components/Sharing/Document/DocumentMemberListItem.tsx: Read -app/components/Sharing/Document/PublicAccess.tsx: Read -app/components/Sharing/Document/SharePopover.tsx: Read -app/components/Sharing/Document/index.tsx: Read -app/components/Sharing/Document/package.json: Read -app/components/Sharing/components/Actions.tsx: Read -app/components/Sharing/components/CopyLinkButton.tsx: Read -app/components/Sharing/components/ListItem.tsx: Read -app/components/Sharing/components/PermissionAction.tsx: Read -app/components/Sharing/components/Placeholder.tsx: Read -app/components/Sharing/components/SearchInput.tsx: Read -app/components/Sharing/components/Suggestions.tsx: Read -app/components/Sharing/components/index.tsx: Read -app/components/Sharing/components/package.json: Read -app/components/Sharing/package.json: Read -app/components/Sidebar/App.tsx: Read -app/components/Sidebar/Right.tsx: Read -app/components/Sidebar/Settings.tsx: Read -app/components/Sidebar/Shared.tsx: Read -app/components/Sidebar/Sidebar.tsx: Read -app/components/Sidebar/components/ArchiveLink.tsx: Read -app/components/Sidebar/components/ArchivedCollectionLink.tsx: Read -app/components/Sidebar/components/CollectionLink.tsx: Read -app/components/Sidebar/components/CollectionLinkChildren.tsx: Read -app/components/Sidebar/components/Collections.tsx: Read -app/components/Sidebar/components/Disclosure.tsx: Read -app/components/Sidebar/components/DocumentLink.tsx: Read -app/components/Sidebar/components/DraftsLink.tsx: Read -app/components/Sidebar/components/DragPlaceholder.tsx: Read -app/components/Sidebar/components/DraggableCollectionLink.tsx: Read -app/components/Sidebar/components/DropCursor.tsx: Read -app/components/Sidebar/components/DropToImport.tsx: Read -app/components/Sidebar/components/Folder.tsx: Read -app/components/Sidebar/components/GroupLink.tsx: Read -app/components/Sidebar/components/Header.tsx: Read -app/components/Sidebar/components/HistoryNavigation.tsx: Read -app/components/Sidebar/components/NavLink.tsx: Read -app/components/Sidebar/components/PlaceholderCollections.tsx: Read -app/components/Sidebar/components/Relative.tsx: Read -app/components/Sidebar/components/ResizeBorder.ts: Read -app/components/Sidebar/components/Section.ts: Read -app/components/Sidebar/components/SharedCollectionLink.tsx: Read -app/components/Sidebar/components/SharedDocumentLink.tsx: Read -app/components/Sidebar/components/SharedWithMe.tsx: Read -app/components/Sidebar/components/SharedWithMeLink.tsx: Read -app/components/Sidebar/components/SidebarAction.tsx: Read -app/components/Sidebar/components/SidebarButton.tsx: Read -app/components/Sidebar/components/SidebarContext.ts: Read -app/components/Sidebar/components/SidebarLink.tsx: Read -app/components/Sidebar/components/Starred.tsx: Read -app/components/Sidebar/components/StarredLink.tsx: Read -app/components/Sidebar/components/ToggleButton.tsx: Read -app/components/Sidebar/components/TrashLink.tsx: Read -app/components/Sidebar/components/Version.tsx: Read -app/components/Sidebar/components/package.json: Read -app/components/Sidebar/hooks/package.json: Read -app/components/Sidebar/hooks/useCollectionDocuments.ts: Read -app/components/Sidebar/hooks/useDragAndDrop.tsx: Read -app/components/Sidebar/hooks/useSidebarLabelAndIcon.tsx: Read -app/components/Sidebar/index.ts: Read -app/components/Sidebar/package.json: Read -app/components/SkipNavContent.tsx: Read -app/components/SkipNavLink.tsx: Read -app/components/SortableTable.tsx: Read -app/components/Star.tsx: Read -app/components/Subheading.tsx: Read -app/components/Switch.tsx: Read -app/components/Tab.tsx: Read -app/components/Table.tsx: Read -app/components/Tabs.tsx: Read -app/components/TeamContext.ts: Read -app/components/TeamLogo.ts: Read -app/components/TemplatizeDialog/SelectLocation.tsx: Read -app/components/TemplatizeDialog/index.tsx: Read -app/components/TemplatizeDialog/package.json: Read -app/components/Text.ts: Read -app/components/Theme.tsx: Read -app/components/Time.tsx: Read -app/components/Toasts.tsx: Read -app/components/Tooltip.tsx: Read -app/components/TooltipContext.tsx: Read -app/components/UnreadBadge.tsx: Read -app/components/UserDialogs.tsx: Read -app/components/WebsocketProvider.tsx: Read -app/components/package.json: Read -app/components/primitives/Drawer.tsx: Read -app/components/primitives/DropdownMenu.tsx: Read -app/components/primitives/InputSelect.tsx: Read -app/components/primitives/Popover.tsx: Read -app/components/primitives/components/InputSelect.tsx: Read -app/components/primitives/components/Menu.tsx: Read -app/components/primitives/components/Overlay.tsx: Read -app/components/primitives/components/package.json: Read -app/components/primitives/package.json: Read -app/components/withStores.tsx: Read -app/editor/components/BlockMenu.tsx: Read -app/editor/components/ComponentView.tsx: Read -app/editor/components/EditorContext.tsx: Read -app/editor/components/EmbedLinkEditor.tsx: Read -app/editor/components/EmojiMenu.tsx: Read -app/editor/components/EmojiMenuItem.tsx: Read -app/editor/components/FindAndReplace.tsx: Read -app/editor/components/FloatingToolbar.tsx: Read -app/editor/components/Input.tsx: Read -app/editor/components/LinkEditor.tsx: Read -app/editor/components/MediaDimension.tsx: Read -app/editor/components/MentionMenu.tsx: Read -app/editor/components/NodeViewRenderer.tsx: Read -app/editor/components/PasteMenu.tsx: Read -app/editor/components/SelectionToolbar.tsx: Read -app/editor/components/SuggestionsMenu.tsx: Read -app/editor/components/SuggestionsMenuItem.tsx: Read -app/editor/components/ToolbarButton.tsx: Read -app/editor/components/ToolbarMenu.tsx: Read -app/editor/components/ToolbarSeparator.tsx: Read -app/editor/components/Tooltip.tsx: Read -app/editor/components/WithTheme.tsx: Read -app/editor/components/package.json: Read -app/editor/extensions/BlockMenu.tsx: Read -app/editor/extensions/ClipboardTextSerializer.ts: Read -app/editor/extensions/EmojiMenu.tsx: Read -app/editor/extensions/FindAndReplace.tsx: Read -app/editor/extensions/HoverPreviews.tsx: Read -app/editor/extensions/Keys.ts: Read -app/editor/extensions/MentionMenu.tsx: Read -app/editor/extensions/Multiplayer.ts: Read -app/editor/extensions/PasteHandler.tsx: Read -app/editor/extensions/PreventTab.ts: Read -app/editor/extensions/SmartText.ts: Read -app/editor/extensions/Suggestion.ts: Read -app/editor/extensions/UpArrowAtStart.ts: Read -app/editor/extensions/index.ts: Read -app/editor/extensions/package.json: Read -app/editor/index.tsx: Read -app/editor/menus/attachment.tsx: Read -app/editor/menus/block.tsx: Read -app/editor/menus/code.tsx: Read -app/editor/menus/divider.tsx: Read -app/editor/menus/formatting.tsx: Read -app/editor/menus/image.tsx: Read -app/editor/menus/notice.tsx: Read -app/editor/menus/package.json: Read -app/editor/menus/readOnly.tsx: Read -app/editor/menus/table.tsx: Read -app/editor/menus/tableCell.tsx: Read -app/editor/menus/tableCol.tsx: Read -app/editor/menus/tableRow.tsx: Read -app/editor/package.json: Read -app/env.ts: Read -app/hooks/package.json: Read -app/hooks/useActionContext.ts: Read -app/hooks/useAutoRefresh.ts: Read -app/hooks/useBoolean.ts: Read -app/hooks/useBuildTheme.ts: Read -app/hooks/useClickIntent.ts: Read -app/hooks/useCollectionTrees.ts: Read -app/hooks/useCommandBarActions.ts: Read -app/hooks/useComputed.ts: Read -app/hooks/useCurrentTeam.ts: Read -app/hooks/useCurrentUser.ts: Read -app/hooks/useDesktopTitlebar.ts: Read -app/hooks/useDictionary.ts: Read -app/hooks/useEditingFocus.ts: Read -app/hooks/useEditorClickHandlers.ts: Read -app/hooks/useEmbeds.ts: Read -app/hooks/useEventListener.ts: Read -app/hooks/useFocusedComment.ts: Read -app/hooks/useHover.ts: Read -app/hooks/useIdle.ts: Read -app/hooks/useImportDocument.ts: Read -app/hooks/useInterval.ts: Read -app/hooks/useIsMounted.ts: Read -app/hooks/useKeyDown.ts: Read -app/hooks/useLastVisitedPath.tsx: Read -app/hooks/useLocaleTime.ts: Read -app/hooks/useLocationSidebarContext.ts: Read -app/hooks/useLoggedInSessions.ts: Read -app/hooks/useMaxHeight.ts: Read -app/hooks/useMediaQuery.ts: Read -app/hooks/useMenuAction.ts: Read -app/hooks/useMenuContext.tsx: Read -app/hooks/useMenuHeight.ts: Read -app/hooks/useMenuState.tsx: Read -app/hooks/useMobile.ts: Read -app/hooks/useMousePosition.ts: Read -app/hooks/useOnClickOutside.ts: Read -app/hooks/useOnScreen.ts: Read -app/hooks/usePageVisibility.ts: Read -app/hooks/usePaginatedRequest.ts: Read -app/hooks/usePersistedState.ts: Read -app/hooks/usePinnedDocuments.ts: Read -app/hooks/usePolicy.ts: Read -app/hooks/usePrevious.ts: Read -app/hooks/useQuery.ts: Read -app/hooks/useQueryNotices.ts: Read -app/hooks/useRequest.ts: Read -app/hooks/useSettingsConfig.ts: Read -app/hooks/useStores.ts: Read -app/hooks/useTableRequest.ts: Read -app/hooks/useTemplateMenuActions.tsx: Read -app/hooks/useTextSelection.ts: Read -app/hooks/useTextStats.ts: Read -app/hooks/useThrottledCallback.ts: Read -app/hooks/useUnmount.ts: Read -app/hooks/useUserLocale.ts: Read -app/hooks/useWindowScrollPosition.ts: Read -app/hooks/useWindowSize.ts: Read -app/index.tsx: Read -app/menus/AccountMenu.tsx: Read -app/menus/ApiKeyMenu.tsx: Read -app/menus/BreadcrumbMenu.tsx: Read -app/menus/CollectionMenu.tsx: Read -app/menus/CommentMenu.tsx: Read -app/menus/DocumentMenu.tsx: Read -app/menus/FileOperationMenu.tsx: Read -app/menus/GroupMemberMenu.tsx: Read -app/menus/GroupMenu.tsx: Read -app/menus/ImportMenu.tsx: Read -app/menus/InsightsMenu.tsx: Read -app/menus/NewChildDocumentMenu.tsx: Read -app/menus/NewDocumentMenu.tsx: Read -app/menus/NewTemplateMenu.tsx: Read -app/menus/NotificationMenu.tsx: Read -app/menus/OAuthAuthenticationMenu.tsx: Read -app/menus/OAuthClientMenu.tsx: Read -app/menus/RevisionMenu.tsx: Read -app/menus/ShareMenu.tsx: Read -app/menus/TableOfContentsMenu.tsx: Read -app/menus/TeamMenu.tsx: Read -app/menus/TemplatesMenu.tsx: Read -app/menus/UserMenu.tsx: Read -app/menus/package.json: Read -app/models/ApiKey.ts: Read -app/models/AuthenticationProvider.ts: Read -app/models/Collection.ts: Read -app/models/Comment.ts: Read -app/models/Document.ts: Read -app/models/Event.ts: Read -app/models/FileOperation.ts: Read -app/models/Group.ts: Read -app/models/GroupMembership.ts: Read -app/models/GroupUser.ts: Read -app/models/Import.ts: Read -app/models/Integration.ts: Read -app/models/Membership.ts: Read -app/models/Notification.ts: Read -app/models/Pin.ts: Read -app/models/Policy.ts: Read -app/models/Revision.ts: Read -app/models/SearchQuery.ts: Read -app/models/Share.ts: Read -app/models/Star.ts: Read -app/models/Subscription.ts: Read -app/models/Team.ts: Read -app/models/Unfurl.ts: Read -app/models/User.ts: Read -app/models/UserMembership.ts: Read -app/models/View.ts: Read -app/models/WebhookSubscription.ts: Read -app/models/base/ArchivableModel.ts: Read -app/models/base/Model.ts: Read -app/models/base/ParanoidModel.ts: Read -app/models/base/package.json: Read -app/models/decorators/Field.ts: Read -app/models/decorators/Lifecycle.ts: Read -app/models/decorators/Relation.ts: Read -app/models/decorators/package.json: Read -app/models/oauth/OAuthAuthentication.ts: Read -app/models/oauth/OAuthClient.ts: Read -app/models/oauth/package.json: Read -app/models/package.json: Read -app/package.json: Read -app/routes/authenticated.tsx: Read -app/routes/index.tsx: Read -app/routes/package.json: Read -app/routes/settings.tsx: Read -app/scenes/ApiKeyNew/components/ExpiryDatePicker.tsx: Read -app/scenes/ApiKeyNew/components/package.json: Read -app/scenes/ApiKeyNew/index.tsx: Read -app/scenes/ApiKeyNew/package.json: Read -app/scenes/ApiKeyNew/utils.ts: Read -app/scenes/Archive.tsx: Read -app/scenes/Collection/components/Actions.tsx: Read -app/scenes/Collection/components/DropToImport.tsx: Read -app/scenes/Collection/components/Empty.tsx: Read -app/scenes/Collection/components/MembershipPreview.tsx: Read -app/scenes/Collection/components/Notices.tsx: Read -app/scenes/Collection/components/Overview.tsx: Read -app/scenes/Collection/components/ShareButton.tsx: Read -app/scenes/Collection/components/package.json: Read -app/scenes/Collection/index.tsx: Read -app/scenes/Collection/package.json: Read -app/scenes/DesktopRedirect.tsx: Read -app/scenes/Document/components/AsyncMultiplayerEditor.ts: Read -app/scenes/Document/components/CommentEditor.tsx: Read -app/scenes/Document/components/CommentForm.tsx: Read -app/scenes/Document/components/CommentSortMenu.tsx: Read -app/scenes/Document/components/CommentThread.tsx: Read -app/scenes/Document/components/CommentThreadItem.tsx: Read -app/scenes/Document/components/Comments.tsx: Read -app/scenes/Document/components/ConnectionStatus.tsx: Read -app/scenes/Document/components/Container.ts: Read -app/scenes/Document/components/Contents.tsx: Read -app/scenes/Document/components/DataLoader.tsx: Read -app/scenes/Document/components/Document.tsx: Read -app/scenes/Document/components/DocumentMeta.tsx: Read -app/scenes/Document/components/DocumentTitle.tsx: Read -app/scenes/Document/components/Editor.tsx: Read -app/scenes/Document/components/Header.tsx: Read -app/scenes/Document/components/HighlightText.ts: Read -app/scenes/Document/components/History.tsx: Read -app/scenes/Document/components/Insights.tsx: Read -app/scenes/Document/components/KeyboardShortcutsButton.tsx: Read -app/scenes/Document/components/Loading.tsx: Read -app/scenes/Document/components/MarkAsViewed.ts: Read -app/scenes/Document/components/MeasuredContainer.tsx: Read -app/scenes/Document/components/MultiplayerEditor.tsx: Read -app/scenes/Document/components/Notices.tsx: Read -app/scenes/Document/components/ObservingBanner.tsx: Read -app/scenes/Document/components/PublicBreadcrumb.tsx: Read -app/scenes/Document/components/PublicReferences.tsx: Read -app/scenes/Document/components/ReferenceListItem.tsx: Read -app/scenes/Document/components/References.tsx: Read -app/scenes/Document/components/RevisionViewer.tsx: Read -app/scenes/Document/components/ShareButton.tsx: Read -app/scenes/Document/components/SidebarLayout.tsx: Read -app/scenes/Document/components/SizeWarning.tsx: Read -app/scenes/Document/components/package.json: Read -app/scenes/Document/index.tsx: Read -app/scenes/Document/package.json: Read -app/scenes/DocumentDelete.tsx: Read -app/scenes/DocumentMove.tsx: Read -app/scenes/DocumentNew.tsx: Read -app/scenes/DocumentPermanentDelete.tsx: Read -app/scenes/DocumentPublish.tsx: Read -app/scenes/Drafts.tsx: Read -app/scenes/Errors/Error402.tsx: Read -app/scenes/Errors/Error403.tsx: Read -app/scenes/Errors/Error404.tsx: Read -app/scenes/Errors/ErrorOffline.tsx: Read -app/scenes/Errors/ErrorSuspended.tsx: Read -app/scenes/Errors/ErrorUnknown.tsx: Read -app/scenes/Errors/package.json: Read -app/scenes/Home.tsx: Read -app/scenes/Invite.tsx: Read -app/scenes/KeyboardShortcuts.tsx: Read -app/scenes/Login/Login.tsx: Read -app/scenes/Login/OAuthAuthorize.tsx: Read -app/scenes/Login/OAuthScopeHelper.ts: Read -app/scenes/Login/components/AuthenticationProvider.tsx: Read -app/scenes/Login/components/BackButton.tsx: Read -app/scenes/Login/components/Background.tsx: Read -app/scenes/Login/components/Centered.tsx: Read -app/scenes/Login/components/ConnectHeader.tsx: Read -app/scenes/Login/components/LoginDialog.tsx: Read -app/scenes/Login/components/Notices.tsx: Read -app/scenes/Login/components/TeamSwitcher.tsx: Read -app/scenes/Login/components/WorkspaceSetup.tsx: Read -app/scenes/Login/components/package.json: Read -app/scenes/Login/index.ts: Read -app/scenes/Login/package.json: Read -app/scenes/Login/urls.ts: Read -app/scenes/Logout.tsx: Read -app/scenes/Search/Search.tsx: Read -app/scenes/Search/components/CollectionFilter.tsx: Read -app/scenes/Search/components/DateFilter.tsx: Read -app/scenes/Search/components/DocumentFilter.tsx: Read -app/scenes/Search/components/DocumentTypeFilter.tsx: Read -app/scenes/Search/components/RecentSearchListItem.tsx: Read -app/scenes/Search/components/RecentSearches.tsx: Read -app/scenes/Search/components/SearchInput.tsx: Read -app/scenes/Search/components/UserFilter.tsx: Read -app/scenes/Search/components/package.json: Read -app/scenes/Search/index.ts: Read -app/scenes/Search/package.json: Read -app/scenes/Settings/APIAndApps.tsx: Read -app/scenes/Settings/ApiKeys.tsx: Read -app/scenes/Settings/Application.tsx: Read -app/scenes/Settings/Applications.tsx: Read -app/scenes/Settings/Details.tsx: Read -app/scenes/Settings/Export.tsx: Read -app/scenes/Settings/Features.tsx: Read -app/scenes/Settings/Groups.tsx: Read -app/scenes/Settings/Import.tsx: Read -app/scenes/Settings/Integrations.tsx: Read -app/scenes/Settings/Members.tsx: Read -app/scenes/Settings/Notifications.tsx: Read -app/scenes/Settings/Preferences.tsx: Read -app/scenes/Settings/Profile.tsx: Read -app/scenes/Settings/Security.tsx: Read -app/scenes/Settings/Shares.tsx: Read -app/scenes/Settings/Templates.tsx: Read -app/scenes/Settings/components/ActionRow.tsx: Read -app/scenes/Settings/components/ApiKeyListItem.tsx: Read -app/scenes/Settings/components/ApiKeyRevokeDialog.tsx: Read -app/scenes/Settings/components/ConnectedButton.tsx: Read -app/scenes/Settings/components/CopyButton.tsx: Read -app/scenes/Settings/components/DomainManagement.tsx: Read -app/scenes/Settings/components/DropToImport.tsx: Read -app/scenes/Settings/components/FileOperationListItem.tsx: Read -app/scenes/Settings/components/GroupDialogs.tsx: Read -app/scenes/Settings/components/GroupsTable.tsx: Read -app/scenes/Settings/components/HelpDisclosure.tsx: Read -app/scenes/Settings/components/ImageInput.tsx: Read -app/scenes/Settings/components/ImageUpload.tsx: Read -app/scenes/Settings/components/ImportJSONDialog.tsx: Read -app/scenes/Settings/components/ImportListItem.tsx: Read -app/scenes/Settings/components/ImportMarkdownDialog.tsx: Read -app/scenes/Settings/components/IntegrationCard.tsx: Read -app/scenes/Settings/components/IntegrationScene.tsx: Read -app/scenes/Settings/components/MembersTable.tsx: Read -app/scenes/Settings/components/OAuthAuthenticationListItem.tsx: Read -app/scenes/Settings/components/OAuthClientDeleteDialog.tsx: Read -app/scenes/Settings/components/OAuthClientListItem.tsx: Read -app/scenes/Settings/components/SettingRow.tsx: Read -app/scenes/Settings/components/SharesTable.tsx: Read -app/scenes/Settings/components/StickyFilters.tsx: Read -app/scenes/Settings/components/UserRoleFilter.tsx: Read -app/scenes/Settings/components/UserStatusFilter.tsx: Read -app/scenes/Settings/components/package.json: Read -app/scenes/Settings/package.json: Read -app/scenes/Shared/Collection.tsx: Read -app/scenes/Shared/Document.tsx: Read -app/scenes/Shared/index.tsx: Read -app/scenes/Shared/package.json: Read -app/scenes/TeamDelete.tsx: Read -app/scenes/TeamNew.tsx: Read -app/scenes/Trash/components/DeleteDocumentsInTrash.tsx: Read -app/scenes/Trash/components/package.json: Read -app/scenes/Trash/index.tsx: Read -app/scenes/Trash/package.json: Read -app/scenes/UserDelete.tsx: Read -app/scenes/package.json: Read -app/stores/ApiKeysStore.ts: Read -app/stores/AuthStore.ts: Read -app/stores/AuthenticationProvidersStore.ts: Read -app/stores/CollectionsStore.ts: Read -app/stores/CommentsStore.ts: Read -app/stores/DialogsStore.ts: Read -app/stores/DocumentPresenceStore.ts: Read -app/stores/DocumentsStore.ts: Read -app/stores/EventsStore.ts: Read -app/stores/FileOperationsStore.ts: Read -app/stores/GroupMembershipsStore.ts: Read -app/stores/GroupUsersStore.ts: Read -app/stores/GroupsStore.ts: Read -app/stores/ImportsStore.ts: Read -app/stores/IntegrationsStore.ts: Read -app/stores/MembershipsStore.ts: Read -app/stores/NotificationsStore.ts: Read -app/stores/OAuthAuthenticationsStore.ts: Read -app/stores/OAuthClientsStore.ts: Read -app/stores/PinsStore.ts: Read -app/stores/PoliciesStore.ts: Read -app/stores/RevisionsStore.ts: Read -app/stores/RootStore.ts: Read -app/stores/SearchesStore.ts: Read -app/stores/SharesStore.ts: Read -app/stores/StarsStore.ts: Read -app/stores/SubscriptionsStore.ts: Read -app/stores/UiStore.ts: Read -app/stores/UnfurlsStore.ts: Read -app/stores/UserMembershipsStore.ts: Read -app/stores/UsersStore.ts: Read -app/stores/ViewsStore.ts: Read -app/stores/WebhookSubscriptionStore.ts: Read -app/stores/base/Store.ts: Read -app/stores/base/package.json: Read -app/stores/index.ts: Read -app/stores/package.json: Read -app/styles/animations.ts: Read -app/styles/index.ts: Read -app/styles/package.json: Read -app/types.ts: Read -app/utils/Analytics.ts: Read -app/utils/ApiClient.ts: Read -app/utils/Desktop.ts: Read -app/utils/FeatureFlags.ts: Read -app/utils/Logger.ts: Read -app/utils/PluginManager.ts: Read -app/utils/compressImage.ts: Read -app/utils/date.ts: Read -app/utils/developer.ts: Read -app/utils/download.ts: Read -app/utils/emoji.ts: Read -app/utils/errors.ts: Read -app/utils/files.ts: Read -app/utils/history.ts: Read -app/utils/i18n.ts: Read -app/utils/isCloudHosted.ts: Read -app/utils/isTextInput.ts: Read -app/utils/language.ts: Read -app/utils/lazyWithRetry.ts: Read -app/utils/mention.ts: Read -app/utils/motion.ts: Read -app/utils/package.json: Read -app/utils/pageVisibility.ts: Read -app/utils/polyfills.ts: Read -app/utils/routeHelpers.ts: Read -app/utils/sentry.ts: Read -app/utils/tree.ts: Read -app/utils/urls.ts: Read -app/utils/viewTransition.ts: Read -build/app: ReadDir -build/app/.vite: ReadDir -build/app/.vite/manifest.json: Write -build/app/assets: ReadDir -build/app/assets/APIAndApps.CeCtbI2K.js: Write -build/app/assets/APIAndApps.CeCtbI2K.js.map: Write -build/app/assets/ActionRow.B8DFVEQR.js: Write -build/app/assets/ActionRow.B8DFVEQR.js.map: Write -build/app/assets/Actions.BEfp7tMk.js: Write -build/app/assets/Actions.BEfp7tMk.js.map: Write -build/app/assets/ApiKeyListItem.B-ya6Dxw.js: Write -build/app/assets/ApiKeyListItem.B-ya6Dxw.js.map: Write -build/app/assets/ApiKeyListItem.J4u_ju8H.css: Write -build/app/assets/ApiKeys.Cy-lQo0A.js: Write -build/app/assets/ApiKeys.Cy-lQo0A.js.map: Write -build/app/assets/Application.Cbv9VI9o.js: Write -build/app/assets/Application.Cbv9VI9o.js.map: Write -build/app/assets/Applications.CMr-BXf4.js: Write -build/app/assets/Applications.CMr-BXf4.js.map: Write -build/app/assets/Archive.4d1bx6yR.js: Write -build/app/assets/Archive.4d1bx6yR.js.map: Write -build/app/assets/Authenticated.jliUNCw_.js: Write -build/app/assets/Authenticated.jliUNCw_.js.map: Write -build/app/assets/Avatar.CKPBk2UG.js: Write -build/app/assets/Avatar.CKPBk2UG.js.map: Write -build/app/assets/Badge.BkeecLtY.js: Write -build/app/assets/Badge.BkeecLtY.js.map: Write -build/app/assets/Breadcrumb.B-wh7FLo.js: Write -build/app/assets/Breadcrumb.B-wh7FLo.js.map: Write -build/app/assets/ButtonLarge.Bvtx7IuJ.js: Write -build/app/assets/ButtonLarge.Bvtx7IuJ.js.map: Write -build/app/assets/ButtonLink.CAYfJ66q.js: Write -build/app/assets/ButtonLink.CAYfJ66q.js.map: Write -build/app/assets/ButtonSmall.DRtPsrw7.js: Write -build/app/assets/ButtonSmall.DRtPsrw7.js.map: Write -build/app/assets/Chrome.apv1pDX8.js: Write -build/app/assets/Chrome.apv1pDX8.js.map: Write -build/app/assets/CloseEvents.vWDEo3gV.js: Write -build/app/assets/CloseEvents.vWDEo3gV.js.map: Write -build/app/assets/Collection.CJCDeTM8.js: Write -build/app/assets/Collection.CJCDeTM8.js.map: Write -build/app/assets/CollectionIcon.BrGlhfFU.js: Write -build/app/assets/CollectionIcon.BrGlhfFU.js.map: Write -build/app/assets/CommandBar.CxLtvbWo.js: Write -build/app/assets/CommandBar.CxLtvbWo.js.map: Write -build/app/assets/CommentEditor.QTnlnC0p.js: Write -build/app/assets/CommentEditor.QTnlnC0p.js.map: Write -build/app/assets/Comments.QgQhtEmv.js: Write -build/app/assets/Comments.QgQhtEmv.js.map: Write -build/app/assets/ConfirmationDialog.DPEzl3H4.js: Write -build/app/assets/ConfirmationDialog.DPEzl3H4.js.map: Write -build/app/assets/ContentEditable.Yf1lwtN0.js: Write -build/app/assets/ContentEditable.Yf1lwtN0.js.map: Write -build/app/assets/CopyToClipboard.BHmPden9.js: Write -build/app/assets/CopyToClipboard.BHmPden9.js.map: Write -build/app/assets/DateFilter.CNtyAAq8.js: Write -build/app/assets/DateFilter.CNtyAAq8.js.map: Write -build/app/assets/Details.C2W0uVxk.js: Write -build/app/assets/Details.C2W0uVxk.js.map: Write -build/app/assets/Document.BDBIUJRC.js: Write -build/app/assets/Document.BDBIUJRC.js.map: Write -build/app/assets/Document.C-yJcent.js: Write -build/app/assets/Document.C-yJcent.js.map: Write -build/app/assets/DocumentBreadcrumb.Cf1Jgw-r.js: Write -build/app/assets/DocumentBreadcrumb.Cf1Jgw-r.js.map: Write -build/app/assets/DocumentContext.ChAmCLtH.js: Write -build/app/assets/DocumentContext.ChAmCLtH.js.map: Write -build/app/assets/DocumentListItem.QTEkAAn6.js: Write -build/app/assets/DocumentListItem.QTEkAAn6.js.map: Write -build/app/assets/DocumentMenu.De2NOugd.js: Write -build/app/assets/DocumentMenu.De2NOugd.js.map: Write -build/app/assets/DocumentNew.BqrIyqQ5.js: Write -build/app/assets/DocumentNew.BqrIyqQ5.js.map: Write -build/app/assets/DocumentViews.BnUI3PXN.js: Write -build/app/assets/DocumentViews.BnUI3PXN.js.map: Write -build/app/assets/Drafts.DB_Uuw7H.js: Write -build/app/assets/Drafts.DB_Uuw7H.js.map: Write -build/app/assets/Drawer.CNvTgSG9.js: Write -build/app/assets/Drawer.CNvTgSG9.js.map: Write -build/app/assets/Editor.ELIwz2Ql.js: Write -build/app/assets/Editor.ELIwz2Ql.js.map: Write -build/app/assets/Emoji.D6BJxTHA.js: Write -build/app/assets/Emoji.D6BJxTHA.js.map: Write -build/app/assets/EmojiPanel.Day5-HYe.js: Write -build/app/assets/EmojiPanel.Oajs8HK3.js: Write -build/app/assets/EmojiPanel.Oajs8HK3.js.map: Write -build/app/assets/Error.CAINuwj_.js: Write -build/app/assets/Error.CAINuwj_.js.map: Write -build/app/assets/Error404.IxOTMhLR.js: Write -build/app/assets/Error404.IxOTMhLR.js.map: Write -build/app/assets/Export.CrjdQxt_.js: Write -build/app/assets/Export.CrjdQxt_.js.map: Write -build/app/assets/ExportDialog.CGw0nvSV.js: Write -build/app/assets/ExportDialog.CGw0nvSV.js.map: Write -build/app/assets/Facepile.NG91ze9H.js: Write -build/app/assets/Facepile.NG91ze9H.js.map: Write -build/app/assets/Features.B_UfYkZU.js: Write -build/app/assets/Features.B_UfYkZU.js.map: Write -build/app/assets/FileOperationListItem.ISlDmZot.js: Write -build/app/assets/FileOperationListItem.ISlDmZot.js.map: Write -build/app/assets/FilterOptions.DomBNHt_.js: Write -build/app/assets/FilterOptions.DomBNHt_.js.map: Write -build/app/assets/FuzzySearch.CwJLylFy.js: Write -build/app/assets/FuzzySearch.CwJLylFy.js.map: Write -build/app/assets/Groups.C33FnS4u.js: Write -build/app/assets/Groups.C33FnS4u.js.map: Write -build/app/assets/Header.BJUcUEwD.js: Write -build/app/assets/Header.BJUcUEwD.js.map: Write -build/app/assets/Header.BVRp27k1.js: Write -build/app/assets/Header.BVRp27k1.js.map: Write -build/app/assets/Highlight.DaflQNY0.js: Write -build/app/assets/Highlight.DaflQNY0.js.map: Write -build/app/assets/History.CpvxLmud.js: Write -build/app/assets/History.CpvxLmud.js.map: Write -build/app/assets/Home.DYvf_5v5.js: Write -build/app/assets/Home.DYvf_5v5.js.map: Write -build/app/assets/Icon.9dvtc6gY.js: Write -build/app/assets/Icon.9dvtc6gY.js.map: Write -build/app/assets/Icon.Bz7-hLI9.js: Write -build/app/assets/Icon.Bz7-hLI9.js.map: Write -build/app/assets/Icon.D3QCfKft.js: Write -build/app/assets/Icon.D3QCfKft.js.map: Write -build/app/assets/Icon.DfhBRl7r.js: Write -build/app/assets/Icon.DfhBRl7r.js.map: Write -build/app/assets/Icon.DvDObCX0.js: Write -build/app/assets/Icon.DvDObCX0.js.map: Write -build/app/assets/Icon.NGdhbOaI.js: Write -build/app/assets/Icon.NGdhbOaI.js.map: Write -build/app/assets/IconPicker.BSYnO2we.js: Write -build/app/assets/IconPicker.BSYnO2we.js.map: Write -build/app/assets/ImageInput.bxlBoQz6.js: Write -build/app/assets/ImageInput.bxlBoQz6.js.map: Write -build/app/assets/Import.Bjw2FeUa.js: Write -build/app/assets/Import.Bjw2FeUa.js.map: Write -build/app/assets/InputSearchPage.YsCC_FvW.js: Write -build/app/assets/InputSearchPage.YsCC_FvW.js.map: Write -build/app/assets/InputSelect.CmUu5xqw.js: Write -build/app/assets/InputSelect.CmUu5xqw.js.map: Write -build/app/assets/InputSelectPermission.Ct_50yTO.js: Write -build/app/assets/InputSelectPermission.Ct_50yTO.js.map: Write -build/app/assets/Insights.4R_p_ivl.js: Write -build/app/assets/Insights.4R_p_ivl.js.map: Write -build/app/assets/IntegrationScene.PqgsiKXp.js: Write -build/app/assets/IntegrationScene.PqgsiKXp.js.map: Write -build/app/assets/Invite.B_fT7lZ3.js: Write -build/app/assets/Invite.B_fT7lZ3.js.map: Write -build/app/assets/Item.CfAPHC0Z.js: Write -build/app/assets/Item.CfAPHC0Z.js.map: Write -build/app/assets/KaTeX_AMS-Regular.BQhdFMY1.woff2: Write -build/app/assets/KaTeX_AMS-Regular.DMm9YOAa.woff: Write -build/app/assets/KaTeX_AMS-Regular.DRggAlZN.ttf: Write -build/app/assets/KaTeX_Caligraphic-Bold.ATXxdsX0.ttf: Write -build/app/assets/KaTeX_Caligraphic-Bold.BEiXGLvX.woff: Write -build/app/assets/KaTeX_Caligraphic-Bold.Dq_IR9rO.woff2: Write -build/app/assets/KaTeX_Caligraphic-Regular.CTRA-rTL.woff: Write -build/app/assets/KaTeX_Caligraphic-Regular.Di6jR-x-.woff2: Write -build/app/assets/KaTeX_Caligraphic-Regular.wX97UBjC.ttf: Write -build/app/assets/KaTeX_Fraktur-Bold.BdnERNNW.ttf: Write -build/app/assets/KaTeX_Fraktur-Bold.BsDP51OF.woff: Write -build/app/assets/KaTeX_Fraktur-Bold.CL6g_b3V.woff2: Write -build/app/assets/KaTeX_Fraktur-Regular.CB_wures.ttf: Write -build/app/assets/KaTeX_Fraktur-Regular.CTYiF6lA.woff2: Write -build/app/assets/KaTeX_Fraktur-Regular.Dxdc4cR9.woff: Write -build/app/assets/KaTeX_Main-Bold.Cx986IdX.woff2: Write -build/app/assets/KaTeX_Main-Bold.Jm3AIy58.woff: Write -build/app/assets/KaTeX_Main-Bold.waoOVXN0.ttf: Write -build/app/assets/KaTeX_Main-BoldItalic.DxDJ3AOS.woff2: Write -build/app/assets/KaTeX_Main-BoldItalic.DzxPMmG6.ttf: Write -build/app/assets/KaTeX_Main-BoldItalic.SpSLRI95.woff: Write -build/app/assets/KaTeX_Main-Italic.3WenGoN9.ttf: Write -build/app/assets/KaTeX_Main-Italic.BMLOBm91.woff: Write -build/app/assets/KaTeX_Main-Italic.NWA7e6Wa.woff2: Write -build/app/assets/KaTeX_Main-Regular.B22Nviop.woff2: Write -build/app/assets/KaTeX_Main-Regular.Dr94JaBh.woff: Write -build/app/assets/KaTeX_Main-Regular.ypZvNtVU.ttf: Write -build/app/assets/KaTeX_Math-BoldItalic.B3XSjfu4.ttf: Write -build/app/assets/KaTeX_Math-BoldItalic.CZnvNsCZ.woff2: Write -build/app/assets/KaTeX_Math-BoldItalic.iY-2wyZ7.woff: Write -build/app/assets/KaTeX_Math-Italic.DA0__PXp.woff: Write -build/app/assets/KaTeX_Math-Italic.flOr_0UB.ttf: Write -build/app/assets/KaTeX_Math-Italic.t53AETM-.woff2: Write -build/app/assets/KaTeX_SansSerif-Bold.CFMepnvq.ttf: Write -build/app/assets/KaTeX_SansSerif-Bold.D1sUS0GD.woff2: Write -build/app/assets/KaTeX_SansSerif-Bold.DbIhKOiC.woff: Write -build/app/assets/KaTeX_SansSerif-Italic.C3H0VqGB.woff2: Write -build/app/assets/KaTeX_SansSerif-Italic.DN2j7dab.woff: Write -build/app/assets/KaTeX_SansSerif-Italic.YYjJ1zSn.ttf: Write -build/app/assets/KaTeX_SansSerif-Regular.BNo7hRIc.ttf: Write -build/app/assets/KaTeX_SansSerif-Regular.CS6fqUqJ.woff: Write -build/app/assets/KaTeX_SansSerif-Regular.DDBCnlJ7.woff2: Write -build/app/assets/KaTeX_Script-Regular.C5JkGWo-.ttf: Write -build/app/assets/KaTeX_Script-Regular.D3wIWfF6.woff2: Write -build/app/assets/KaTeX_Script-Regular.D5yQViql.woff: Write -build/app/assets/KaTeX_Size1-Regular.C195tn64.woff: Write -build/app/assets/KaTeX_Size1-Regular.Dbsnue_I.ttf: Write -build/app/assets/KaTeX_Size1-Regular.mCD8mA8B.woff2: Write -build/app/assets/KaTeX_Size2-Regular.B7gKUWhC.ttf: Write -build/app/assets/KaTeX_Size2-Regular.Dy4dx90m.woff2: Write -build/app/assets/KaTeX_Size2-Regular.oD1tc_U0.woff: Write -build/app/assets/KaTeX_Size3-Regular.CTq5MqoE.woff: Write -build/app/assets/KaTeX_Size3-Regular.DgpXs0kz.ttf: Write -build/app/assets/KaTeX_Size3-Regular.gV2CO0n9.woff2: Write -build/app/assets/KaTeX_Size4-Regular.BF-4gkZK.woff: Write -build/app/assets/KaTeX_Size4-Regular.DWFBv043.ttf: Write -build/app/assets/KaTeX_Size4-Regular.Dl5lxZxV.woff2: Write -build/app/assets/KaTeX_Typewriter-Regular.C0xS9mPB.woff: Write -build/app/assets/KaTeX_Typewriter-Regular.CO6r4hn1.woff2: Write -build/app/assets/KaTeX_Typewriter-Regular.D3Ib7_Hf.ttf: Write -build/app/assets/LazyLoad.DrBDxP_r.js: Write -build/app/assets/LazyLoad.DrBDxP_r.js.map: Write -build/app/assets/List.Yza4EdGW.js: Write -build/app/assets/List.Yza4EdGW.js.map: Write -build/app/assets/ListItem.BByeqkFx.js: Write -build/app/assets/ListItem.BByeqkFx.js.map: Write -build/app/assets/LoadingIndicator.pfRsPzzG.js: Write -build/app/assets/LoadingIndicator.pfRsPzzG.js.map: Write -build/app/assets/LocaleTime.B-IKjZHo.js: Write -build/app/assets/LocaleTime.B-IKjZHo.js.map: Write -build/app/assets/Login.BMMOK4Yi.js: Write -build/app/assets/Login.BMMOK4Yi.js.map: Write -build/app/assets/Login.BNammznU.js: Write -build/app/assets/Login.GitV74lU.js: Write -build/app/assets/Login.GitV74lU.js.map: Write -build/app/assets/Logout.EXq9bSOa.js: Write -build/app/assets/Logout.EXq9bSOa.js.map: Write -build/app/assets/MarkdownIcon.DY4U96kH.js: Write -build/app/assets/MarkdownIcon.DY4U96kH.js.map: Write -build/app/assets/Members.Dr6qiPaj.js: Write -build/app/assets/Members.Dr6qiPaj.js.map: Write -build/app/assets/MenuItem.CL2IMhoo.js: Write -build/app/assets/MenuItem.CL2IMhoo.js.map: Write -build/app/assets/MultiplayerEditor.C-vSetQ5.js: Write -build/app/assets/MultiplayerEditor.C-vSetQ5.js.map: Write -build/app/assets/NewDocumentMenu.10dQ7tLB.js: Write -build/app/assets/NewDocumentMenu.10dQ7tLB.js.map: Write -build/app/assets/Notice.DCZ-sIv4.js: Write -build/app/assets/Notice.DCZ-sIv4.js.map: Write -build/app/assets/Notifications.Bc67bsGW.js: Write -build/app/assets/Notifications.Bc67bsGW.js.map: Write -build/app/assets/OAuthAuthorize.Dr3eXzoW.js: Write -build/app/assets/OAuthAuthorize.Dr3eXzoW.js.map: Write -build/app/assets/OAuthClientMenu.D99yryCu.js: Write -build/app/assets/OAuthClientMenu.D99yryCu.js.map: Write -build/app/assets/OAuthScopeHelper.DbTxUcbf.js: Write -build/app/assets/OAuthScopeHelper.DbTxUcbf.js.map: Write -build/app/assets/OutlineIcon.C5iezx4I.js: Write -build/app/assets/OutlineIcon.C5iezx4I.js.map: Write -build/app/assets/OverflowMenuButton.B8TpNzSc.js: Write -build/app/assets/OverflowMenuButton.B8TpNzSc.js.map: Write -build/app/assets/Overview.Dv34JSmd.js: Write -build/app/assets/Overview.Dv34JSmd.js.map: Write -build/app/assets/PaginatedDocumentList.DooF8mLB.js: Write -build/app/assets/PaginatedDocumentList.DooF8mLB.js.map: Write -build/app/assets/PaginatedList.MsfVrj7U.js: Write -build/app/assets/PaginatedList.MsfVrj7U.js.map: Write -build/app/assets/PinnedDocuments.BtV3L1bA.js: Write -build/app/assets/PinnedDocuments.BtV3L1bA.js.map: Write -build/app/assets/Placeholder.Di0q8PAT.js: Write -build/app/assets/Placeholder.Di0q8PAT.js.map: Write -build/app/assets/PlaceholderDocument.DarGecn3.js: Write -build/app/assets/PlaceholderDocument.DarGecn3.js.map: Write -build/app/assets/PlaceholderText.D2DDUkd2.js: Write -build/app/assets/PlaceholderText.D2DDUkd2.js.map: Write -build/app/assets/PluginIcon.F0JwJNx8.js: Write -build/app/assets/PluginIcon.F0JwJNx8.js.map: Write -build/app/assets/Popover.CqqbO2AB.js: Write -build/app/assets/Popover.CqqbO2AB.js.map: Write -build/app/assets/PopoverButton.ug12t-Ug.js: Write -build/app/assets/PopoverButton.ug12t-Ug.js.map: Write -build/app/assets/Portal.DyGMBz_x.js: Write -build/app/assets/Portal.DyGMBz_x.js.map: Write -build/app/assets/PortalCompat.D9hze_HJ.js: Write -build/app/assets/PortalCompat.D9hze_HJ.js.map: Write -build/app/assets/Preferences.jCW-Fffh.js: Write -build/app/assets/Preferences.jCW-Fffh.js.map: Write -build/app/assets/Profile.BBI1ugPk.js: Write -build/app/assets/Profile.BBI1ugPk.js.map: Write -build/app/assets/RegisterKeyDown.Br3yVyiy.js: Write -build/app/assets/RegisterKeyDown.Br3yVyiy.js.map: Write -build/app/assets/Relative.8jXysdXz.js: Write -build/app/assets/Relative.8jXysdXz.js.map: Write -build/app/assets/ResizingHeightContainer.DBLAjJ9z.js: Write -build/app/assets/ResizingHeightContainer.DBLAjJ9z.js.map: Write -build/app/assets/RevisionHelper.DnJN_spI.js: Write -build/app/assets/RevisionHelper.DnJN_spI.js.map: Write -build/app/assets/Scene.r8I3Onn7.js: Write -build/app/assets/Scene.r8I3Onn7.js.map: Write -build/app/assets/Search.C70bQMRQ.js: Write -build/app/assets/Search.C70bQMRQ.js.map: Write -build/app/assets/Section.FnZv_oFE.js: Write -build/app/assets/Section.FnZv_oFE.js.map: Write -build/app/assets/Security.CE4adUwF.js: Write -build/app/assets/Security.CE4adUwF.js.map: Write -build/app/assets/Separator.DVjDjTv7.js: Write -build/app/assets/Separator.DVjDjTv7.js.map: Write -build/app/assets/SettingRow.bKH22nBY.js: Write -build/app/assets/SettingRow.bKH22nBY.js.map: Write -build/app/assets/Settings.B99eW8in.js: Write -build/app/assets/Settings.B99eW8in.js.map: Write -build/app/assets/Settings.BAkxooAz.js: Write -build/app/assets/Settings.BAkxooAz.js.map: Write -build/app/assets/Settings.C7VVEr6L.js: Write -build/app/assets/Settings.C7VVEr6L.js.map: Write -build/app/assets/Settings.DOxWsvOe.js: Write -build/app/assets/Settings.DOxWsvOe.js.map: Write -build/app/assets/Settings.DZixgezE.js: Write -build/app/assets/Settings.DZixgezE.js.map: Write -build/app/assets/Settings.We_Q7vJL.js: Write -build/app/assets/Settings.We_Q7vJL.js.map: Write -build/app/assets/Settings.cYexJ9vP.js: Write -build/app/assets/Settings.cYexJ9vP.js.map: Write -build/app/assets/Settings.n3LK1mfI.js: Write -build/app/assets/Settings.n3LK1mfI.js.map: Write -build/app/assets/Shared.Dcj9_ONB.js: Write -build/app/assets/Shared.Dcj9_ONB.js.map: Write -build/app/assets/SharedLayoutContext.BXfushv5.js: Write -build/app/assets/SharedLayoutContext.BXfushv5.js.map: Write -build/app/assets/Shares.Dn_O7MBb.js: Write -build/app/assets/Shares.Dn_O7MBb.js.map: Write -build/app/assets/SidebarLayout.Dlnlnz87.js: Write -build/app/assets/SidebarLayout.Dlnlnz87.js.map: Write -build/app/assets/SmartText.DO0imxAb.js: Write -build/app/assets/SmartText.DO0imxAb.js.map: Write -build/app/assets/SortableTable.CClJp563.js: Write -build/app/assets/SortableTable.CClJp563.js.map: Write -build/app/assets/Star.CJW6FSze.js: Write -build/app/assets/Star.CJW6FSze.js.map: Write -build/app/assets/StickyFilters.BTeXasDH.js: Write -build/app/assets/StickyFilters.BTeXasDH.js.map: Write -build/app/assets/Subheading.2SRXu1yq.js: Write -build/app/assets/Subheading.2SRXu1yq.js.map: Write -build/app/assets/Switch.D9x4TZ8x.js: Write -build/app/assets/Switch.D9x4TZ8x.js.map: Write -build/app/assets/Table.BfM2EiT5.js: Write -build/app/assets/Table.BfM2EiT5.js.map: Write -build/app/assets/Tabs.DCtjKZjr.js: Write -build/app/assets/Tabs.DCtjKZjr.js.map: Write -build/app/assets/TeamLogo.D6K44HOK.js: Write -build/app/assets/TeamLogo.D6K44HOK.js.map: Write -build/app/assets/Templates.B5bPN-JG.js: Write -build/app/assets/Templates.B5bPN-JG.js.map: Write -build/app/assets/Time.C0tKaAVk.js: Write -build/app/assets/Time.C0tKaAVk.js.map: Write -build/app/assets/Trash.7HF2CSEp.js: Write -build/app/assets/Trash.7HF2CSEp.js.map: Write -build/app/assets/UserDialogs.BfBydeQG.js: Write -build/app/assets/UserDialogs.BfBydeQG.js.map: Write -build/app/assets/VisualElementDragControls.2AWg8B2J.js: Write -build/app/assets/VisualElementDragControls.2AWg8B2J.js.map: Write -build/app/assets/_basePickBy.DCs3TymO.js: Write -build/app/assets/_basePickBy.DCs3TymO.js.map: Write -build/app/assets/_baseUniq.Cr_gtjW1.js: Write -build/app/assets/_baseUniq.Cr_gtjW1.js.map: Write -build/app/assets/_castFunction.tDPe2PUO.js: Write -build/app/assets/_castFunction.tDPe2PUO.js.map: Write -build/app/assets/_copyArray.DLc5PLRc.js: Write -build/app/assets/_copyArray.DLc5PLRc.js.map: Write -build/app/assets/arc.CiJXqVrc.js: Write -build/app/assets/arc.CiJXqVrc.js.map: Write -build/app/assets/architecture-O4VJ6CD3.DATN9rQ8.js: Write -build/app/assets/architectureDiagram-SUXI7LT5.RBXENAcB.js: Write -build/app/assets/architectureDiagram-SUXI7LT5.RBXENAcB.js.map: Write -build/app/assets/array.CShjgF6K.js: Write -build/app/assets/array.CShjgF6K.js.map: Write -build/app/assets/assertString.CoNxBcX_.js: Write -build/app/assets/assertString.CoNxBcX_.js.map: Write -build/app/assets/authenticated.BFsy-ywT.js: Write -build/app/assets/authenticated.BFsy-ywT.js.map: Write -build/app/assets/autotrack.BA9KwFtb.js: Write -build/app/assets/autotrack.BA9KwFtb.js.map: Write -build/app/assets/bash.Cu9r24LA.js: Write -build/app/assets/bash.Cu9r24LA.js.map: Write -build/app/assets/blockDiagram-6J76NXCF.D3MW-u4Z.js: Write -build/app/assets/blockDiagram-6J76NXCF.D3MW-u4Z.js.map: Write -build/app/assets/c.Bn_N9pnL.js: Write -build/app/assets/c.Bn_N9pnL.js.map: Write -build/app/assets/c4Diagram-6F6E4RAY.D87Bs709.js: Write -build/app/assets/c4Diagram-6F6E4RAY.D87Bs709.js.map: Write -build/app/assets/channel.IE84S4h1.js: Write -build/app/assets/channel.IE84S4h1.js.map: Write -build/app/assets/chunk-353BL4L5.Dytvqwwr.js: Write -build/app/assets/chunk-353BL4L5.Dytvqwwr.js.map: Write -build/app/assets/chunk-4KMFLZZN.DuxZhtpZ.js: Write -build/app/assets/chunk-4KMFLZZN.DuxZhtpZ.js.map: Write -build/app/assets/chunk-55PJQP7W.BlRXSyPT.js: Write -build/app/assets/chunk-55PJQP7W.BlRXSyPT.js.map: Write -build/app/assets/chunk-67H74DCK.IL9ivW6t.js: Write -build/app/assets/chunk-67H74DCK.IL9ivW6t.js.map: Write -build/app/assets/chunk-AACKK3MU.Dl7Fn6zG.js: Write -build/app/assets/chunk-AACKK3MU.Dl7Fn6zG.js.map: Write -build/app/assets/chunk-AC5SNWB5.Droba-Gq.js: Write -build/app/assets/chunk-AC5SNWB5.Droba-Gq.js.map: Write -build/app/assets/chunk-BFAMUDN2.BrFbKj8d.js: Write -build/app/assets/chunk-BFAMUDN2.BrFbKj8d.js.map: Write -build/app/assets/chunk-BN7GFLIU.DzaWqhco.js: Write -build/app/assets/chunk-BN7GFLIU.DzaWqhco.js.map: Write -build/app/assets/chunk-E2GYISFI.D-sSoGGq.js: Write -build/app/assets/chunk-E2GYISFI.D-sSoGGq.js.map: Write -build/app/assets/chunk-IWUHOULB.Dbr7YyC_.js: Write -build/app/assets/chunk-IWUHOULB.Dbr7YyC_.js.map: Write -build/app/assets/chunk-JEIROHC2.DIT--1ws.js: Write -build/app/assets/chunk-JEIROHC2.DIT--1ws.js.map: Write -build/app/assets/chunk-JW4RIYDF.DKon-4bH.js: Write -build/app/assets/chunk-JW4RIYDF.DKon-4bH.js.map: Write -build/app/assets/chunk-KMC2YHZD.C1SlEIAE.js: Write -build/app/assets/chunk-KMC2YHZD.C1SlEIAE.js.map: Write -build/app/assets/chunk-L5ZGVLVO.3b1vhtek.js: Write -build/app/assets/chunk-L5ZGVLVO.3b1vhtek.js.map: Write -build/app/assets/chunk-M6DAPIYF.CcfsY1Rv.js: Write -build/app/assets/chunk-M6DAPIYF.CcfsY1Rv.js.map: Write -build/app/assets/chunk-MXNHSMXR.DTAMmHe8.js: Write -build/app/assets/chunk-MXNHSMXR.DTAMmHe8.js.map: Write -build/app/assets/chunk-OW32GOEJ.Cx4flZtN.js: Write -build/app/assets/chunk-OW32GOEJ.Cx4flZtN.js.map: Write -build/app/assets/chunk-P3VETL53.AzIovDmm.js: Write -build/app/assets/chunk-P3VETL53.AzIovDmm.js.map: Write -build/app/assets/chunk-QESNASVV.CbGRFRvQ.js: Write -build/app/assets/chunk-QESNASVV.CbGRFRvQ.js.map: Write -build/app/assets/chunk-RKKUNAVE.B2FBkKKM.js: Write -build/app/assets/chunk-RKKUNAVE.B2FBkKKM.js.map: Write -build/app/assets/chunk-SKB7J2MH.DpFrNIhw.js: Write -build/app/assets/chunk-SKB7J2MH.DpFrNIhw.js.map: Write -build/app/assets/chunk-SZ463SBG.BvPyLWVj.js: Write -build/app/assets/chunk-SZ463SBG.BvPyLWVj.js.map: Write -build/app/assets/chunk-T44TD3VJ.CGIVI8Za.js: Write -build/app/assets/chunk-T44TD3VJ.CGIVI8Za.js.map: Write -build/app/assets/chunk-UWXLY5YG.DIcgzX68.js: Write -build/app/assets/chunk-UWXLY5YG.DIcgzX68.js.map: Write -build/app/assets/chunk-WFRQ32O7.CFXfeIPJ.js: Write -build/app/assets/chunk-WFRQ32O7.CFXfeIPJ.js.map: Write -build/app/assets/chunk-WFWHJNB7.BXRvPwz4.js: Write -build/app/assets/chunk-WFWHJNB7.BXRvPwz4.js.map: Write -build/app/assets/chunk-XRWGC2XP.DvmIuE4T.js: Write -build/app/assets/chunk-XRWGC2XP.DvmIuE4T.js.map: Write -build/app/assets/classDiagram-M3E45YP4.zAf0QDwM.js: Write -build/app/assets/classDiagram-M3E45YP4.zAf0QDwM.js.map: Write -build/app/assets/classDiagram-v2-YAWTLIQI.BiCbJrsR.js: Write -build/app/assets/classDiagram-v2-YAWTLIQI.BiCbJrsR.js.map: Write -build/app/assets/client.2o_pou_b.js: Write -build/app/assets/client.2o_pou_b.js.map: Write -build/app/assets/client.B1cCrEgP.js: Write -build/app/assets/client.B1cCrEgP.js.map: Write -build/app/assets/client.B5y00gIH.js: Write -build/app/assets/client.B5y00gIH.js.map: Write -build/app/assets/client.BmL6X9D8.js: Write -build/app/assets/client.BmL6X9D8.js.map: Write -build/app/assets/client.CeonnAuu.js: Write -build/app/assets/client.CeonnAuu.js.map: Write -build/app/assets/client.CpZUUNvd.js: Write -build/app/assets/client.CpZUUNvd.js.map: Write -build/app/assets/client.DfT4zSep.js: Write -build/app/assets/client.DfT4zSep.js.map: Write -build/app/assets/client.Dkgo-s8U.js: Write -build/app/assets/client.Dkgo-s8U.js.map: Write -build/app/assets/client.Dt1gJmBG.js: Write -build/app/assets/client.Dt1gJmBG.js.map: Write -build/app/assets/client.Dy8D4cOh.js: Write -build/app/assets/client.Dy8D4cOh.js.map: Write -build/app/assets/client.hb3fNs_X.js: Write -build/app/assets/client.hb3fNs_X.js.map: Write -build/app/assets/client.z5ipq2L2.js: Write -build/app/assets/client.z5ipq2L2.js.map: Write -build/app/assets/clike.D0K-R0vR.js: Write -build/app/assets/clone.B9SZ2pwt.js: Write -build/app/assets/clone.B9SZ2pwt.js.map: Write -build/app/assets/cloneDeep.B8jI5Ks5.js: Write -build/app/assets/cloneDeep.B8jI5Ks5.js.map: Write -build/app/assets/collections.Dvn04qQv.js: Write -build/app/assets/collections.Dvn04qQv.js.map: Write -build/app/assets/constant.Dnd2Q7Ds.js: Write -build/app/assets/constant.Dnd2Q7Ds.js.map: Write -build/app/assets/cpp.BzBJOQRH.js: Write -build/app/assets/cpp.BzBJOQRH.js.map: Write -build/app/assets/csharp.CuhAVAXz.js: Write -build/app/assets/csharp.CuhAVAXz.js.map: Write -build/app/assets/css.COGPg9q2.js: Write -build/app/assets/csv.DbmDc7aw.js: Write -build/app/assets/csv.DbmDc7aw.js.map: Write -build/app/assets/cytoscape.esm.BonP2qFu.js: Write -build/app/assets/cytoscape.esm.BonP2qFu.js.map: Write -build/app/assets/dagre-JOIXM2OF.B7Igasyb.js: Write -build/app/assets/dagre-JOIXM2OF.B7Igasyb.js.map: Write -build/app/assets/dagre.57TskpUC.js: Write -build/app/assets/dagre.57TskpUC.js.map: Write -build/app/assets/dart.oebdL6q8.js: Write -build/app/assets/dart.oebdL6q8.js.map: Write -build/app/assets/defaultLocale.B4RC9C1D.js: Write -build/app/assets/defaultLocale.B4RC9C1D.js.map: Write -build/app/assets/diagram-5UYTHUR4.DAVH8Yg9.js: Write -build/app/assets/diagram-5UYTHUR4.DAVH8Yg9.js.map: Write -build/app/assets/diagram-VMROVX33.D7ldsAmD.js: Write -build/app/assets/diagram-VMROVX33.D7ldsAmD.js.map: Write -build/app/assets/diagram-ZTM2IBQH.6_rPs4Bk.js: Write -build/app/assets/diagram-ZTM2IBQH.6_rPs4Bk.js.map: Write -build/app/assets/difference.MYWeAQ5F.js: Write -build/app/assets/difference.MYWeAQ5F.js.map: Write -build/app/assets/dist.CnQg5nuW.js: Write -build/app/assets/dist.CnQg5nuW.js.map: Write -build/app/assets/dist.CzscpE82.js: Write -build/app/assets/dist.CzscpE82.js.map: Write -build/app/assets/dist.DNhykc0O.js: Write -build/app/assets/dist.DNhykc0O.js.map: Write -build/app/assets/dist.HkvsFUcv.js: Write -build/app/assets/dist.HkvsFUcv.js.map: Write -build/app/assets/dist.NFH2ueqJ.js: Write -build/app/assets/dist.NFH2ueqJ.js.map: Write -build/app/assets/dist.fWJXhdfU.js: Write -build/app/assets/dist.fWJXhdfU.js.map: Write -build/app/assets/dist.gM5GTOSQ.js: Write -build/app/assets/dist.gM5GTOSQ.js.map: Write -build/app/assets/dist.mF0Q2E6x.js: Write -build/app/assets/dist.mF0Q2E6x.js.map: Write -build/app/assets/docker.DRpER2fN.js: Write -build/app/assets/docker.DRpER2fN.js.map: Write -build/app/assets/documents.CnHMLeiR.js: Write -build/app/assets/documents.CnHMLeiR.js.map: Write -build/app/assets/editor.BGFpW_nu.js: Write -build/app/assets/editor.BGFpW_nu.js.map: Write -build/app/assets/elixir.DcW53LpU.js: Write -build/app/assets/elixir.DcW53LpU.js.map: Write -build/app/assets/emoji.BV1169vl.js: Write -build/app/assets/emoji.BV1169vl.js.map: Write -build/app/assets/erDiagram-3M52JZNH.DCy9F1z7.js: Write -build/app/assets/erDiagram-3M52JZNH.DCy9F1z7.js.map: Write -build/app/assets/erb.B_B6O1si.js: Write -build/app/assets/erb.B_B6O1si.js.map: Write -build/app/assets/erlang.BPM5Qc99.js: Write -build/app/assets/erlang.BPM5Qc99.js.map: Write -build/app/assets/es.B2TYTIPr.js: Write -build/app/assets/es.B2TYTIPr.js.map: Write -build/app/assets/es.DJctT12U.js: Write -build/app/assets/es.DJctT12U.js.map: Write -build/app/assets/extensions.Den9lGJv.js: Write -build/app/assets/extensions.Den9lGJv.js.map: Write -build/app/assets/fast-deep-equal.Bftml-xO.js: Write -build/app/assets/fast-deep-equal.Bftml-xO.js.map: Write -build/app/assets/flowDiagram-KYDEHFYC.BpPn_29z.js: Write -build/app/assets/flowDiagram-KYDEHFYC.BpPn_29z.js.map: Write -build/app/assets/format.shf8vVN4.js: Write -build/app/assets/format.shf8vVN4.js.map: Write -build/app/assets/fractional-index._CtxWx9W.js: Write -build/app/assets/fractional-index._CtxWx9W.js.map: Write -build/app/assets/ganttDiagram-EK5VF46D.CMJGVPUZ.js: Write -build/app/assets/ganttDiagram-EK5VF46D.CMJGVPUZ.js.map: Write -build/app/assets/gitGraph-ZV4HHKMB.v4J8V0Tq.js: Write -build/app/assets/gitGraphDiagram-GW3U2K7C.BlyEIMxe.js: Write -build/app/assets/gitGraphDiagram-GW3U2K7C.BlyEIMxe.js.map: Write -build/app/assets/go.lhv0oNd4.js: Write -build/app/assets/go.lhv0oNd4.js.map: Write -build/app/assets/graphlib.BE1wgH5K.js: Write -build/app/assets/graphlib.BE1wgH5K.js.map: Write -build/app/assets/graphql.CaSD5lRP.js: Write -build/app/assets/graphql.CaSD5lRP.js.map: Write -build/app/assets/groovy.DqkEd-zo.js: Write -build/app/assets/groovy.DqkEd-zo.js.map: Write -build/app/assets/haskell.9-auqd-J.js: Write -build/app/assets/haskell.9-auqd-J.js.map: Write -build/app/assets/hcl.BaJEyyWQ.js: Write -build/app/assets/hcl.BaJEyyWQ.js.map: Write -build/app/assets/includes.BdoEZQqZ.js: Write -build/app/assets/includes.BdoEZQqZ.js.map: Write -build/app/assets/index.CQsPnCEr.js: Write -build/app/assets/index.CQsPnCEr.js.map: Write -build/app/assets/index.es.BljPmFkw.js: Write -build/app/assets/index.es.BljPmFkw.js.map: Write -build/app/assets/index.esm.BIJhLLtp.js: Write -build/app/assets/index.esm.BIJhLLtp.js.map: Write -build/app/assets/index.esm.CYWq8TUu.js: Write -build/app/assets/index.esm.CYWq8TUu.js.map: Write -build/app/assets/info-63CPKGFF.KYNam61q.js: Write -build/app/assets/infoDiagram-LHK5PUON.BlBADoJs.js: Write -build/app/assets/infoDiagram-LHK5PUON.BlBADoJs.js.map: Write -build/app/assets/ini.DmoutFIb.js: Write -build/app/assets/ini.DmoutFIb.js.map: Write -build/app/assets/init.9x-J6N5U.js: Write -build/app/assets/init.9x-J6N5U.js.map: Write -build/app/assets/integrations.CLxLZ2j6.js: Write -build/app/assets/integrations.CLxLZ2j6.js.map: Write -build/app/assets/isArrayLikeObject.B5CC6qnu.js: Write -build/app/assets/isArrayLikeObject.B5CC6qnu.js.map: Write -build/app/assets/isEmpty.Dwo49K0N.js: Write -build/app/assets/isEmpty.Dwo49K0N.js.map: Write -build/app/assets/isString.CUY1zGqy.js: Write -build/app/assets/isString.CUY1zGqy.js.map: Write -build/app/assets/java.BCRPzOy1.js: Write -build/app/assets/java.BCRPzOy1.js.map: Write -build/app/assets/java.CZ2wTfLy.js: Write -build/app/assets/javascript.BIDnWV5A.js: Write -build/app/assets/journeyDiagram-EWQZEKCU.kPn7Rnvm.js: Write -build/app/assets/journeyDiagram-EWQZEKCU.kPn7Rnvm.js.map: Write -build/app/assets/json.qmTKVSL5.js: Write -build/app/assets/json.qmTKVSL5.js.map: Write -build/app/assets/jsx.B4UPVK-z.js: Write -build/app/assets/jsx.BjNfLT19.js: Write -build/app/assets/jsx.BjNfLT19.js.map: Write -build/app/assets/kanban-definition-ZSS6B67P.CObQwAbA.js: Write -build/app/assets/kanban-definition-ZSS6B67P.CObQwAbA.js.map: Write -build/app/assets/katex.1H6oYqCB.css: Write -build/app/assets/katex.lwSJcEDT.js: Write -build/app/assets/kotlin.CSxmErLU.js: Write -build/app/assets/kotlin.CSxmErLU.js.map: Write -build/app/assets/kusto.C-9_uyHM.js: Write -build/app/assets/kusto.C-9_uyHM.js.map: Write -build/app/assets/language.BzVj8PjN.js: Write -build/app/assets/language.BzVj8PjN.js.map: Write -build/app/assets/line.BCVbXnFW.js: Write -build/app/assets/line.BCVbXnFW.js.map: Write -build/app/assets/linear.D1TgXhtw.js: Write -build/app/assets/linear.D1TgXhtw.js.map: Write -build/app/assets/lisp.BaAcHti4.js: Write -build/app/assets/lisp.BaAcHti4.js.map: Write -build/app/assets/lua.DHziOLWs.js: Write -build/app/assets/lua.DHziOLWs.js.map: Write -build/app/assets/makefile.BeI346cO.js: Write -build/app/assets/makefile.BeI346cO.js.map: Write -build/app/assets/markdown.BVifaPuw.js: Write -build/app/assets/markdown.BVifaPuw.js.map: Write -build/app/assets/markup-templating.jA5Jh-Tt.js: Write -build/app/assets/markup-templating.jA5Jh-Tt.js.map: Write -build/app/assets/markup.DK-4aqj_.js: Write -build/app/assets/math.B36Xw4GG.js: Write -build/app/assets/math.B36Xw4GG.js.map: Write -build/app/assets/mermaid-parser.core.BCwbdykx.js: Write -build/app/assets/mermaid-parser.core.BCwbdykx.js.map: Write -build/app/assets/mermaid.DmKIOsVc.js: Write -build/app/assets/mermaid.DmKIOsVc.js.map: Write -build/app/assets/mermaid.core.Dq7UiFFm.js: Write -build/app/assets/mermaid.core.Dq7UiFFm.js.map: Write -build/app/assets/mindmap-definition-6CBA2TL7.uOmgbaMy.js: Write -build/app/assets/mindmap-definition-6CBA2TL7.uOmgbaMy.js.map: Write -build/app/assets/motion.C5K7INgR.js: Write -build/app/assets/motion.C5K7INgR.js.map: Write -build/app/assets/navigation.DEFJtlR3.js: Write -build/app/assets/navigation.DEFJtlR3.js.map: Write -build/app/assets/nginx.DRleEnM6.js: Write -build/app/assets/nginx.DRleEnM6.js.map: Write -build/app/assets/nix.DBrK8jj1.js: Write -build/app/assets/nix.DBrK8jj1.js.map: Write -build/app/assets/objectivec.CY0XbvXk.js: Write -build/app/assets/objectivec.CY0XbvXk.js.map: Write -build/app/assets/ocaml.jD-2f36J.js: Write -build/app/assets/ocaml.jD-2f36J.js.map: Write -build/app/assets/ordinal.DJjXxV19.js: Write -build/app/assets/ordinal.DJjXxV19.js.map: Write -build/app/assets/packet-HUATNLJX.BsbMIBQj.js: Write -build/app/assets/perl.GUYuVIz3.js: Write -build/app/assets/perl.GUYuVIz3.js.map: Write -build/app/assets/php.DSTbGlPu.js: Write -build/app/assets/php.DSTbGlPu.js.map: Write -build/app/assets/pie-WTHONI2E.aQLYjzxr.js: Write -build/app/assets/pieDiagram-NIOCPIFQ.gNT8h87N.js: Write -build/app/assets/pieDiagram-NIOCPIFQ.gNT8h87N.js.map: Write -build/app/assets/powershell.BBnxGMsI.js: Write -build/app/assets/powershell.BBnxGMsI.js.map: Write -build/app/assets/promql.D6aH6qKz.js: Write -build/app/assets/promql.D6aH6qKz.js.map: Write -build/app/assets/protobuf.BnOU2lX6.js: Write -build/app/assets/protobuf.BnOU2lX6.js.map: Write -build/app/assets/python.Cgk2Urpf.js: Write -build/app/assets/python.Cgk2Urpf.js.map: Write -build/app/assets/quadrantDiagram-2OG54O6I.CEgYNwjX.js: Write -build/app/assets/quadrantDiagram-2OG54O6I.CEgYNwjX.js.map: Write -build/app/assets/r.Djlc7hzq.js: Write -build/app/assets/r.Djlc7hzq.js.map: Write -build/app/assets/radar-NJJJXTRR.DC2dKKy_.js: Write -build/app/assets/regex.U7-0kyI9.js: Write -build/app/assets/regex.U7-0kyI9.js.map: Write -build/app/assets/requirementDiagram-QOLK2EJ7.KHKh8jUP.js: Write -build/app/assets/requirementDiagram-QOLK2EJ7.KHKh8jUP.js.map: Write -build/app/assets/resize-observer.OlHBqQx8.js: Write -build/app/assets/resize-observer.OlHBqQx8.js.map: Write -build/app/assets/resolve-value.BOj4faII.js: Write -build/app/assets/resolve-value.BOj4faII.js.map: Write -build/app/assets/revisions.BLLcCGbe.js: Write -build/app/assets/revisions.BLLcCGbe.js.map: Write -build/app/assets/routeHelpers.BNqhhuZ_.js: Write -build/app/assets/routeHelpers.BNqhhuZ_.js.map: Write -build/app/assets/ruby.BPHw6mxy.js: Write -build/app/assets/ruby.BPHw6mxy.js.map: Write -build/app/assets/ruby.DGZ8yxUl.js: Write -build/app/assets/rust.BFBrdFm6.js: Write -build/app/assets/rust.BFBrdFm6.js.map: Write -build/app/assets/sankeyDiagram-4UZDY2LN.oB5FtqPu.js: Write -build/app/assets/sankeyDiagram-4UZDY2LN.oB5FtqPu.js.map: Write -build/app/assets/sass.lg7-Y7NO.js: Write -build/app/assets/sass.lg7-Y7NO.js.map: Write -build/app/assets/scala.Cy4mIGDF.js: Write -build/app/assets/scala.Cy4mIGDF.js.map: Write -build/app/assets/scss.DVc38Mnl.js: Write -build/app/assets/scss.DVc38Mnl.js.map: Write -build/app/assets/sections.D6RDW0CA.js: Write -build/app/assets/sections.D6RDW0CA.js.map: Write -build/app/assets/sequenceDiagram-SKLFT4DO.CSMRouat.js: Write -build/app/assets/sequenceDiagram-SKLFT4DO.CSMRouat.js.map: Write -build/app/assets/settings.BSEj5WI5.js: Write -build/app/assets/settings.BSEj5WI5.js.map: Write -build/app/assets/settings.CVLr_E2e.js: Write -build/app/assets/settings.CVLr_E2e.js.map: Write -build/app/assets/solidity.DviJmCDE.js: Write -build/app/assets/solidity.DviJmCDE.js.map: Write -build/app/assets/splunk-spl.CdV3r-32.js: Write -build/app/assets/splunk-spl.CdV3r-32.js.map: Write -build/app/assets/sql.CF5SujFQ.js: Write -build/app/assets/sql.CF5SujFQ.js.map: Write -build/app/assets/src.BxtNaNUu.js: Write -build/app/assets/src.BxtNaNUu.js.map: Write -build/app/assets/stateDiagram-MI5ZYTHO.Bq_9BeIy.js: Write -build/app/assets/stateDiagram-MI5ZYTHO.Bq_9BeIy.js.map: Write -build/app/assets/stateDiagram-v2-5AN5P6BG.cLCugQZ6.js: Write -build/app/assets/stateDiagram-v2-5AN5P6BG.cLCugQZ6.js.map: Write -build/app/assets/swift.BulLk9m7.js: Write -build/app/assets/swift.BulLk9m7.js.map: Write -build/app/assets/teams.Bw2EGp_L.js: Write -build/app/assets/teams.Bw2EGp_L.js.map: Write -build/app/assets/time.D05ktYTh.js: Write -build/app/assets/time.D05ktYTh.js.map: Write -build/app/assets/timeline-definition-MYPXXCX6.CaBjW9YB.js: Write -build/app/assets/timeline-definition-MYPXXCX6.CaBjW9YB.js.map: Write -build/app/assets/toml.B_Le73Qo.js: Write -build/app/assets/toml.B_Le73Qo.js.map: Write -build/app/assets/treemap-75Q7IDZK.DM709moN.js: Write -build/app/assets/tsx.CELNLfoj.js: Write -build/app/assets/tsx.CELNLfoj.js.map: Write -build/app/assets/typescript.CCa9tvuW.js: Write -build/app/assets/typescript.CCa9tvuW.js.map: Write -build/app/assets/typescript.CxD86D8t.js: Write -build/app/assets/urls.CvEG1eOq.js: Write -build/app/assets/urls.CvEG1eOq.js.map: Write -build/app/assets/useActionContext.D2ngFTOh.js: Write -build/app/assets/useActionContext.D2ngFTOh.js.map: Write -build/app/assets/useBoolean.BmbRUcLi.js: Write -build/app/assets/useBoolean.BmbRUcLi.js.map: Write -build/app/assets/useClickIntent.DlDWFaNn.js: Write -build/app/assets/useClickIntent.DlDWFaNn.js.map: Write -build/app/assets/useCommandBarActions.BtO7S0ni.js: Write -build/app/assets/useCommandBarActions.BtO7S0ni.js.map: Write -build/app/assets/useCurrentTeam.C1K3IM0Z.js: Write -build/app/assets/useCurrentTeam.C1K3IM0Z.js.map: Write -build/app/assets/useCurrentUser.DjL6NiLC.js: Write -build/app/assets/useCurrentUser.DjL6NiLC.js.map: Write -build/app/assets/useEmbeds.DVF0SwvX.js: Write -build/app/assets/useEmbeds.DVF0SwvX.js.map: Write -build/app/assets/useFocusedComment.BfD3-7Hb.js: Write -build/app/assets/useFocusedComment.BfD3-7Hb.js.map: Write -build/app/assets/useIdle.D__AHEzZ.js: Write -build/app/assets/useIdle.D__AHEzZ.js.map: Write -build/app/assets/useImportDocument.BBU2WHGo.js: Write -build/app/assets/useImportDocument.BBU2WHGo.js.map: Write -build/app/assets/useKeyDown.BBdNGFj0.js: Write -build/app/assets/useKeyDown.BBdNGFj0.js.map: Write -build/app/assets/useLocationSidebarContext.Dv4qjgG6.js: Write -build/app/assets/useLocationSidebarContext.Dv4qjgG6.js.map: Write -build/app/assets/useMenuAction.CFOyQ4-O.js: Write -build/app/assets/useMenuAction.CFOyQ4-O.js.map: Write -build/app/assets/useMenuContext.DLtjCPPm.js: Write -build/app/assets/useMenuContext.DLtjCPPm.js.map: Write -build/app/assets/useMenuState.D800f-Mo.js: Write -build/app/assets/useMenuState.D800f-Mo.js.map: Write -build/app/assets/useOnClickOutside.B9IuwCVM.js: Write -build/app/assets/useOnClickOutside.B9IuwCVM.js.map: Write -build/app/assets/usePageVisibility.CekUbTUR.js: Write -build/app/assets/usePageVisibility.CekUbTUR.js.map: Write -build/app/assets/usePaginatedRequest.Bunj0UfP.js: Write -build/app/assets/usePaginatedRequest.Bunj0UfP.js.map: Write -build/app/assets/usePolicy.DxZBczBT.js: Write -build/app/assets/usePolicy.DxZBczBT.js.map: Write -build/app/assets/useRequest.Dd99KxuI.js: Write -build/app/assets/useRequest.Dd99KxuI.js.map: Write -build/app/assets/useSettingsConfig.C78yZf6m.js: Write -build/app/assets/useSettingsConfig.C78yZf6m.js.map: Write -build/app/assets/useTextStats.5c715Zhs.js: Write -build/app/assets/useTextStats.5c715Zhs.js.map: Write -build/app/assets/useUserLocale.CnEWDhcu.js: Write -build/app/assets/useUserLocale.CnEWDhcu.js.map: Write -build/app/assets/users.nyTFwg0K.js: Write -build/app/assets/users.nyTFwg0K.js.map: Write -build/app/assets/vbnet.DS_1x-Hi.js: Write -build/app/assets/vbnet.DS_1x-Hi.js.map: Write -build/app/assets/verilog.DHxVJzvp.js: Write -build/app/assets/verilog.DHxVJzvp.js.map: Write -build/app/assets/vhdl.RZcK0e6K.js: Write -build/app/assets/vhdl.RZcK0e6K.js.map: Write -build/app/assets/withStores.n57_ZG_7.js: Write -build/app/assets/withStores.n57_ZG_7.js.map: Write -build/app/assets/xychartDiagram-H2YORKM3.XpfijHob.js: Write -build/app/assets/xychartDiagram-H2YORKM3.XpfijHob.js.map: Write -build/app/assets/yaml.B2APYX2k.js: Write -build/app/assets/yaml.B2APYX2k.js.map: Write -build/app/assets/zig.Bv9gRpF9.js: Write -build/app/assets/zig.Bv9gRpF9.js.map: Write -build/app/error.dev.html: Write -build/app/error.prod.html: Write -build/app/images: ReadDir -build/app/images/Icon-1024.png: Write -build/app/images/abstract.png: Write -build/app/images/airtable.png: Write -build/app/images/apple-touch-icon.png: Write -build/app/images/berrycast.png: Write -build/app/images/bilibili.png: Write -build/app/images/camunda.png: Write -build/app/images/canva.png: Write -build/app/images/cawemo.png: Write -build/app/images/clickup.png: Write -build/app/images/codepen.png: Write -build/app/images/confluence.png: Write -build/app/images/dbdiagram.png: Write -build/app/images/descript.png: Write -build/app/images/diagrams.png: Write -build/app/images/dropbox.png: Write -build/app/images/favicon-16.png: Write -build/app/images/favicon-32.png: Write -build/app/images/figma.png: Write -build/app/images/framer.png: Write -build/app/images/github-gist.png: Write -build/app/images/gitlab.png: Write -build/app/images/gliffy.png: Write -build/app/images/google-calendar.png: Write -build/app/images/google-docs.png: Write -build/app/images/google-drawings.png: Write -build/app/images/google-drive.png: Write -build/app/images/google-forms.png: Write -build/app/images/google-lookerstudio.png: Write -build/app/images/google-maps.png: Write -build/app/images/google-sheets.png: Write -build/app/images/google-slides.png: Write -build/app/images/grist.png: Write -build/app/images/icon-192.png: Write -build/app/images/icon-512.png: Write -build/app/images/instagram.png: Write -build/app/images/invision.png: Write -build/app/images/jsfiddle.png: Write -build/app/images/linkedin.png: Write -build/app/images/loom.png: Write -build/app/images/lucidchart.png: Write -build/app/images/marvel.png: Write -build/app/images/mermaidjs.png: Write -build/app/images/mindmeister.png: Write -build/app/images/miro.png: Write -build/app/images/mode-analytics.png: Write -build/app/images/notion.png: Write -build/app/images/otter.png: Write -build/app/images/pinterest.png: Write -build/app/images/pitch.png: Write -build/app/images/prezi.png: Write -build/app/images/scribe.png: Write -build/app/images/slack.png: Write -build/app/images/smartsuite.png: Write -build/app/images/spotify.png: Write -build/app/images/tella.png: Write -build/app/images/tldraw.png: Write -build/app/images/trello.png: Write -build/app/images/typeform.png: Write -build/app/images/valtown.png: Write -build/app/images/vimeo.png: Write -build/app/images/whimsical.png: Write -build/app/images/youtube.png: Write -build/app/images/zapier.png: Write -build/app/index.html: Write -build/app/sw.js: Write -build/app/sw.js.map: Write -build/app/webpack-stats.json: Write -build/app/workbox-d4edd7c5.js: Write -build/app/workbox-d4edd7c5.js.map: Write -node_modules/.bin: ReadDir -node_modules/.bin/vite: Read -node_modules/@apideck/better-ajv-errors/dist/better-ajv-errors.cjs.production.min.js: Read -node_modules/@apideck/better-ajv-errors/dist/index.js: Read -node_modules/@apideck/better-ajv-errors/dist/package.json: Read -node_modules/@apideck/better-ajv-errors/package.json: Read -node_modules/@babel/code-frame/lib/index.js: Read -node_modules/@babel/code-frame/lib/package.json: Read -node_modules/@babel/code-frame/package.json: Read -node_modules/@babel/compat-data/data/native-modules.json: Read -node_modules/@babel/compat-data/data/overlapping-plugins.json: Read -node_modules/@babel/compat-data/data/plugin-bugfixes.json: Read -node_modules/@babel/compat-data/data/plugins.json: Read -node_modules/@babel/compat-data/native-modules.js: Read -node_modules/@babel/compat-data/overlapping-plugins.js: Read -node_modules/@babel/compat-data/package.json: Read -node_modules/@babel/compat-data/plugin-bugfixes.js: Read -node_modules/@babel/compat-data/plugins.js: Read -node_modules/@babel/core/lib/config/caching.js: Read -node_modules/@babel/core/lib/config/config-chain.js: Read -node_modules/@babel/core/lib/config/config-descriptors.js: Read -node_modules/@babel/core/lib/config/files/configuration.js: Read -node_modules/@babel/core/lib/config/files/import.cjs: Read -node_modules/@babel/core/lib/config/files/index.js: Read -node_modules/@babel/core/lib/config/files/module-types.js: Read -node_modules/@babel/core/lib/config/files/package.js: Read -node_modules/@babel/core/lib/config/files/package.json: Read -node_modules/@babel/core/lib/config/files/plugins.js: Read -node_modules/@babel/core/lib/config/files/utils.js: Read -node_modules/@babel/core/lib/config/full.js: Read -node_modules/@babel/core/lib/config/helpers/config-api.js: Read -node_modules/@babel/core/lib/config/helpers/deep-array.js: Read -node_modules/@babel/core/lib/config/helpers/environment.js: Read -node_modules/@babel/core/lib/config/helpers/package.json: Read -node_modules/@babel/core/lib/config/index.js: Read -node_modules/@babel/core/lib/config/item.js: Read -node_modules/@babel/core/lib/config/package.json: Read -node_modules/@babel/core/lib/config/partial.js: Read -node_modules/@babel/core/lib/config/pattern-to-regex.js: Read -node_modules/@babel/core/lib/config/plugin.js: Read -node_modules/@babel/core/lib/config/printer.js: Read -node_modules/@babel/core/lib/config/resolve-targets.js: Read -node_modules/@babel/core/lib/config/util.js: Read -node_modules/@babel/core/lib/config/validation/option-assertions.js: Read -node_modules/@babel/core/lib/config/validation/options.js: Read -node_modules/@babel/core/lib/config/validation/package.json: Read -node_modules/@babel/core/lib/config/validation/plugins.js: Read -node_modules/@babel/core/lib/config/validation/removed.js: Read -node_modules/@babel/core/lib/errors/config-error.js: Read -node_modules/@babel/core/lib/errors/package.json: Read -node_modules/@babel/core/lib/errors/rewrite-stack-trace.js: Read -node_modules/@babel/core/lib/gensync-utils/async.js: Read -node_modules/@babel/core/lib/gensync-utils/fs.js: Read -node_modules/@babel/core/lib/gensync-utils/functional.js: Read -node_modules/@babel/core/lib/gensync-utils/package.json: Read -node_modules/@babel/core/lib/index.js: Read -node_modules/@babel/core/lib/package.json: Read -node_modules/@babel/core/lib/parse.js: Read -node_modules/@babel/core/lib/parser/index.js: Read -node_modules/@babel/core/lib/parser/package.json: Read -node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js: Read -node_modules/@babel/core/lib/parser/util/package.json: Read -node_modules/@babel/core/lib/tools/build-external-helpers.js: Read -node_modules/@babel/core/lib/tools/package.json: Read -node_modules/@babel/core/lib/transform-ast.js: Read -node_modules/@babel/core/lib/transform-file.js: Read -node_modules/@babel/core/lib/transform.js: Read -node_modules/@babel/core/lib/transformation/block-hoist-plugin.js: Read -node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs: Read -node_modules/@babel/core/lib/transformation/file/file.js: Read -node_modules/@babel/core/lib/transformation/file/generate.js: Read -node_modules/@babel/core/lib/transformation/file/merge-map.js: Read -node_modules/@babel/core/lib/transformation/file/package.json: Read -node_modules/@babel/core/lib/transformation/index.js: Read -node_modules/@babel/core/lib/transformation/normalize-file.js: Read -node_modules/@babel/core/lib/transformation/normalize-opts.js: Read -node_modules/@babel/core/lib/transformation/package.json: Read -node_modules/@babel/core/lib/transformation/plugin-pass.js: Read -node_modules/@babel/core/lib/transformation/util/clone-deep.js: Read -node_modules/@babel/core/lib/transformation/util/package.json: Read -node_modules/@babel/core/lib/vendor/import-meta-resolve.js: Read -node_modules/@babel/core/lib/vendor/package.json: Read -node_modules/@babel/core/node_modules/@babel/generator/package.json: Read -node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/package.json: Read -node_modules/@babel/core/node_modules/@babel/helpers/package.json: Read -node_modules/@babel/core/node_modules/@babel/parser/package.json: Read -node_modules/@babel/core/node_modules/@babel/template/package.json: Read -node_modules/@babel/core/node_modules/@babel/traverse/package.json: Read -node_modules/@babel/core/node_modules/@babel/types/package.json: Read -node_modules/@babel/core/node_modules/debug/package.json: Read -node_modules/@babel/core/node_modules/gensync/package.json: Read -node_modules/@babel/core/node_modules/semver/package.json: Read -node_modules/@babel/core/node_modules/semver/semver.js: Read -node_modules/@babel/core/package.json: Read -node_modules/@babel/generator/lib/buffer.js: Read -node_modules/@babel/generator/lib/generators/base.js: Read -node_modules/@babel/generator/lib/generators/classes.js: Read -node_modules/@babel/generator/lib/generators/deprecated.js: Read -node_modules/@babel/generator/lib/generators/expressions.js: Read -node_modules/@babel/generator/lib/generators/flow.js: Read -node_modules/@babel/generator/lib/generators/index.js: Read -node_modules/@babel/generator/lib/generators/jsx.js: Read -node_modules/@babel/generator/lib/generators/methods.js: Read -node_modules/@babel/generator/lib/generators/modules.js: Read -node_modules/@babel/generator/lib/generators/package.json: Read -node_modules/@babel/generator/lib/generators/statements.js: Read -node_modules/@babel/generator/lib/generators/template-literals.js: Read -node_modules/@babel/generator/lib/generators/types.js: Read -node_modules/@babel/generator/lib/generators/typescript.js: Read -node_modules/@babel/generator/lib/index.js: Read -node_modules/@babel/generator/lib/node/index.js: Read -node_modules/@babel/generator/lib/node/package.json: Read -node_modules/@babel/generator/lib/node/parentheses.js: Read -node_modules/@babel/generator/lib/node/whitespace.js: Read -node_modules/@babel/generator/lib/package.json: Read -node_modules/@babel/generator/lib/printer.js: Read -node_modules/@babel/generator/lib/source-map.js: Read -node_modules/@babel/generator/lib/token-map.js: Read -node_modules/@babel/generator/node_modules/@babel/types/package.json: Read -node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping/package.json: Read -node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping/package.json: Read -node_modules/@babel/generator/node_modules/jsesc/package.json: Read -node_modules/@babel/generator/package.json: Read -node_modules/@babel/helper-annotate-as-pure/lib/index.js: Read -node_modules/@babel/helper-annotate-as-pure/lib/package.json: Read -node_modules/@babel/helper-annotate-as-pure/package.json: Read -node_modules/@babel/helper-compilation-targets/lib/debug.js: Read -node_modules/@babel/helper-compilation-targets/lib/filter-items.js: Read -node_modules/@babel/helper-compilation-targets/lib/index.js: Read -node_modules/@babel/helper-compilation-targets/lib/options.js: Read -node_modules/@babel/helper-compilation-targets/lib/package.json: Read -node_modules/@babel/helper-compilation-targets/lib/pretty.js: Read -node_modules/@babel/helper-compilation-targets/lib/targets.js: Read -node_modules/@babel/helper-compilation-targets/lib/utils.js: Read -node_modules/@babel/helper-compilation-targets/node_modules/@babel/compat-data/package.json: Read -node_modules/@babel/helper-compilation-targets/node_modules/@babel/helper-validator-option/package.json: Read -node_modules/@babel/helper-compilation-targets/node_modules/browserslist/package.json: Read -node_modules/@babel/helper-compilation-targets/node_modules/lru-cache/index.js: Read -node_modules/@babel/helper-compilation-targets/node_modules/lru-cache/package.json: Read -node_modules/@babel/helper-compilation-targets/node_modules/semver/package.json: Read -node_modules/@babel/helper-compilation-targets/node_modules/semver/semver.js: Read -node_modules/@babel/helper-compilation-targets/node_modules/yallist/package.json: Read -node_modules/@babel/helper-compilation-targets/package.json: Read -node_modules/@babel/helper-create-class-features-plugin/lib/decorators-2018-09.js: Read -node_modules/@babel/helper-create-class-features-plugin/lib/decorators.js: Read -node_modules/@babel/helper-create-class-features-plugin/lib/features.js: Read -node_modules/@babel/helper-create-class-features-plugin/lib/fields.js: Read -node_modules/@babel/helper-create-class-features-plugin/lib/index.js: Read -node_modules/@babel/helper-create-class-features-plugin/lib/misc.js: Read -node_modules/@babel/helper-create-class-features-plugin/lib/package.json: Read -node_modules/@babel/helper-create-class-features-plugin/lib/typescript.js: Read -node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/core/package.json: Read -node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure/package.json: Read -node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-member-expression-to-functions/package.json: Read -node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-optimise-call-expression/package.json: Read -node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-replace-supers/package.json: Read -node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-skip-transparent-expression-wrappers/package.json: Read -node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/traverse/package.json: Read -node_modules/@babel/helper-create-class-features-plugin/node_modules/semver/package.json: Read -node_modules/@babel/helper-create-class-features-plugin/node_modules/semver/semver.js: Read -node_modules/@babel/helper-create-class-features-plugin/package.json: Read -node_modules/@babel/helper-create-regexp-features-plugin/lib/features.js: Read -node_modules/@babel/helper-create-regexp-features-plugin/lib/index.js: Read -node_modules/@babel/helper-create-regexp-features-plugin/lib/package.json: Read -node_modules/@babel/helper-create-regexp-features-plugin/lib/util.js: Read -node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/core/package.json: Read -node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure/package.json: Read -node_modules/@babel/helper-create-regexp-features-plugin/node_modules/regexpu-core/package.json: Read -node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver/package.json: Read -node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver/semver.js: Read -node_modules/@babel/helper-create-regexp-features-plugin/package.json: Read -node_modules/@babel/helper-globals/data/browser-upper.json: Read -node_modules/@babel/helper-globals/data/builtin-lower.json: Read -node_modules/@babel/helper-globals/data/builtin-upper.json: Read -node_modules/@babel/helper-globals/package.json: Read -node_modules/@babel/helper-member-expression-to-functions/lib/index.js: Read -node_modules/@babel/helper-member-expression-to-functions/lib/package.json: Read -node_modules/@babel/helper-member-expression-to-functions/package.json: Read -node_modules/@babel/helper-module-imports/lib/import-builder.js: Read -node_modules/@babel/helper-module-imports/lib/import-injector.js: Read -node_modules/@babel/helper-module-imports/lib/index.js: Read -node_modules/@babel/helper-module-imports/lib/is-module.js: Read -node_modules/@babel/helper-module-imports/lib/package.json: Read -node_modules/@babel/helper-module-imports/package.json: Read -node_modules/@babel/helper-module-transforms/lib/dynamic-import.js: Read -node_modules/@babel/helper-module-transforms/lib/get-module-name.js: Read -node_modules/@babel/helper-module-transforms/lib/index.js: Read -node_modules/@babel/helper-module-transforms/lib/lazy-modules.js: Read -node_modules/@babel/helper-module-transforms/lib/normalize-and-load-metadata.js: Read -node_modules/@babel/helper-module-transforms/lib/package.json: Read -node_modules/@babel/helper-module-transforms/lib/rewrite-live-references.js: Read -node_modules/@babel/helper-module-transforms/lib/rewrite-this.js: Read -node_modules/@babel/helper-module-transforms/package.json: Read -node_modules/@babel/helper-optimise-call-expression/lib/index.js: Read -node_modules/@babel/helper-optimise-call-expression/lib/package.json: Read -node_modules/@babel/helper-optimise-call-expression/package.json: Read -node_modules/@babel/helper-plugin-utils/lib/index.js: Read -node_modules/@babel/helper-plugin-utils/lib/package.json: Read -node_modules/@babel/helper-plugin-utils/package.json: Read -node_modules/@babel/helper-remap-async-to-generator/lib/index.js: Read -node_modules/@babel/helper-remap-async-to-generator/lib/package.json: Read -node_modules/@babel/helper-remap-async-to-generator/package.json: Read -node_modules/@babel/helper-replace-supers/lib/index.js: Read -node_modules/@babel/helper-replace-supers/lib/package.json: Read -node_modules/@babel/helper-replace-supers/package.json: Read -node_modules/@babel/helper-skip-transparent-expression-wrappers/lib/index.js: Read -node_modules/@babel/helper-skip-transparent-expression-wrappers/lib/package.json: Read -node_modules/@babel/helper-skip-transparent-expression-wrappers/package.json: Read -node_modules/@babel/helper-string-parser/lib/index.js: Read -node_modules/@babel/helper-string-parser/lib/package.json: Read -node_modules/@babel/helper-string-parser/package.json: Read -node_modules/@babel/helper-validator-identifier/lib/identifier.js: Read -node_modules/@babel/helper-validator-identifier/lib/index.js: Read -node_modules/@babel/helper-validator-identifier/lib/keyword.js: Read -node_modules/@babel/helper-validator-identifier/lib/package.json: Read -node_modules/@babel/helper-validator-identifier/package.json: Read -node_modules/@babel/helper-validator-option/lib/find-suggestion.js: Read -node_modules/@babel/helper-validator-option/lib/index.js: Read -node_modules/@babel/helper-validator-option/lib/package.json: Read -node_modules/@babel/helper-validator-option/lib/validator.js: Read -node_modules/@babel/helper-validator-option/package.json: Read -node_modules/@babel/helper-wrap-function/lib/index.js: Read -node_modules/@babel/helper-wrap-function/lib/package.json: Read -node_modules/@babel/helper-wrap-function/package.json: Read -node_modules/@babel/helpers/lib/helpers-generated.js: Read -node_modules/@babel/helpers/lib/index.js: Read -node_modules/@babel/helpers/lib/package.json: Read -node_modules/@babel/helpers/package.json: Read -node_modules/@babel/package.json: Read -node_modules/@babel/parser/lib/index.js: Read -node_modules/@babel/parser/lib/package.json: Read -node_modules/@babel/parser/package.json: Read -node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key/lib/index.js: Read -node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key/lib/package.json: Read -node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key/package.json: Read -node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope/lib/index.js: Read -node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope/lib/package.json: Read -node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope/package.json: Read -node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/lib/index.js: Read -node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/lib/package.json: Read -node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/package.json: Read -node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/lib/index.js: Read -node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/lib/package.json: Read -node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/package.json: Read -node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/lib/index.js: Read -node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/lib/package.json: Read -node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/package.json: Read -node_modules/@babel/plugin-syntax-import-assertions/lib/index.js: Read -node_modules/@babel/plugin-syntax-import-assertions/lib/package.json: Read -node_modules/@babel/plugin-syntax-import-assertions/package.json: Read -node_modules/@babel/plugin-syntax-import-attributes/lib/index.js: Read -node_modules/@babel/plugin-syntax-import-attributes/lib/package.json: Read -node_modules/@babel/plugin-syntax-import-attributes/package.json: Read -node_modules/@babel/plugin-transform-arrow-functions/lib/index.js: Read -node_modules/@babel/plugin-transform-arrow-functions/lib/package.json: Read -node_modules/@babel/plugin-transform-arrow-functions/package.json: Read -node_modules/@babel/plugin-transform-async-generator-functions/lib/for-await.js: Read -node_modules/@babel/plugin-transform-async-generator-functions/lib/index.js: Read -node_modules/@babel/plugin-transform-async-generator-functions/lib/package.json: Read -node_modules/@babel/plugin-transform-async-generator-functions/package.json: Read -node_modules/@babel/plugin-transform-async-to-generator/lib/index.js: Read -node_modules/@babel/plugin-transform-async-to-generator/lib/package.json: Read -node_modules/@babel/plugin-transform-async-to-generator/package.json: Read -node_modules/@babel/plugin-transform-block-scoped-functions/lib/index.js: Read -node_modules/@babel/plugin-transform-block-scoped-functions/lib/package.json: Read -node_modules/@babel/plugin-transform-block-scoped-functions/package.json: Read -node_modules/@babel/plugin-transform-block-scoping/lib/annex-B_3_3.js: Read -node_modules/@babel/plugin-transform-block-scoping/lib/index.js: Read -node_modules/@babel/plugin-transform-block-scoping/lib/loop.js: Read -node_modules/@babel/plugin-transform-block-scoping/lib/package.json: Read -node_modules/@babel/plugin-transform-block-scoping/lib/validation.js: Read -node_modules/@babel/plugin-transform-block-scoping/package.json: Read -node_modules/@babel/plugin-transform-class-properties/lib/index.js: Read -node_modules/@babel/plugin-transform-class-properties/lib/package.json: Read -node_modules/@babel/plugin-transform-class-properties/package.json: Read -node_modules/@babel/plugin-transform-class-static-block/lib/index.js: Read -node_modules/@babel/plugin-transform-class-static-block/lib/package.json: Read -node_modules/@babel/plugin-transform-class-static-block/package.json: Read -node_modules/@babel/plugin-transform-classes/lib/index.js: Read -node_modules/@babel/plugin-transform-classes/lib/inline-callSuper-helpers.js: Read -node_modules/@babel/plugin-transform-classes/lib/package.json: Read -node_modules/@babel/plugin-transform-classes/lib/transformClass.js: Read -node_modules/@babel/plugin-transform-classes/package.json: Read -node_modules/@babel/plugin-transform-computed-properties/lib/index.js: Read -node_modules/@babel/plugin-transform-computed-properties/lib/package.json: Read -node_modules/@babel/plugin-transform-computed-properties/package.json: Read -node_modules/@babel/plugin-transform-destructuring/lib/index.js: Read -node_modules/@babel/plugin-transform-destructuring/lib/package.json: Read -node_modules/@babel/plugin-transform-destructuring/package.json: Read -node_modules/@babel/plugin-transform-dotall-regex/lib/index.js: Read -node_modules/@babel/plugin-transform-dotall-regex/lib/package.json: Read -node_modules/@babel/plugin-transform-dotall-regex/package.json: Read -node_modules/@babel/plugin-transform-duplicate-keys/lib/index.js: Read -node_modules/@babel/plugin-transform-duplicate-keys/lib/package.json: Read -node_modules/@babel/plugin-transform-duplicate-keys/package.json: Read -node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex/lib/index.js: Read -node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex/lib/package.json: Read -node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex/package.json: Read -node_modules/@babel/plugin-transform-dynamic-import/lib/index.js: Read -node_modules/@babel/plugin-transform-dynamic-import/lib/package.json: Read -node_modules/@babel/plugin-transform-dynamic-import/package.json: Read -node_modules/@babel/plugin-transform-explicit-resource-management/lib/index.js: Read -node_modules/@babel/plugin-transform-explicit-resource-management/lib/package.json: Read -node_modules/@babel/plugin-transform-explicit-resource-management/package.json: Read -node_modules/@babel/plugin-transform-exponentiation-operator/lib/index.js: Read -node_modules/@babel/plugin-transform-exponentiation-operator/lib/package.json: Read -node_modules/@babel/plugin-transform-exponentiation-operator/package.json: Read -node_modules/@babel/plugin-transform-export-namespace-from/lib/index.js: Read -node_modules/@babel/plugin-transform-export-namespace-from/lib/package.json: Read -node_modules/@babel/plugin-transform-export-namespace-from/package.json: Read -node_modules/@babel/plugin-transform-for-of/lib/index.js: Read -node_modules/@babel/plugin-transform-for-of/lib/no-helper-implementation.js: Read -node_modules/@babel/plugin-transform-for-of/lib/package.json: Read -node_modules/@babel/plugin-transform-for-of/package.json: Read -node_modules/@babel/plugin-transform-function-name/lib/index.js: Read -node_modules/@babel/plugin-transform-function-name/lib/package.json: Read -node_modules/@babel/plugin-transform-function-name/package.json: Read -node_modules/@babel/plugin-transform-json-strings/lib/index.js: Read -node_modules/@babel/plugin-transform-json-strings/lib/package.json: Read -node_modules/@babel/plugin-transform-json-strings/package.json: Read -node_modules/@babel/plugin-transform-literals/lib/index.js: Read -node_modules/@babel/plugin-transform-literals/lib/package.json: Read -node_modules/@babel/plugin-transform-literals/package.json: Read -node_modules/@babel/plugin-transform-logical-assignment-operators/lib/index.js: Read -node_modules/@babel/plugin-transform-logical-assignment-operators/lib/package.json: Read -node_modules/@babel/plugin-transform-logical-assignment-operators/package.json: Read -node_modules/@babel/plugin-transform-member-expression-literals/lib/index.js: Read -node_modules/@babel/plugin-transform-member-expression-literals/lib/package.json: Read -node_modules/@babel/plugin-transform-member-expression-literals/package.json: Read -node_modules/@babel/plugin-transform-modules-amd/lib/index.js: Read -node_modules/@babel/plugin-transform-modules-amd/lib/package.json: Read -node_modules/@babel/plugin-transform-modules-amd/package.json: Read -node_modules/@babel/plugin-transform-modules-commonjs/lib/dynamic-import.js: Read -node_modules/@babel/plugin-transform-modules-commonjs/lib/hooks.js: Read -node_modules/@babel/plugin-transform-modules-commonjs/lib/index.js: Read -node_modules/@babel/plugin-transform-modules-commonjs/lib/lazy.js: Read -node_modules/@babel/plugin-transform-modules-commonjs/lib/package.json: Read -node_modules/@babel/plugin-transform-modules-commonjs/package.json: Read -node_modules/@babel/plugin-transform-modules-systemjs/lib/index.js: Read -node_modules/@babel/plugin-transform-modules-systemjs/lib/package.json: Read -node_modules/@babel/plugin-transform-modules-systemjs/package.json: Read -node_modules/@babel/plugin-transform-modules-umd/lib/index.js: Read -node_modules/@babel/plugin-transform-modules-umd/lib/package.json: Read -node_modules/@babel/plugin-transform-modules-umd/package.json: Read -node_modules/@babel/plugin-transform-named-capturing-groups-regex/lib/index.js: Read -node_modules/@babel/plugin-transform-named-capturing-groups-regex/lib/package.json: Read -node_modules/@babel/plugin-transform-named-capturing-groups-regex/package.json: Read -node_modules/@babel/plugin-transform-new-target/lib/index.js: Read -node_modules/@babel/plugin-transform-new-target/lib/package.json: Read -node_modules/@babel/plugin-transform-new-target/package.json: Read -node_modules/@babel/plugin-transform-nullish-coalescing-operator/lib/index.js: Read -node_modules/@babel/plugin-transform-nullish-coalescing-operator/lib/package.json: Read -node_modules/@babel/plugin-transform-nullish-coalescing-operator/package.json: Read -node_modules/@babel/plugin-transform-numeric-separator/lib/index.js: Read -node_modules/@babel/plugin-transform-numeric-separator/lib/package.json: Read -node_modules/@babel/plugin-transform-numeric-separator/package.json: Read -node_modules/@babel/plugin-transform-object-rest-spread/lib/index.js: Read -node_modules/@babel/plugin-transform-object-rest-spread/lib/package.json: Read -node_modules/@babel/plugin-transform-object-rest-spread/package.json: Read -node_modules/@babel/plugin-transform-object-super/lib/index.js: Read -node_modules/@babel/plugin-transform-object-super/lib/package.json: Read -node_modules/@babel/plugin-transform-object-super/package.json: Read -node_modules/@babel/plugin-transform-optional-catch-binding/lib/index.js: Read -node_modules/@babel/plugin-transform-optional-catch-binding/lib/package.json: Read -node_modules/@babel/plugin-transform-optional-catch-binding/package.json: Read -node_modules/@babel/plugin-transform-optional-chaining/lib/index.js: Read -node_modules/@babel/plugin-transform-optional-chaining/lib/package.json: Read -node_modules/@babel/plugin-transform-optional-chaining/package.json: Read -node_modules/@babel/plugin-transform-parameters/lib/index.js: Read -node_modules/@babel/plugin-transform-parameters/lib/package.json: Read -node_modules/@babel/plugin-transform-parameters/lib/params.js: Read -node_modules/@babel/plugin-transform-parameters/lib/rest.js: Read -node_modules/@babel/plugin-transform-parameters/lib/shadow-utils.js: Read -node_modules/@babel/plugin-transform-parameters/package.json: Read -node_modules/@babel/plugin-transform-private-methods/lib/index.js: Read -node_modules/@babel/plugin-transform-private-methods/lib/package.json: Read -node_modules/@babel/plugin-transform-private-methods/package.json: Read -node_modules/@babel/plugin-transform-private-property-in-object/lib/index.js: Read -node_modules/@babel/plugin-transform-private-property-in-object/lib/package.json: Read -node_modules/@babel/plugin-transform-private-property-in-object/package.json: Read -node_modules/@babel/plugin-transform-property-literals/lib/index.js: Read -node_modules/@babel/plugin-transform-property-literals/lib/package.json: Read -node_modules/@babel/plugin-transform-property-literals/package.json: Read -node_modules/@babel/plugin-transform-regenerator/lib/index.js: Read -node_modules/@babel/plugin-transform-regenerator/lib/package.json: Read -node_modules/@babel/plugin-transform-regenerator/lib/regenerator/emit.js: Read -node_modules/@babel/plugin-transform-regenerator/lib/regenerator/hoist.js: Read -node_modules/@babel/plugin-transform-regenerator/lib/regenerator/leap.js: Read -node_modules/@babel/plugin-transform-regenerator/lib/regenerator/meta.js: Read -node_modules/@babel/plugin-transform-regenerator/lib/regenerator/package.json: Read -node_modules/@babel/plugin-transform-regenerator/lib/regenerator/replaceShorthandObjectMethod.js: Read -node_modules/@babel/plugin-transform-regenerator/lib/regenerator/util.js: Read -node_modules/@babel/plugin-transform-regenerator/lib/regenerator/visit.js: Read -node_modules/@babel/plugin-transform-regenerator/package.json: Read -node_modules/@babel/plugin-transform-regexp-modifiers/lib/index.js: Read -node_modules/@babel/plugin-transform-regexp-modifiers/lib/package.json: Read -node_modules/@babel/plugin-transform-regexp-modifiers/package.json: Read -node_modules/@babel/plugin-transform-reserved-words/lib/index.js: Read -node_modules/@babel/plugin-transform-reserved-words/lib/package.json: Read -node_modules/@babel/plugin-transform-reserved-words/package.json: Read -node_modules/@babel/plugin-transform-shorthand-properties/lib/index.js: Read -node_modules/@babel/plugin-transform-shorthand-properties/lib/package.json: Read -node_modules/@babel/plugin-transform-shorthand-properties/package.json: Read -node_modules/@babel/plugin-transform-spread/lib/index.js: Read -node_modules/@babel/plugin-transform-spread/lib/package.json: Read -node_modules/@babel/plugin-transform-spread/package.json: Read -node_modules/@babel/plugin-transform-sticky-regex/lib/index.js: Read -node_modules/@babel/plugin-transform-sticky-regex/lib/package.json: Read -node_modules/@babel/plugin-transform-sticky-regex/package.json: Read -node_modules/@babel/plugin-transform-template-literals/lib/index.js: Read -node_modules/@babel/plugin-transform-template-literals/lib/package.json: Read -node_modules/@babel/plugin-transform-template-literals/package.json: Read -node_modules/@babel/plugin-transform-typeof-symbol/lib/index.js: Read -node_modules/@babel/plugin-transform-typeof-symbol/lib/package.json: Read -node_modules/@babel/plugin-transform-typeof-symbol/package.json: Read -node_modules/@babel/plugin-transform-unicode-escapes/lib/index.js: Read -node_modules/@babel/plugin-transform-unicode-escapes/lib/package.json: Read -node_modules/@babel/plugin-transform-unicode-escapes/package.json: Read -node_modules/@babel/plugin-transform-unicode-property-regex/lib/index.js: Read -node_modules/@babel/plugin-transform-unicode-property-regex/lib/package.json: Read -node_modules/@babel/plugin-transform-unicode-property-regex/package.json: Read -node_modules/@babel/plugin-transform-unicode-regex/lib/index.js: Read -node_modules/@babel/plugin-transform-unicode-regex/lib/package.json: Read -node_modules/@babel/plugin-transform-unicode-regex/package.json: Read -node_modules/@babel/plugin-transform-unicode-sets-regex/lib/index.js: Read -node_modules/@babel/plugin-transform-unicode-sets-regex/lib/package.json: Read -node_modules/@babel/plugin-transform-unicode-sets-regex/package.json: Read -node_modules/@babel/preset-env/lib/available-plugins.js: Read -node_modules/@babel/preset-env/lib/debug.js: Read -node_modules/@babel/preset-env/lib/filter-items.js: Read -node_modules/@babel/preset-env/lib/index.js: Read -node_modules/@babel/preset-env/lib/module-transformations.js: Read -node_modules/@babel/preset-env/lib/normalize-options.js: Read -node_modules/@babel/preset-env/lib/options.js: Read -node_modules/@babel/preset-env/lib/package.json: Read -node_modules/@babel/preset-env/lib/plugins-compat-data.js: Read -node_modules/@babel/preset-env/lib/polyfills/babel-7-plugins.cjs: Read -node_modules/@babel/preset-env/lib/shipped-proposals.js: Read -node_modules/@babel/preset-env/node_modules/@babel/compat-data/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/helper-compilation-targets/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/helper-plugin-utils/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/helper-validator-option/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-syntax-import-assertions/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-syntax-import-attributes/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-arrow-functions/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-async-generator-functions/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-async-to-generator/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-block-scoped-functions/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-block-scoping/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-class-properties/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-class-static-block/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-classes/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-computed-properties/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-destructuring/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-dotall-regex/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-duplicate-keys/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-dynamic-import/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-explicit-resource-management/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-exponentiation-operator/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-export-namespace-from/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-for-of/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-function-name/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-json-strings/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-literals/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-logical-assignment-operators/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-member-expression-literals/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-modules-amd/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-modules-commonjs/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-modules-systemjs/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-modules-umd/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-named-capturing-groups-regex/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-new-target/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-nullish-coalescing-operator/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-numeric-separator/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-object-rest-spread/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-object-super/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-optional-catch-binding/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-optional-chaining/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-parameters/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-private-methods/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-private-property-in-object/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-property-literals/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-regenerator/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-regexp-modifiers/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-reserved-words/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-shorthand-properties/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-spread/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-sticky-regex/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-template-literals/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-typeof-symbol/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-unicode-escapes/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-unicode-property-regex/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-unicode-regex/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-unicode-sets-regex/package.json: Read -node_modules/@babel/preset-env/node_modules/@babel/preset-modules/package.json: Read -node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3/package.json: Read -node_modules/@babel/preset-env/node_modules/core-js-compat/package.json: Read -node_modules/@babel/preset-env/node_modules/semver/package.json: Read -node_modules/@babel/preset-env/node_modules/semver/semver.js: Read -node_modules/@babel/preset-env/package.json: Read -node_modules/@babel/preset-modules/lib/package.json: Read -node_modules/@babel/preset-modules/lib/plugins/package.json: Read -node_modules/@babel/preset-modules/lib/plugins/transform-async-arrows-in-class/index.js: Read -node_modules/@babel/preset-modules/lib/plugins/transform-async-arrows-in-class/package.json: Read -node_modules/@babel/preset-modules/lib/plugins/transform-edge-default-parameters/index.js: Read -node_modules/@babel/preset-modules/lib/plugins/transform-edge-default-parameters/package.json: Read -node_modules/@babel/preset-modules/lib/plugins/transform-edge-function-name/index.js: Read -node_modules/@babel/preset-modules/lib/plugins/transform-edge-function-name/package.json: Read -node_modules/@babel/preset-modules/lib/plugins/transform-safari-block-shadowing/index.js: Read -node_modules/@babel/preset-modules/lib/plugins/transform-safari-block-shadowing/package.json: Read -node_modules/@babel/preset-modules/lib/plugins/transform-safari-for-shadowing/index.js: Read -node_modules/@babel/preset-modules/lib/plugins/transform-safari-for-shadowing/package.json: Read -node_modules/@babel/preset-modules/lib/plugins/transform-tagged-template-caching/index.js: Read -node_modules/@babel/preset-modules/lib/plugins/transform-tagged-template-caching/package.json: Read -node_modules/@babel/preset-modules/package.json: Read -node_modules/@babel/runtime/helpers/esm/arrayLikeToArray.js: Read -node_modules/@babel/runtime/helpers/esm/arrayWithHoles.js: Read -node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js: Read -node_modules/@babel/runtime/helpers/esm/classCallCheck.js: Read -node_modules/@babel/runtime/helpers/esm/construct.js: Read -node_modules/@babel/runtime/helpers/esm/createClass.js: Read -node_modules/@babel/runtime/helpers/esm/defineProperty.js: Read -node_modules/@babel/runtime/helpers/esm/extends.js: Read -node_modules/@babel/runtime/helpers/esm/getPrototypeOf.js: Read -node_modules/@babel/runtime/helpers/esm/inherits.js: Read -node_modules/@babel/runtime/helpers/esm/inheritsLoose.js: Read -node_modules/@babel/runtime/helpers/esm/isNativeFunction.js: Read -node_modules/@babel/runtime/helpers/esm/isNativeReflectConstruct.js: Read -node_modules/@babel/runtime/helpers/esm/iterableToArray.js: Read -node_modules/@babel/runtime/helpers/esm/iterableToArrayLimit.js: Read -node_modules/@babel/runtime/helpers/esm/nonIterableRest.js: Read -node_modules/@babel/runtime/helpers/esm/objectSpread2.js: Read -node_modules/@babel/runtime/helpers/esm/objectWithoutProperties.js: Read -node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js: Read -node_modules/@babel/runtime/helpers/esm/package.json: Read -node_modules/@babel/runtime/helpers/esm/possibleConstructorReturn.js: Read -node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js: Read -node_modules/@babel/runtime/helpers/esm/slicedToArray.js: Read -node_modules/@babel/runtime/helpers/esm/taggedTemplateLiteralLoose.js: Read -node_modules/@babel/runtime/helpers/esm/toArray.js: Read -node_modules/@babel/runtime/helpers/esm/toPrimitive.js: Read -node_modules/@babel/runtime/helpers/esm/toPropertyKey.js: Read -node_modules/@babel/runtime/helpers/esm/typeof.js: Read -node_modules/@babel/runtime/helpers/esm/unsupportedIterableToArray.js: Read -node_modules/@babel/runtime/helpers/esm/wrapNativeSuper.js: Read -node_modules/@babel/runtime/helpers/package.json: Read -node_modules/@babel/runtime/package.json: Read -node_modules/@babel/template/lib/builder.js: Read -node_modules/@babel/template/lib/formatters.js: Read -node_modules/@babel/template/lib/index.js: Read -node_modules/@babel/template/lib/literal.js: Read -node_modules/@babel/template/lib/options.js: Read -node_modules/@babel/template/lib/package.json: Read -node_modules/@babel/template/lib/parse.js: Read -node_modules/@babel/template/lib/populate.js: Read -node_modules/@babel/template/lib/string.js: Read -node_modules/@babel/template/node_modules/@babel/code-frame/package.json: Read -node_modules/@babel/template/node_modules/@babel/parser/package.json: Read -node_modules/@babel/template/node_modules/@babel/types/package.json: Read -node_modules/@babel/template/package.json: Read -node_modules/@babel/traverse/lib/cache.js: Read -node_modules/@babel/traverse/lib/context.js: Read -node_modules/@babel/traverse/lib/hub.js: Read -node_modules/@babel/traverse/lib/index.js: Read -node_modules/@babel/traverse/lib/package.json: Read -node_modules/@babel/traverse/lib/path/ancestry.js: Read -node_modules/@babel/traverse/lib/path/comments.js: Read -node_modules/@babel/traverse/lib/path/context.js: Read -node_modules/@babel/traverse/lib/path/conversion.js: Read -node_modules/@babel/traverse/lib/path/evaluation.js: Read -node_modules/@babel/traverse/lib/path/family.js: Read -node_modules/@babel/traverse/lib/path/index.js: Read -node_modules/@babel/traverse/lib/path/inference/index.js: Read -node_modules/@babel/traverse/lib/path/inference/inferer-reference.js: Read -node_modules/@babel/traverse/lib/path/inference/inferers.js: Read -node_modules/@babel/traverse/lib/path/inference/package.json: Read -node_modules/@babel/traverse/lib/path/inference/util.js: Read -node_modules/@babel/traverse/lib/path/introspection.js: Read -node_modules/@babel/traverse/lib/path/lib/hoister.js: Read -node_modules/@babel/traverse/lib/path/lib/package.json: Read -node_modules/@babel/traverse/lib/path/lib/removal-hooks.js: Read -node_modules/@babel/traverse/lib/path/lib/virtual-types-validator.js: Read -node_modules/@babel/traverse/lib/path/lib/virtual-types.js: Read -node_modules/@babel/traverse/lib/path/modification.js: Read -node_modules/@babel/traverse/lib/path/package.json: Read -node_modules/@babel/traverse/lib/path/removal.js: Read -node_modules/@babel/traverse/lib/path/replacement.js: Read -node_modules/@babel/traverse/lib/scope/binding.js: Read -node_modules/@babel/traverse/lib/scope/index.js: Read -node_modules/@babel/traverse/lib/scope/lib/package.json: Read -node_modules/@babel/traverse/lib/scope/lib/renamer.js: Read -node_modules/@babel/traverse/lib/scope/package.json: Read -node_modules/@babel/traverse/lib/traverse-node.js: Read -node_modules/@babel/traverse/lib/visitors.js: Read -node_modules/@babel/traverse/node_modules/@babel/code-frame/package.json: Read -node_modules/@babel/traverse/node_modules/@babel/generator/package.json: Read -node_modules/@babel/traverse/node_modules/@babel/helper-globals/package.json: Read -node_modules/@babel/traverse/node_modules/@babel/parser/package.json: Read -node_modules/@babel/traverse/node_modules/@babel/template/package.json: Read -node_modules/@babel/traverse/node_modules/@babel/types/package.json: Read -node_modules/@babel/traverse/node_modules/debug/package.json: Read -node_modules/@babel/traverse/package.json: Read -node_modules/@babel/types/lib/asserts/assertNode.js: Read -node_modules/@babel/types/lib/asserts/generated/index.js: Read -node_modules/@babel/types/lib/asserts/generated/package.json: Read -node_modules/@babel/types/lib/asserts/package.json: Read -node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js: Read -node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js: Read -node_modules/@babel/types/lib/builders/flow/package.json: Read -node_modules/@babel/types/lib/builders/generated/index.js: Read -node_modules/@babel/types/lib/builders/generated/lowercase.js: Read -node_modules/@babel/types/lib/builders/generated/package.json: Read -node_modules/@babel/types/lib/builders/generated/uppercase.js: Read -node_modules/@babel/types/lib/builders/package.json: Read -node_modules/@babel/types/lib/builders/productions.js: Read -node_modules/@babel/types/lib/builders/react/buildChildren.js: Read -node_modules/@babel/types/lib/builders/react/package.json: Read -node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js: Read -node_modules/@babel/types/lib/builders/typescript/package.json: Read -node_modules/@babel/types/lib/clone/clone.js: Read -node_modules/@babel/types/lib/clone/cloneDeep.js: Read -node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js: Read -node_modules/@babel/types/lib/clone/cloneNode.js: Read -node_modules/@babel/types/lib/clone/cloneWithoutLoc.js: Read -node_modules/@babel/types/lib/clone/package.json: Read -node_modules/@babel/types/lib/comments/addComment.js: Read -node_modules/@babel/types/lib/comments/addComments.js: Read -node_modules/@babel/types/lib/comments/inheritInnerComments.js: Read -node_modules/@babel/types/lib/comments/inheritLeadingComments.js: Read -node_modules/@babel/types/lib/comments/inheritTrailingComments.js: Read -node_modules/@babel/types/lib/comments/inheritsComments.js: Read -node_modules/@babel/types/lib/comments/package.json: Read -node_modules/@babel/types/lib/comments/removeComments.js: Read -node_modules/@babel/types/lib/constants/generated/index.js: Read -node_modules/@babel/types/lib/constants/generated/package.json: Read -node_modules/@babel/types/lib/constants/index.js: Read -node_modules/@babel/types/lib/constants/package.json: Read -node_modules/@babel/types/lib/converters/ensureBlock.js: Read -node_modules/@babel/types/lib/converters/gatherSequenceExpressions.js: Read -node_modules/@babel/types/lib/converters/package.json: Read -node_modules/@babel/types/lib/converters/toBindingIdentifierName.js: Read -node_modules/@babel/types/lib/converters/toBlock.js: Read -node_modules/@babel/types/lib/converters/toComputedKey.js: Read -node_modules/@babel/types/lib/converters/toExpression.js: Read -node_modules/@babel/types/lib/converters/toIdentifier.js: Read -node_modules/@babel/types/lib/converters/toKeyAlias.js: Read -node_modules/@babel/types/lib/converters/toSequenceExpression.js: Read -node_modules/@babel/types/lib/converters/toStatement.js: Read -node_modules/@babel/types/lib/converters/valueToNode.js: Read -node_modules/@babel/types/lib/definitions/core.js: Read -node_modules/@babel/types/lib/definitions/deprecated-aliases.js: Read -node_modules/@babel/types/lib/definitions/experimental.js: Read -node_modules/@babel/types/lib/definitions/flow.js: Read -node_modules/@babel/types/lib/definitions/index.js: Read -node_modules/@babel/types/lib/definitions/jsx.js: Read -node_modules/@babel/types/lib/definitions/misc.js: Read -node_modules/@babel/types/lib/definitions/package.json: Read -node_modules/@babel/types/lib/definitions/placeholders.js: Read -node_modules/@babel/types/lib/definitions/typescript.js: Read -node_modules/@babel/types/lib/definitions/utils.js: Read -node_modules/@babel/types/lib/index.js: Read -node_modules/@babel/types/lib/modifications/appendToMemberExpression.js: Read -node_modules/@babel/types/lib/modifications/flow/package.json: Read -node_modules/@babel/types/lib/modifications/flow/removeTypeDuplicates.js: Read -node_modules/@babel/types/lib/modifications/inherits.js: Read -node_modules/@babel/types/lib/modifications/package.json: Read -node_modules/@babel/types/lib/modifications/prependToMemberExpression.js: Read -node_modules/@babel/types/lib/modifications/removeProperties.js: Read -node_modules/@babel/types/lib/modifications/removePropertiesDeep.js: Read -node_modules/@babel/types/lib/modifications/typescript/package.json: Read -node_modules/@babel/types/lib/modifications/typescript/removeTypeDuplicates.js: Read -node_modules/@babel/types/lib/package.json: Read -node_modules/@babel/types/lib/retrievers/getAssignmentIdentifiers.js: Read -node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js: Read -node_modules/@babel/types/lib/retrievers/getFunctionName.js: Read -node_modules/@babel/types/lib/retrievers/getOuterBindingIdentifiers.js: Read -node_modules/@babel/types/lib/retrievers/package.json: Read -node_modules/@babel/types/lib/traverse/package.json: Read -node_modules/@babel/types/lib/traverse/traverse.js: Read -node_modules/@babel/types/lib/traverse/traverseFast.js: Read -node_modules/@babel/types/lib/utils/deprecationWarning.js: Read -node_modules/@babel/types/lib/utils/inherit.js: Read -node_modules/@babel/types/lib/utils/package.json: Read -node_modules/@babel/types/lib/utils/react/cleanJSXElementLiteralChild.js: Read -node_modules/@babel/types/lib/utils/react/package.json: Read -node_modules/@babel/types/lib/utils/shallowEqual.js: Read -node_modules/@babel/types/lib/validators/buildMatchMemberExpression.js: Read -node_modules/@babel/types/lib/validators/generated/index.js: Read -node_modules/@babel/types/lib/validators/generated/package.json: Read -node_modules/@babel/types/lib/validators/is.js: Read -node_modules/@babel/types/lib/validators/isBinding.js: Read -node_modules/@babel/types/lib/validators/isBlockScoped.js: Read -node_modules/@babel/types/lib/validators/isImmutable.js: Read -node_modules/@babel/types/lib/validators/isLet.js: Read -node_modules/@babel/types/lib/validators/isNode.js: Read -node_modules/@babel/types/lib/validators/isNodesEquivalent.js: Read -node_modules/@babel/types/lib/validators/isPlaceholderType.js: Read -node_modules/@babel/types/lib/validators/isReferenced.js: Read -node_modules/@babel/types/lib/validators/isScope.js: Read -node_modules/@babel/types/lib/validators/isSpecifierDefault.js: Read -node_modules/@babel/types/lib/validators/isType.js: Read -node_modules/@babel/types/lib/validators/isValidES3Identifier.js: Read -node_modules/@babel/types/lib/validators/isValidIdentifier.js: Read -node_modules/@babel/types/lib/validators/isVar.js: Read -node_modules/@babel/types/lib/validators/matchesPattern.js: Read -node_modules/@babel/types/lib/validators/package.json: Read -node_modules/@babel/types/lib/validators/react/isCompatTag.js: Read -node_modules/@babel/types/lib/validators/react/isReactComponent.js: Read -node_modules/@babel/types/lib/validators/react/package.json: Read -node_modules/@babel/types/lib/validators/validate.js: Read -node_modules/@babel/types/package.json: Read -node_modules/@benrbray/package.json: Read -node_modules/@benrbray/prosemirror-math/dist/index.es.js: Read -node_modules/@benrbray/prosemirror-math/dist/package.json: Read -node_modules/@benrbray/prosemirror-math/package.json: Read -node_modules/@braintree/package.json: Read -node_modules/@braintree/sanitize-url/dist/constants.js: Read -node_modules/@braintree/sanitize-url/dist/index.js: Read -node_modules/@braintree/sanitize-url/dist/package.json: Read -node_modules/@braintree/sanitize-url/package.json: Read -node_modules/@chevrotain/cst-dts-gen/lib/package.json: Read -node_modules/@chevrotain/cst-dts-gen/lib/src/api.js: Read -node_modules/@chevrotain/cst-dts-gen/lib/src/generate.js: Read -node_modules/@chevrotain/cst-dts-gen/lib/src/model.js: Read -node_modules/@chevrotain/cst-dts-gen/lib/src/package.json: Read -node_modules/@chevrotain/cst-dts-gen/package.json: Read -node_modules/@chevrotain/gast/lib/package.json: Read -node_modules/@chevrotain/gast/lib/src/api.js: Read -node_modules/@chevrotain/gast/lib/src/helpers.js: Read -node_modules/@chevrotain/gast/lib/src/model.js: Read -node_modules/@chevrotain/gast/lib/src/package.json: Read -node_modules/@chevrotain/gast/lib/src/visitor.js: Read -node_modules/@chevrotain/gast/package.json: Read -node_modules/@chevrotain/package.json: Read -node_modules/@chevrotain/regexp-to-ast/lib/package.json: Read -node_modules/@chevrotain/regexp-to-ast/lib/src/api.js: Read -node_modules/@chevrotain/regexp-to-ast/lib/src/base-regexp-visitor.js: Read -node_modules/@chevrotain/regexp-to-ast/lib/src/character-classes.js: Read -node_modules/@chevrotain/regexp-to-ast/lib/src/package.json: Read -node_modules/@chevrotain/regexp-to-ast/lib/src/regexp-parser.js: Read -node_modules/@chevrotain/regexp-to-ast/lib/src/utils.js: Read -node_modules/@chevrotain/regexp-to-ast/package.json: Read -node_modules/@chevrotain/utils/lib/package.json: Read -node_modules/@chevrotain/utils/lib/src/api.js: Read -node_modules/@chevrotain/utils/lib/src/package.json: Read -node_modules/@chevrotain/utils/lib/src/print.js: Read -node_modules/@chevrotain/utils/lib/src/timer.js: Read -node_modules/@chevrotain/utils/lib/src/to-fast-properties.js: Read -node_modules/@chevrotain/utils/package.json: Read -node_modules/@dnd-kit/accessibility/dist/accessibility.esm.js: Read -node_modules/@dnd-kit/accessibility/dist/package.json: Read -node_modules/@dnd-kit/accessibility/package.json: Read -node_modules/@dnd-kit/core/dist/core.esm.js: Read -node_modules/@dnd-kit/core/dist/package.json: Read -node_modules/@dnd-kit/core/package.json: Read -node_modules/@dnd-kit/modifiers/dist/modifiers.esm.js: Read -node_modules/@dnd-kit/modifiers/dist/package.json: Read -node_modules/@dnd-kit/modifiers/package.json: Read -node_modules/@dnd-kit/package.json: Read -node_modules/@dnd-kit/sortable/dist/package.json: Read -node_modules/@dnd-kit/sortable/dist/sortable.esm.js: Read -node_modules/@dnd-kit/sortable/package.json: Read -node_modules/@dnd-kit/utilities/dist/package.json: Read -node_modules/@dnd-kit/utilities/dist/utilities.esm.js: Read -node_modules/@dnd-kit/utilities/package.json: Read -node_modules/@emoji-mart/data/package.json: Read -node_modules/@emoji-mart/data/sets/15/native.json: Read -node_modules/@emoji-mart/data/sets/15/package.json: Read -node_modules/@emoji-mart/data/sets/package.json: Read -node_modules/@emoji-mart/package.json: Read -node_modules/@emotion/is-prop-valid/dist/is-prop-valid.browser.esm.js: Read -node_modules/@emotion/is-prop-valid/dist/package.json: Read -node_modules/@emotion/is-prop-valid/package.json: Read -node_modules/@emotion/memoize/dist/memoize.browser.esm.js: Read -node_modules/@emotion/memoize/dist/package.json: Read -node_modules/@emotion/memoize/package.json: Read -node_modules/@emotion/package.json: Read -node_modules/@emotion/stylis/dist/package.json: Read -node_modules/@emotion/stylis/dist/stylis.browser.esm.js: Read -node_modules/@emotion/stylis/package.json: Read -node_modules/@emotion/unitless/dist/package.json: Read -node_modules/@emotion/unitless/dist/unitless.browser.esm.js: Read -node_modules/@emotion/unitless/package.json: Read -node_modules/@floating-ui/core/dist/floating-ui.core.mjs: Read -node_modules/@floating-ui/core/dist/package.json: Read -node_modules/@floating-ui/core/package.json: Read -node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs: Read -node_modules/@floating-ui/dom/dist/package.json: Read -node_modules/@floating-ui/dom/package.json: Read -node_modules/@floating-ui/package.json: Read -node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.mjs: Read -node_modules/@floating-ui/react-dom/dist/package.json: Read -node_modules/@floating-ui/react-dom/package.json: Read -node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.mjs: Read -node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs: Read -node_modules/@floating-ui/utils/dist/package.json: Read -node_modules/@floating-ui/utils/package.json: Read -node_modules/@fortawesome/fontawesome-svg-core/index.mjs: Read -node_modules/@fortawesome/fontawesome-svg-core/package.json: Read -node_modules/@fortawesome/free-brands-svg-icons/index.mjs: Read -node_modules/@fortawesome/free-brands-svg-icons/package.json: Read -node_modules/@fortawesome/free-solid-svg-icons/index.mjs: Read -node_modules/@fortawesome/free-solid-svg-icons/package.json: Read -node_modules/@fortawesome/package.json: Read -node_modules/@fortawesome/react-fontawesome/index.es.js: Read -node_modules/@fortawesome/react-fontawesome/package.json: Read -node_modules/@getoutline/package.json: Read -node_modules/@getoutline/react-roving-tabindex/dist/index.es.js: Read -node_modules/@getoutline/react-roving-tabindex/dist/package.json: Read -node_modules/@getoutline/react-roving-tabindex/package.json: Read -node_modules/@hocuspocus/common/dist/hocuspocus-common.esm.js: Read -node_modules/@hocuspocus/common/dist/package.json: Read -node_modules/@hocuspocus/common/package.json: Read -node_modules/@hocuspocus/package.json: Read -node_modules/@hocuspocus/provider/dist/hocuspocus-provider.esm.js: Read -node_modules/@hocuspocus/provider/dist/package.json: Read -node_modules/@hocuspocus/provider/node_modules/package.json: Read -node_modules/@hocuspocus/provider/node_modules/yjs/package.json: Read -node_modules/@hocuspocus/provider/package.json: Read -node_modules/@iconify/package.json: Read -node_modules/@iconify/utils/lib/colors/index.mjs: Read -node_modules/@iconify/utils/lib/colors/keywords.mjs: Read -node_modules/@iconify/utils/lib/colors/package.json: Read -node_modules/@iconify/utils/lib/css/common.mjs: Read -node_modules/@iconify/utils/lib/css/format.mjs: Read -node_modules/@iconify/utils/lib/css/icon.mjs: Read -node_modules/@iconify/utils/lib/css/icons.mjs: Read -node_modules/@iconify/utils/lib/css/package.json: Read -node_modules/@iconify/utils/lib/customisations/bool.mjs: Read -node_modules/@iconify/utils/lib/customisations/defaults.mjs: Read -node_modules/@iconify/utils/lib/customisations/flip.mjs: Read -node_modules/@iconify/utils/lib/customisations/merge.mjs: Read -node_modules/@iconify/utils/lib/customisations/package.json: Read -node_modules/@iconify/utils/lib/customisations/rotate.mjs: Read -node_modules/@iconify/utils/lib/emoji/cleanup.mjs: Read -node_modules/@iconify/utils/lib/emoji/convert.mjs: Read -node_modules/@iconify/utils/lib/emoji/data.mjs: Read -node_modules/@iconify/utils/lib/emoji/format.mjs: Read -node_modules/@iconify/utils/lib/emoji/package.json: Read -node_modules/@iconify/utils/lib/emoji/parse.mjs: Read -node_modules/@iconify/utils/lib/emoji/regex/base.mjs: Read -node_modules/@iconify/utils/lib/emoji/regex/create.mjs: Read -node_modules/@iconify/utils/lib/emoji/regex/numbers.mjs: Read -node_modules/@iconify/utils/lib/emoji/regex/package.json: Read -node_modules/@iconify/utils/lib/emoji/regex/similar.mjs: Read -node_modules/@iconify/utils/lib/emoji/regex/tree.mjs: Read -node_modules/@iconify/utils/lib/emoji/replace/find.mjs: Read -node_modules/@iconify/utils/lib/emoji/replace/package.json: Read -node_modules/@iconify/utils/lib/emoji/replace/replace.mjs: Read -node_modules/@iconify/utils/lib/emoji/test/components.mjs: Read -node_modules/@iconify/utils/lib/emoji/test/missing.mjs: Read -node_modules/@iconify/utils/lib/emoji/test/name.mjs: Read -node_modules/@iconify/utils/lib/emoji/test/package.json: Read -node_modules/@iconify/utils/lib/emoji/test/parse.mjs: Read -node_modules/@iconify/utils/lib/emoji/test/similar.mjs: Read -node_modules/@iconify/utils/lib/emoji/test/tree.mjs: Read -node_modules/@iconify/utils/lib/emoji/test/variations.mjs: Read -node_modules/@iconify/utils/lib/icon-set/convert-info.mjs: Read -node_modules/@iconify/utils/lib/icon-set/expand.mjs: Read -node_modules/@iconify/utils/lib/icon-set/get-icon.mjs: Read -node_modules/@iconify/utils/lib/icon-set/get-icons.mjs: Read -node_modules/@iconify/utils/lib/icon-set/minify.mjs: Read -node_modules/@iconify/utils/lib/icon-set/package.json: Read -node_modules/@iconify/utils/lib/icon-set/parse.mjs: Read -node_modules/@iconify/utils/lib/icon-set/tree.mjs: Read -node_modules/@iconify/utils/lib/icon-set/validate-basic.mjs: Read -node_modules/@iconify/utils/lib/icon-set/validate.mjs: Read -node_modules/@iconify/utils/lib/icon/defaults.mjs: Read -node_modules/@iconify/utils/lib/icon/merge.mjs: Read -node_modules/@iconify/utils/lib/icon/name.mjs: Read -node_modules/@iconify/utils/lib/icon/package.json: Read -node_modules/@iconify/utils/lib/icon/square.mjs: Read -node_modules/@iconify/utils/lib/icon/transformations.mjs: Read -node_modules/@iconify/utils/lib/index.mjs: Read -node_modules/@iconify/utils/lib/loader/custom.mjs: Read -node_modules/@iconify/utils/lib/loader/loader.mjs: Read -node_modules/@iconify/utils/lib/loader/modern.mjs: Read -node_modules/@iconify/utils/lib/loader/package.json: Read -node_modules/@iconify/utils/lib/loader/utils.mjs: Read -node_modules/@iconify/utils/lib/misc/objects.mjs: Read -node_modules/@iconify/utils/lib/misc/package.json: Read -node_modules/@iconify/utils/lib/misc/strings.mjs: Read -node_modules/@iconify/utils/lib/misc/title.mjs: Read -node_modules/@iconify/utils/lib/package.json: Read -node_modules/@iconify/utils/lib/svg/build.mjs: Read -node_modules/@iconify/utils/lib/svg/defs.mjs: Read -node_modules/@iconify/utils/lib/svg/encode-svg-for-css.mjs: Read -node_modules/@iconify/utils/lib/svg/html.mjs: Read -node_modules/@iconify/utils/lib/svg/id.mjs: Read -node_modules/@iconify/utils/lib/svg/inner-html.mjs: Read -node_modules/@iconify/utils/lib/svg/package.json: Read -node_modules/@iconify/utils/lib/svg/parse.mjs: Read -node_modules/@iconify/utils/lib/svg/pretty.mjs: Read -node_modules/@iconify/utils/lib/svg/size.mjs: Read -node_modules/@iconify/utils/lib/svg/trim.mjs: Read -node_modules/@iconify/utils/lib/svg/url.mjs: Read -node_modules/@iconify/utils/lib/svg/viewbox.mjs: Read -node_modules/@iconify/utils/package.json: Read -node_modules/@icons/material/UnfoldMoreHorizontalIcon.js: Read -node_modules/@icons/material/package.json: Read -node_modules/@icons/package.json: Read -node_modules/@jridgewell/gen-mapping/dist/gen-mapping.umd.js: Read -node_modules/@jridgewell/gen-mapping/dist/package.json: Read -node_modules/@jridgewell/gen-mapping/package.json: Read -node_modules/@jridgewell/resolve-uri/dist/package.json: Read -node_modules/@jridgewell/resolve-uri/dist/resolve-uri.umd.js: Read -node_modules/@jridgewell/resolve-uri/package.json: Read -node_modules/@jridgewell/source-map/dist/package.json: Read -node_modules/@jridgewell/source-map/dist/source-map.umd.js: Read -node_modules/@jridgewell/source-map/package.json: Read -node_modules/@jridgewell/sourcemap-codec/dist/package.json: Read -node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js: Read -node_modules/@jridgewell/sourcemap-codec/package.json: Read -node_modules/@jridgewell/trace-mapping/dist/package.json: Read -node_modules/@jridgewell/trace-mapping/dist/trace-mapping.umd.js: Read -node_modules/@jridgewell/trace-mapping/package.json: Read -node_modules/@juggle/package.json: Read -node_modules/@juggle/resize-observer/lib/DOMRectReadOnly.js: Read -node_modules/@juggle/resize-observer/lib/ResizeObservation.js: Read -node_modules/@juggle/resize-observer/lib/ResizeObserver.js: Read -node_modules/@juggle/resize-observer/lib/ResizeObserverBoxOptions.js: Read -node_modules/@juggle/resize-observer/lib/ResizeObserverController.js: Read -node_modules/@juggle/resize-observer/lib/ResizeObserverDetail.js: Read -node_modules/@juggle/resize-observer/lib/ResizeObserverEntry.js: Read -node_modules/@juggle/resize-observer/lib/ResizeObserverSize.js: Read -node_modules/@juggle/resize-observer/lib/algorithms/broadcastActiveObservations.js: Read -node_modules/@juggle/resize-observer/lib/algorithms/calculateBoxSize.js: Read -node_modules/@juggle/resize-observer/lib/algorithms/calculateDepthForNode.js: Read -node_modules/@juggle/resize-observer/lib/algorithms/deliverResizeLoopError.js: Read -node_modules/@juggle/resize-observer/lib/algorithms/gatherActiveObservationsAtDepth.js: Read -node_modules/@juggle/resize-observer/lib/algorithms/hasActiveObservations.js: Read -node_modules/@juggle/resize-observer/lib/algorithms/hasSkippedObservations.js: Read -node_modules/@juggle/resize-observer/lib/algorithms/package.json: Read -node_modules/@juggle/resize-observer/lib/exports/package.json: Read -node_modules/@juggle/resize-observer/lib/exports/resize-observer.js: Read -node_modules/@juggle/resize-observer/lib/package.json: Read -node_modules/@juggle/resize-observer/lib/utils/element.js: Read -node_modules/@juggle/resize-observer/lib/utils/freeze.js: Read -node_modules/@juggle/resize-observer/lib/utils/global.js: Read -node_modules/@juggle/resize-observer/lib/utils/package.json: Read -node_modules/@juggle/resize-observer/lib/utils/process.js: Read -node_modules/@juggle/resize-observer/lib/utils/queueMicroTask.js: Read -node_modules/@juggle/resize-observer/lib/utils/queueResizeObserver.js: Read -node_modules/@juggle/resize-observer/lib/utils/resizeObservers.js: Read -node_modules/@juggle/resize-observer/lib/utils/scheduler.js: Read -node_modules/@juggle/resize-observer/package.json: Read -node_modules/@lifeomic/attempt/dist/es6/package.json: Read -node_modules/@lifeomic/attempt/dist/es6/src/index.js: Read -node_modules/@lifeomic/attempt/dist/es6/src/package.json: Read -node_modules/@lifeomic/attempt/dist/package.json: Read -node_modules/@lifeomic/attempt/package.json: Read -node_modules/@lifeomic/package.json: Read -node_modules/@mermaid-js/package.json: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/architecture-O4VJ6CD3.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/chunk-4KMFLZZN.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/chunk-BN7GFLIU.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/chunk-JEIROHC2.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/chunk-KMC2YHZD.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/chunk-T44TD3VJ.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/chunk-WFRQ32O7.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/chunk-WFWHJNB7.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/chunk-XRWGC2XP.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/gitGraph-ZV4HHKMB.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/info-63CPKGFF.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/package.json: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/packet-HUATNLJX.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/pie-WTHONI2E.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/radar-NJJJXTRR.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/mermaid-parser.core/treemap-75Q7IDZK.mjs: Read -node_modules/@mermaid-js/parser/dist/chunks/package.json: Read -node_modules/@mermaid-js/parser/dist/mermaid-parser.core.mjs: Read -node_modules/@mermaid-js/parser/dist/package.json: Read -node_modules/@mermaid-js/parser/package.json: Read -node_modules/@nodelib/fs.scandir/out/adapters/fs.js: Read -node_modules/@nodelib/fs.scandir/out/adapters/package.json: Read -node_modules/@nodelib/fs.scandir/out/constants.js: Read -node_modules/@nodelib/fs.scandir/out/index.js: Read -node_modules/@nodelib/fs.scandir/out/package.json: Read -node_modules/@nodelib/fs.scandir/out/providers/async.js: Read -node_modules/@nodelib/fs.scandir/out/providers/common.js: Read -node_modules/@nodelib/fs.scandir/out/providers/package.json: Read -node_modules/@nodelib/fs.scandir/out/providers/sync.js: Read -node_modules/@nodelib/fs.scandir/out/settings.js: Read -node_modules/@nodelib/fs.scandir/out/utils/fs.js: Read -node_modules/@nodelib/fs.scandir/out/utils/index.js: Read -node_modules/@nodelib/fs.scandir/out/utils/package.json: Read -node_modules/@nodelib/fs.scandir/package.json: Read -node_modules/@nodelib/fs.stat/out/adapters/fs.js: Read -node_modules/@nodelib/fs.stat/out/adapters/package.json: Read -node_modules/@nodelib/fs.stat/out/index.js: Read -node_modules/@nodelib/fs.stat/out/package.json: Read -node_modules/@nodelib/fs.stat/out/providers/async.js: Read -node_modules/@nodelib/fs.stat/out/providers/package.json: Read -node_modules/@nodelib/fs.stat/out/providers/sync.js: Read -node_modules/@nodelib/fs.stat/out/settings.js: Read -node_modules/@nodelib/fs.stat/package.json: Read -node_modules/@nodelib/fs.walk/out/index.js: Read -node_modules/@nodelib/fs.walk/out/package.json: Read -node_modules/@nodelib/fs.walk/out/providers/async.js: Read -node_modules/@nodelib/fs.walk/out/providers/package.json: Read -node_modules/@nodelib/fs.walk/out/providers/stream.js: Read -node_modules/@nodelib/fs.walk/out/providers/sync.js: Read -node_modules/@nodelib/fs.walk/out/readers/async.js: Read -node_modules/@nodelib/fs.walk/out/readers/common.js: Read -node_modules/@nodelib/fs.walk/out/readers/package.json: Read -node_modules/@nodelib/fs.walk/out/readers/reader.js: Read -node_modules/@nodelib/fs.walk/out/readers/sync.js: Read -node_modules/@nodelib/fs.walk/out/settings.js: Read -node_modules/@nodelib/fs.walk/package.json: Read -node_modules/@popperjs/core/lib/createPopper.js: Read -node_modules/@popperjs/core/lib/dom-utils/contains.js: Read -node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js: Read -node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js: Read -node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js: Read -node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js: Read -node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js: Read -node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js: Read -node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js: Read -node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js: Read -node_modules/@popperjs/core/lib/dom-utils/getNodeName.js: Read -node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js: Read -node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js: Read -node_modules/@popperjs/core/lib/dom-utils/getParentNode.js: Read -node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js: Read -node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js: Read -node_modules/@popperjs/core/lib/dom-utils/getWindow.js: Read -node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js: Read -node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js: Read -node_modules/@popperjs/core/lib/dom-utils/instanceOf.js: Read -node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js: Read -node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js: Read -node_modules/@popperjs/core/lib/dom-utils/isTableElement.js: Read -node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js: Read -node_modules/@popperjs/core/lib/dom-utils/package.json: Read -node_modules/@popperjs/core/lib/enums.js: Read -node_modules/@popperjs/core/lib/index.js: Read -node_modules/@popperjs/core/lib/modifiers/applyStyles.js: Read -node_modules/@popperjs/core/lib/modifiers/arrow.js: Read -node_modules/@popperjs/core/lib/modifiers/computeStyles.js: Read -node_modules/@popperjs/core/lib/modifiers/eventListeners.js: Read -node_modules/@popperjs/core/lib/modifiers/flip.js: Read -node_modules/@popperjs/core/lib/modifiers/hide.js: Read -node_modules/@popperjs/core/lib/modifiers/index.js: Read -node_modules/@popperjs/core/lib/modifiers/offset.js: Read -node_modules/@popperjs/core/lib/modifiers/package.json: Read -node_modules/@popperjs/core/lib/modifiers/popperOffsets.js: Read -node_modules/@popperjs/core/lib/modifiers/preventOverflow.js: Read -node_modules/@popperjs/core/lib/package.json: Read -node_modules/@popperjs/core/lib/popper-lite.js: Read -node_modules/@popperjs/core/lib/popper.js: Read -node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js: Read -node_modules/@popperjs/core/lib/utils/computeOffsets.js: Read -node_modules/@popperjs/core/lib/utils/debounce.js: Read -node_modules/@popperjs/core/lib/utils/detectOverflow.js: Read -node_modules/@popperjs/core/lib/utils/expandToHashMap.js: Read -node_modules/@popperjs/core/lib/utils/getAltAxis.js: Read -node_modules/@popperjs/core/lib/utils/getBasePlacement.js: Read -node_modules/@popperjs/core/lib/utils/getFreshSideObject.js: Read -node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js: Read -node_modules/@popperjs/core/lib/utils/getOppositePlacement.js: Read -node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js: Read -node_modules/@popperjs/core/lib/utils/getVariation.js: Read -node_modules/@popperjs/core/lib/utils/math.js: Read -node_modules/@popperjs/core/lib/utils/mergeByName.js: Read -node_modules/@popperjs/core/lib/utils/mergePaddingObject.js: Read -node_modules/@popperjs/core/lib/utils/orderModifiers.js: Read -node_modules/@popperjs/core/lib/utils/package.json: Read -node_modules/@popperjs/core/lib/utils/rectToClientRect.js: Read -node_modules/@popperjs/core/lib/utils/userAgent.js: Read -node_modules/@popperjs/core/lib/utils/within.js: Read -node_modules/@popperjs/core/package.json: Read -node_modules/@popperjs/package.json: Read -node_modules/@radix-ui/number/dist/index.mjs: Read -node_modules/@radix-ui/number/dist/package.json: Read -node_modules/@radix-ui/number/package.json: Read -node_modules/@radix-ui/package.json: Read -node_modules/@radix-ui/primitive/dist/index.mjs: Read -node_modules/@radix-ui/primitive/dist/package.json: Read -node_modules/@radix-ui/primitive/package.json: Read -node_modules/@radix-ui/react-arrow/dist/index.mjs: Read -node_modules/@radix-ui/react-arrow/dist/package.json: Read -node_modules/@radix-ui/react-arrow/package.json: Read -node_modules/@radix-ui/react-collapsible/dist/index.mjs: Read -node_modules/@radix-ui/react-collapsible/dist/package.json: Read -node_modules/@radix-ui/react-collapsible/package.json: Read -node_modules/@radix-ui/react-collection/dist/index.mjs: Read -node_modules/@radix-ui/react-collection/dist/package.json: Read -node_modules/@radix-ui/react-collection/package.json: Read -node_modules/@radix-ui/react-compose-refs/dist/index.js: Read -node_modules/@radix-ui/react-compose-refs/dist/index.mjs: Read -node_modules/@radix-ui/react-compose-refs/dist/package.json: Read -node_modules/@radix-ui/react-compose-refs/package.json: Read -node_modules/@radix-ui/react-context/dist/index.mjs: Read -node_modules/@radix-ui/react-context/dist/package.json: Read -node_modules/@radix-ui/react-context/package.json: Read -node_modules/@radix-ui/react-dialog/dist/index.mjs: Read -node_modules/@radix-ui/react-dialog/dist/package.json: Read -node_modules/@radix-ui/react-dialog/package.json: Read -node_modules/@radix-ui/react-direction/dist/index.mjs: Read -node_modules/@radix-ui/react-direction/dist/package.json: Read -node_modules/@radix-ui/react-direction/package.json: Read -node_modules/@radix-ui/react-dismissable-layer/dist/index.mjs: Read -node_modules/@radix-ui/react-dismissable-layer/dist/package.json: Read -node_modules/@radix-ui/react-dismissable-layer/package.json: Read -node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs: Read -node_modules/@radix-ui/react-dropdown-menu/dist/package.json: Read -node_modules/@radix-ui/react-dropdown-menu/package.json: Read -node_modules/@radix-ui/react-focus-guards/dist/index.mjs: Read -node_modules/@radix-ui/react-focus-guards/dist/package.json: Read -node_modules/@radix-ui/react-focus-guards/package.json: Read -node_modules/@radix-ui/react-focus-scope/dist/index.mjs: Read -node_modules/@radix-ui/react-focus-scope/dist/package.json: Read -node_modules/@radix-ui/react-focus-scope/package.json: Read -node_modules/@radix-ui/react-id/dist/index.mjs: Read -node_modules/@radix-ui/react-id/dist/package.json: Read -node_modules/@radix-ui/react-id/package.json: Read -node_modules/@radix-ui/react-menu/dist/index.mjs: Read -node_modules/@radix-ui/react-menu/dist/package.json: Read -node_modules/@radix-ui/react-menu/package.json: Read -node_modules/@radix-ui/react-one-time-password-field/dist/index.mjs: Read -node_modules/@radix-ui/react-one-time-password-field/dist/package.json: Read -node_modules/@radix-ui/react-one-time-password-field/package.json: Read -node_modules/@radix-ui/react-popover/dist/index.mjs: Read -node_modules/@radix-ui/react-popover/dist/package.json: Read -node_modules/@radix-ui/react-popover/package.json: Read -node_modules/@radix-ui/react-popper/dist/index.mjs: Read -node_modules/@radix-ui/react-popper/dist/package.json: Read -node_modules/@radix-ui/react-popper/package.json: Read -node_modules/@radix-ui/react-portal/dist/index.js: Read -node_modules/@radix-ui/react-portal/dist/index.mjs: Read -node_modules/@radix-ui/react-portal/dist/package.json: Read -node_modules/@radix-ui/react-portal/package.json: Read -node_modules/@radix-ui/react-presence/dist/index.mjs: Read -node_modules/@radix-ui/react-presence/dist/package.json: Read -node_modules/@radix-ui/react-presence/package.json: Read -node_modules/@radix-ui/react-primitive/dist/index.js: Read -node_modules/@radix-ui/react-primitive/dist/index.mjs: Read -node_modules/@radix-ui/react-primitive/dist/package.json: Read -node_modules/@radix-ui/react-primitive/package.json: Read -node_modules/@radix-ui/react-roving-focus/dist/index.mjs: Read -node_modules/@radix-ui/react-roving-focus/dist/package.json: Read -node_modules/@radix-ui/react-roving-focus/package.json: Read -node_modules/@radix-ui/react-select/dist/index.mjs: Read -node_modules/@radix-ui/react-select/dist/package.json: Read -node_modules/@radix-ui/react-select/package.json: Read -node_modules/@radix-ui/react-slot/dist/index.js: Read -node_modules/@radix-ui/react-slot/dist/index.mjs: Read -node_modules/@radix-ui/react-slot/dist/package.json: Read -node_modules/@radix-ui/react-slot/package.json: Read -node_modules/@radix-ui/react-switch/dist/index.mjs: Read -node_modules/@radix-ui/react-switch/dist/package.json: Read -node_modules/@radix-ui/react-switch/package.json: Read -node_modules/@radix-ui/react-tabs/dist/index.mjs: Read -node_modules/@radix-ui/react-tabs/dist/package.json: Read -node_modules/@radix-ui/react-tabs/package.json: Read -node_modules/@radix-ui/react-tooltip/dist/index.mjs: Read -node_modules/@radix-ui/react-tooltip/dist/package.json: Read -node_modules/@radix-ui/react-tooltip/package.json: Read -node_modules/@radix-ui/react-use-callback-ref/dist/index.mjs: Read -node_modules/@radix-ui/react-use-callback-ref/dist/package.json: Read -node_modules/@radix-ui/react-use-callback-ref/package.json: Read -node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs: Read -node_modules/@radix-ui/react-use-controllable-state/dist/package.json: Read -node_modules/@radix-ui/react-use-controllable-state/package.json: Read -node_modules/@radix-ui/react-use-effect-event/dist/index.mjs: Read -node_modules/@radix-ui/react-use-effect-event/dist/package.json: Read -node_modules/@radix-ui/react-use-effect-event/package.json: Read -node_modules/@radix-ui/react-use-escape-keydown/dist/index.mjs: Read -node_modules/@radix-ui/react-use-escape-keydown/dist/package.json: Read -node_modules/@radix-ui/react-use-escape-keydown/package.json: Read -node_modules/@radix-ui/react-use-is-hydrated/dist/index.mjs: Read -node_modules/@radix-ui/react-use-is-hydrated/dist/package.json: Read -node_modules/@radix-ui/react-use-is-hydrated/package.json: Read -node_modules/@radix-ui/react-use-layout-effect/dist/index.js: Read -node_modules/@radix-ui/react-use-layout-effect/dist/index.mjs: Read -node_modules/@radix-ui/react-use-layout-effect/dist/package.json: Read -node_modules/@radix-ui/react-use-layout-effect/package.json: Read -node_modules/@radix-ui/react-use-previous/dist/index.mjs: Read -node_modules/@radix-ui/react-use-previous/dist/package.json: Read -node_modules/@radix-ui/react-use-previous/package.json: Read -node_modules/@radix-ui/react-use-size/dist/index.mjs: Read -node_modules/@radix-ui/react-use-size/dist/package.json: Read -node_modules/@radix-ui/react-use-size/package.json: Read -node_modules/@radix-ui/react-visually-hidden/dist/index.mjs: Read -node_modules/@radix-ui/react-visually-hidden/dist/package.json: Read -node_modules/@radix-ui/react-visually-hidden/package.json: Read -node_modules/@react-dnd/asap/dist/AsapQueue.js: Read -node_modules/@react-dnd/asap/dist/RawTask.js: Read -node_modules/@react-dnd/asap/dist/TaskFactory.js: Read -node_modules/@react-dnd/asap/dist/asap.js: Read -node_modules/@react-dnd/asap/dist/index.js: Read -node_modules/@react-dnd/asap/dist/makeRequestCall.js: Read -node_modules/@react-dnd/asap/dist/package.json: Read -node_modules/@react-dnd/asap/dist/types.js: Read -node_modules/@react-dnd/asap/package.json: Read -node_modules/@react-dnd/invariant/dist/index.js: Read -node_modules/@react-dnd/invariant/dist/package.json: Read -node_modules/@react-dnd/invariant/package.json: Read -node_modules/@react-dnd/package.json: Read -node_modules/@react-dnd/shallowequal/dist/index.js: Read -node_modules/@react-dnd/shallowequal/dist/package.json: Read -node_modules/@react-dnd/shallowequal/package.json: Read -node_modules/@rolldown/binding-linux-arm64-gnu/package.json: Read -node_modules/@rolldown/pluginutils/dist/index.js: Read -node_modules/@rolldown/pluginutils/dist/package.json: Read -node_modules/@rolldown/pluginutils/package.json: Read -node_modules/@rollup/plugin-babel/dist/index.js: Read -node_modules/@rollup/plugin-babel/dist/package.json: Read -node_modules/@rollup/plugin-babel/node_modules/@babel/core/package.json: Read -node_modules/@rollup/plugin-babel/node_modules/@babel/helper-module-imports/package.json: Read -node_modules/@rollup/plugin-babel/node_modules/@rollup/pluginutils/package.json: Read -node_modules/@rollup/plugin-babel/package.json: Read -node_modules/@rollup/plugin-node-resolve/dist/cjs/index.js: Read -node_modules/@rollup/plugin-node-resolve/dist/cjs/package.json: Read -node_modules/@rollup/plugin-node-resolve/dist/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils/dist/cjs/index.js: Read -node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils/dist/cjs/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils/dist/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils/node_modules/estree-walker/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils/node_modules/picomatch/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/deepmerge/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker/dist/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker/dist/umd/estree-walker.js: Read -node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker/dist/umd/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/is-builtin-module/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/is-module/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/picomatch/package.json: Read -node_modules/@rollup/plugin-node-resolve/node_modules/resolve/package.json: Read -node_modules/@rollup/plugin-node-resolve/package.json: Read -node_modules/@rollup/plugin-replace/dist/package.json: Read -node_modules/@rollup/plugin-replace/dist/rollup-plugin-replace.cjs.js: Read -node_modules/@rollup/plugin-replace/node_modules/@rollup/pluginutils/package.json: Read -node_modules/@rollup/plugin-replace/node_modules/magic-string/package.json: Read -node_modules/@rollup/plugin-replace/package.json: Read -node_modules/@rollup/plugin-terser/dist/cjs/index.js: Read -node_modules/@rollup/plugin-terser/dist/cjs/package.json: Read -node_modules/@rollup/plugin-terser/dist/package.json: Read -node_modules/@rollup/plugin-terser/node_modules/serialize-javascript/package.json: Read -node_modules/@rollup/plugin-terser/node_modules/smob/package.json: Read -node_modules/@rollup/plugin-terser/node_modules/terser/package.json: Read -node_modules/@rollup/plugin-terser/package.json: Read -node_modules/@rollup/pluginutils/dist/cjs/index.js: Read -node_modules/@rollup/pluginutils/dist/cjs/package.json: Read -node_modules/@rollup/pluginutils/dist/package.json: Read -node_modules/@rollup/pluginutils/node_modules/picomatch/package.json: Read -node_modules/@rollup/pluginutils/package.json: Read -node_modules/@sentry-internal/feedback/esm/index.js: Read -node_modules/@sentry-internal/feedback/esm/package.json: Read -node_modules/@sentry-internal/feedback/package.json: Read -node_modules/@sentry-internal/package.json: Read -node_modules/@sentry-internal/replay-canvas/esm/index.js: Read -node_modules/@sentry-internal/replay-canvas/esm/package.json: Read -node_modules/@sentry-internal/replay-canvas/package.json: Read -node_modules/@sentry-internal/tracing/esm/browser/backgroundtab.js: Read -node_modules/@sentry-internal/tracing/esm/browser/browserTracingIntegration.js: Read -node_modules/@sentry-internal/tracing/esm/browser/browsertracing.js: Read -node_modules/@sentry-internal/tracing/esm/browser/instrument.js: Read -node_modules/@sentry-internal/tracing/esm/browser/metrics/index.js: Read -node_modules/@sentry-internal/tracing/esm/browser/metrics/package.json: Read -node_modules/@sentry-internal/tracing/esm/browser/metrics/utils.js: Read -node_modules/@sentry-internal/tracing/esm/browser/package.json: Read -node_modules/@sentry-internal/tracing/esm/browser/request.js: Read -node_modules/@sentry-internal/tracing/esm/browser/router.js: Read -node_modules/@sentry-internal/tracing/esm/browser/types.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getCLS.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getFID.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getINP.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/getLCP.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/bindReporter.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/generateUniqueID.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getActivationStart.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getNavigationEntry.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/getVisibilityWatcher.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/initMetric.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/observe.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/onHidden.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/package.json: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/polyfills/interactionCountPolyfill.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/lib/polyfills/package.json: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/onTTFB.js: Read -node_modules/@sentry-internal/tracing/esm/browser/web-vitals/package.json: Read -node_modules/@sentry-internal/tracing/esm/common/debug-build.js: Read -node_modules/@sentry-internal/tracing/esm/common/fetch.js: Read -node_modules/@sentry-internal/tracing/esm/common/package.json: Read -node_modules/@sentry-internal/tracing/esm/extensions.js: Read -node_modules/@sentry-internal/tracing/esm/index.js: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/apollo.js: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/express.js: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/graphql.js: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/lazy.js: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/mongo.js: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/mysql.js: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/package.json: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/postgres.js: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/prisma.js: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/utils/node-utils.js: Read -node_modules/@sentry-internal/tracing/esm/node/integrations/utils/package.json: Read -node_modules/@sentry-internal/tracing/esm/node/package.json: Read -node_modules/@sentry-internal/tracing/esm/package.json: Read -node_modules/@sentry-internal/tracing/package.json: Read -node_modules/@sentry/browser/esm/client.js: Read -node_modules/@sentry/browser/esm/debug-build.js: Read -node_modules/@sentry/browser/esm/eventbuilder.js: Read -node_modules/@sentry/browser/esm/helpers.js: Read -node_modules/@sentry/browser/esm/index.js: Read -node_modules/@sentry/browser/esm/integrations/breadcrumbs.js: Read -node_modules/@sentry/browser/esm/integrations/dedupe.js: Read -node_modules/@sentry/browser/esm/integrations/globalhandlers.js: Read -node_modules/@sentry/browser/esm/integrations/httpcontext.js: Read -node_modules/@sentry/browser/esm/integrations/index.js: Read -node_modules/@sentry/browser/esm/integrations/linkederrors.js: Read -node_modules/@sentry/browser/esm/integrations/package.json: Read -node_modules/@sentry/browser/esm/integrations/trycatch.js: Read -node_modules/@sentry/browser/esm/package.json: Read -node_modules/@sentry/browser/esm/profiling/hubextensions.js: Read -node_modules/@sentry/browser/esm/profiling/integration.js: Read -node_modules/@sentry/browser/esm/profiling/package.json: Read -node_modules/@sentry/browser/esm/profiling/utils.js: Read -node_modules/@sentry/browser/esm/sdk.js: Read -node_modules/@sentry/browser/esm/stack-parsers.js: Read -node_modules/@sentry/browser/esm/transports/fetch.js: Read -node_modules/@sentry/browser/esm/transports/offline.js: Read -node_modules/@sentry/browser/esm/transports/package.json: Read -node_modules/@sentry/browser/esm/transports/utils.js: Read -node_modules/@sentry/browser/esm/transports/xhr.js: Read -node_modules/@sentry/browser/esm/userfeedback.js: Read -node_modules/@sentry/browser/package.json: Read -node_modules/@sentry/core/esm/api.js: Read -node_modules/@sentry/core/esm/baseclient.js: Read -node_modules/@sentry/core/esm/checkin.js: Read -node_modules/@sentry/core/esm/constants.js: Read -node_modules/@sentry/core/esm/debug-build.js: Read -node_modules/@sentry/core/esm/envelope.js: Read -node_modules/@sentry/core/esm/eventProcessors.js: Read -node_modules/@sentry/core/esm/exports.js: Read -node_modules/@sentry/core/esm/hub.js: Read -node_modules/@sentry/core/esm/index.js: Read -node_modules/@sentry/core/esm/integration.js: Read -node_modules/@sentry/core/esm/integrations/functiontostring.js: Read -node_modules/@sentry/core/esm/integrations/inboundfilters.js: Read -node_modules/@sentry/core/esm/integrations/index.js: Read -node_modules/@sentry/core/esm/integrations/linkederrors.js: Read -node_modules/@sentry/core/esm/integrations/metadata.js: Read -node_modules/@sentry/core/esm/integrations/package.json: Read -node_modules/@sentry/core/esm/integrations/requestdata.js: Read -node_modules/@sentry/core/esm/metadata.js: Read -node_modules/@sentry/core/esm/metrics/aggregator.js: Read -node_modules/@sentry/core/esm/metrics/browser-aggregator.js: Read -node_modules/@sentry/core/esm/metrics/constants.js: Read -node_modules/@sentry/core/esm/metrics/envelope.js: Read -node_modules/@sentry/core/esm/metrics/exports.js: Read -node_modules/@sentry/core/esm/metrics/instance.js: Read -node_modules/@sentry/core/esm/metrics/integration.js: Read -node_modules/@sentry/core/esm/metrics/metric-summary.js: Read -node_modules/@sentry/core/esm/metrics/package.json: Read -node_modules/@sentry/core/esm/metrics/utils.js: Read -node_modules/@sentry/core/esm/package.json: Read -node_modules/@sentry/core/esm/scope.js: Read -node_modules/@sentry/core/esm/sdk.js: Read -node_modules/@sentry/core/esm/semanticAttributes.js: Read -node_modules/@sentry/core/esm/server-runtime-client.js: Read -node_modules/@sentry/core/esm/session.js: Read -node_modules/@sentry/core/esm/sessionflusher.js: Read -node_modules/@sentry/core/esm/span.js: Read -node_modules/@sentry/core/esm/tracing/dynamicSamplingContext.js: Read -node_modules/@sentry/core/esm/tracing/errors.js: Read -node_modules/@sentry/core/esm/tracing/hubextensions.js: Read -node_modules/@sentry/core/esm/tracing/idletransaction.js: Read -node_modules/@sentry/core/esm/tracing/measurement.js: Read -node_modules/@sentry/core/esm/tracing/package.json: Read -node_modules/@sentry/core/esm/tracing/sampling.js: Read -node_modules/@sentry/core/esm/tracing/span.js: Read -node_modules/@sentry/core/esm/tracing/spanstatus.js: Read -node_modules/@sentry/core/esm/tracing/trace.js: Read -node_modules/@sentry/core/esm/tracing/transaction.js: Read -node_modules/@sentry/core/esm/tracing/utils.js: Read -node_modules/@sentry/core/esm/transports/base.js: Read -node_modules/@sentry/core/esm/transports/multiplexed.js: Read -node_modules/@sentry/core/esm/transports/offline.js: Read -node_modules/@sentry/core/esm/transports/package.json: Read -node_modules/@sentry/core/esm/utils/applyScopeDataToEvent.js: Read -node_modules/@sentry/core/esm/utils/getRootSpan.js: Read -node_modules/@sentry/core/esm/utils/handleCallbackErrors.js: Read -node_modules/@sentry/core/esm/utils/hasTracingEnabled.js: Read -node_modules/@sentry/core/esm/utils/isSentryRequestUrl.js: Read -node_modules/@sentry/core/esm/utils/package.json: Read -node_modules/@sentry/core/esm/utils/parameterize.js: Read -node_modules/@sentry/core/esm/utils/prepareEvent.js: Read -node_modules/@sentry/core/esm/utils/sdkMetadata.js: Read -node_modules/@sentry/core/esm/utils/spanUtils.js: Read -node_modules/@sentry/core/esm/version.js: Read -node_modules/@sentry/core/package.json: Read -node_modules/@sentry/integrations/esm/captureconsole.js: Read -node_modules/@sentry/integrations/esm/contextlines.js: Read -node_modules/@sentry/integrations/esm/debug-build.js: Read -node_modules/@sentry/integrations/esm/debug.js: Read -node_modules/@sentry/integrations/esm/dedupe.js: Read -node_modules/@sentry/integrations/esm/extraerrordata.js: Read -node_modules/@sentry/integrations/esm/httpclient.js: Read -node_modules/@sentry/integrations/esm/index.js: Read -node_modules/@sentry/integrations/esm/offline.js: Read -node_modules/@sentry/integrations/esm/package.json: Read -node_modules/@sentry/integrations/esm/reportingobserver.js: Read -node_modules/@sentry/integrations/esm/rewriteframes.js: Read -node_modules/@sentry/integrations/esm/sessiontiming.js: Read -node_modules/@sentry/integrations/esm/transaction.js: Read -node_modules/@sentry/integrations/package.json: Read -node_modules/@sentry/package.json: Read -node_modules/@sentry/react/esm/constants.js: Read -node_modules/@sentry/react/esm/debug-build.js: Read -node_modules/@sentry/react/esm/errorboundary.js: Read -node_modules/@sentry/react/esm/index.js: Read -node_modules/@sentry/react/esm/package.json: Read -node_modules/@sentry/react/esm/profiler.js: Read -node_modules/@sentry/react/esm/reactrouter.js: Read -node_modules/@sentry/react/esm/reactrouterv3.js: Read -node_modules/@sentry/react/esm/reactrouterv6.js: Read -node_modules/@sentry/react/esm/redux.js: Read -node_modules/@sentry/react/esm/sdk.js: Read -node_modules/@sentry/react/package.json: Read -node_modules/@sentry/replay/esm/index.js: Read -node_modules/@sentry/replay/esm/package.json: Read -node_modules/@sentry/replay/package.json: Read -node_modules/@sentry/utils/esm/aggregate-errors.js: Read -node_modules/@sentry/utils/esm/anr.js: Read -node_modules/@sentry/utils/esm/baggage.js: Read -node_modules/@sentry/utils/esm/browser.js: Read -node_modules/@sentry/utils/esm/buildPolyfills/_asyncNullishCoalesce.js: Read -node_modules/@sentry/utils/esm/buildPolyfills/_asyncOptionalChain.js: Read -node_modules/@sentry/utils/esm/buildPolyfills/_asyncOptionalChainDelete.js: Read -node_modules/@sentry/utils/esm/buildPolyfills/_nullishCoalesce.js: Read -node_modules/@sentry/utils/esm/buildPolyfills/_optionalChain.js: Read -node_modules/@sentry/utils/esm/buildPolyfills/_optionalChainDelete.js: Read -node_modules/@sentry/utils/esm/buildPolyfills/package.json: Read -node_modules/@sentry/utils/esm/cache.js: Read -node_modules/@sentry/utils/esm/clientreport.js: Read -node_modules/@sentry/utils/esm/cookie.js: Read -node_modules/@sentry/utils/esm/debug-build.js: Read -node_modules/@sentry/utils/esm/dsn.js: Read -node_modules/@sentry/utils/esm/env.js: Read -node_modules/@sentry/utils/esm/envelope.js: Read -node_modules/@sentry/utils/esm/error.js: Read -node_modules/@sentry/utils/esm/eventbuilder.js: Read -node_modules/@sentry/utils/esm/index.js: Read -node_modules/@sentry/utils/esm/instrument/_handlers.js: Read -node_modules/@sentry/utils/esm/instrument/console.js: Read -node_modules/@sentry/utils/esm/instrument/dom.js: Read -node_modules/@sentry/utils/esm/instrument/fetch.js: Read -node_modules/@sentry/utils/esm/instrument/globalError.js: Read -node_modules/@sentry/utils/esm/instrument/globalUnhandledRejection.js: Read -node_modules/@sentry/utils/esm/instrument/history.js: Read -node_modules/@sentry/utils/esm/instrument/index.js: Read -node_modules/@sentry/utils/esm/instrument/package.json: Read -node_modules/@sentry/utils/esm/instrument/xhr.js: Read -node_modules/@sentry/utils/esm/is.js: Read -node_modules/@sentry/utils/esm/isBrowser.js: Read -node_modules/@sentry/utils/esm/logger.js: Read -node_modules/@sentry/utils/esm/lru.js: Read -node_modules/@sentry/utils/esm/memo.js: Read -node_modules/@sentry/utils/esm/misc.js: Read -node_modules/@sentry/utils/esm/node-stack-trace.js: Read -node_modules/@sentry/utils/esm/node.js: Read -node_modules/@sentry/utils/esm/normalize.js: Read -node_modules/@sentry/utils/esm/object.js: Read -node_modules/@sentry/utils/esm/package.json: Read -node_modules/@sentry/utils/esm/path.js: Read -node_modules/@sentry/utils/esm/promisebuffer.js: Read -node_modules/@sentry/utils/esm/ratelimit.js: Read -node_modules/@sentry/utils/esm/requestdata.js: Read -node_modules/@sentry/utils/esm/severity.js: Read -node_modules/@sentry/utils/esm/stacktrace.js: Read -node_modules/@sentry/utils/esm/string.js: Read -node_modules/@sentry/utils/esm/supports.js: Read -node_modules/@sentry/utils/esm/syncpromise.js: Read -node_modules/@sentry/utils/esm/time.js: Read -node_modules/@sentry/utils/esm/tracing.js: Read -node_modules/@sentry/utils/esm/url.js: Read -node_modules/@sentry/utils/esm/userIntegrations.js: Read -node_modules/@sentry/utils/esm/vendor/escapeStringForRegex.js: Read -node_modules/@sentry/utils/esm/vendor/package.json: Read -node_modules/@sentry/utils/esm/vendor/supportsHistory.js: Read -node_modules/@sentry/utils/esm/worldwide.js: Read -node_modules/@sentry/utils/package.json: Read -node_modules/@socket.io/component-emitter/index.mjs: Read -node_modules/@socket.io/component-emitter/package.json: Read -node_modules/@socket.io/package.json: Read -node_modules/@surma/rollup-plugin-off-main-thread/index.js: Read -node_modules/@surma/rollup-plugin-off-main-thread/loader.ejs: Read -node_modules/@surma/rollup-plugin-off-main-thread/node_modules/ejs/package.json: Read -node_modules/@surma/rollup-plugin-off-main-thread/node_modules/json5/package.json: Read -node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string/package.json: Read -node_modules/@surma/rollup-plugin-off-main-thread/node_modules/string.prototype.matchall/package.json: Read -node_modules/@surma/rollup-plugin-off-main-thread/package.json: Read -node_modules/@tanstack/package.json: Read -node_modules/@tanstack/react-table/build/lib/index.mjs: Read -node_modules/@tanstack/react-table/build/lib/package.json: Read -node_modules/@tanstack/react-table/build/package.json: Read -node_modules/@tanstack/react-table/package.json: Read -node_modules/@tanstack/react-virtual/dist/esm/index.js: Read -node_modules/@tanstack/react-virtual/dist/esm/package.json: Read -node_modules/@tanstack/react-virtual/dist/package.json: Read -node_modules/@tanstack/react-virtual/package.json: Read -node_modules/@tanstack/table-core/build/lib/index.mjs: Read -node_modules/@tanstack/table-core/build/lib/package.json: Read -node_modules/@tanstack/table-core/build/package.json: Read -node_modules/@tanstack/table-core/package.json: Read -node_modules/@tanstack/virtual-core/dist/esm/index.js: Read -node_modules/@tanstack/virtual-core/dist/esm/package.json: Read -node_modules/@tanstack/virtual-core/dist/esm/utils.js: Read -node_modules/@tanstack/virtual-core/dist/package.json: Read -node_modules/@tanstack/virtual-core/package.json: Read -node_modules/@vitejs/plugin-react-oxc/dist/index.mjs: Read -node_modules/@vitejs/plugin-react-oxc/dist/package.json: Read -node_modules/@vitejs/plugin-react-oxc/package.json: Read -node_modules/ajv/dist/ajv.js: Read -node_modules/ajv/dist/compile/codegen/code.js: Read -node_modules/ajv/dist/compile/codegen/index.js: Read -node_modules/ajv/dist/compile/codegen/package.json: Read -node_modules/ajv/dist/compile/codegen/scope.js: Read -node_modules/ajv/dist/compile/errors.js: Read -node_modules/ajv/dist/compile/index.js: Read -node_modules/ajv/dist/compile/names.js: Read -node_modules/ajv/dist/compile/package.json: Read -node_modules/ajv/dist/compile/ref_error.js: Read -node_modules/ajv/dist/compile/resolve.js: Read -node_modules/ajv/dist/compile/rules.js: Read -node_modules/ajv/dist/compile/util.js: Read -node_modules/ajv/dist/compile/validate/applicability.js: Read -node_modules/ajv/dist/compile/validate/boolSchema.js: Read -node_modules/ajv/dist/compile/validate/dataType.js: Read -node_modules/ajv/dist/compile/validate/defaults.js: Read -node_modules/ajv/dist/compile/validate/index.js: Read -node_modules/ajv/dist/compile/validate/keyword.js: Read -node_modules/ajv/dist/compile/validate/package.json: Read -node_modules/ajv/dist/compile/validate/subschema.js: Read -node_modules/ajv/dist/core.js: Read -node_modules/ajv/dist/package.json: Read -node_modules/ajv/dist/refs/data.json: Read -node_modules/ajv/dist/refs/json-schema-draft-07.json: Read -node_modules/ajv/dist/runtime/equal.js: Read -node_modules/ajv/dist/runtime/package.json: Read -node_modules/ajv/dist/runtime/ucs2length.js: Read -node_modules/ajv/dist/runtime/uri.js: Read -node_modules/ajv/dist/runtime/validation_error.js: Read -node_modules/ajv/dist/vocabularies/applicator/additionalItems.js: Read -node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js: Read -node_modules/ajv/dist/vocabularies/applicator/allOf.js: Read -node_modules/ajv/dist/vocabularies/applicator/anyOf.js: Read -node_modules/ajv/dist/vocabularies/applicator/contains.js: Read -node_modules/ajv/dist/vocabularies/applicator/dependencies.js: Read -node_modules/ajv/dist/vocabularies/applicator/if.js: Read -node_modules/ajv/dist/vocabularies/applicator/index.js: Read -node_modules/ajv/dist/vocabularies/applicator/items.js: Read -node_modules/ajv/dist/vocabularies/applicator/items2020.js: Read -node_modules/ajv/dist/vocabularies/applicator/not.js: Read -node_modules/ajv/dist/vocabularies/applicator/oneOf.js: Read -node_modules/ajv/dist/vocabularies/applicator/package.json: Read -node_modules/ajv/dist/vocabularies/applicator/patternProperties.js: Read -node_modules/ajv/dist/vocabularies/applicator/prefixItems.js: Read -node_modules/ajv/dist/vocabularies/applicator/properties.js: Read -node_modules/ajv/dist/vocabularies/applicator/propertyNames.js: Read -node_modules/ajv/dist/vocabularies/applicator/thenElse.js: Read -node_modules/ajv/dist/vocabularies/code.js: Read -node_modules/ajv/dist/vocabularies/core/id.js: Read -node_modules/ajv/dist/vocabularies/core/index.js: Read -node_modules/ajv/dist/vocabularies/core/package.json: Read -node_modules/ajv/dist/vocabularies/core/ref.js: Read -node_modules/ajv/dist/vocabularies/discriminator/index.js: Read -node_modules/ajv/dist/vocabularies/discriminator/package.json: Read -node_modules/ajv/dist/vocabularies/discriminator/types.js: Read -node_modules/ajv/dist/vocabularies/draft7.js: Read -node_modules/ajv/dist/vocabularies/format/format.js: Read -node_modules/ajv/dist/vocabularies/format/index.js: Read -node_modules/ajv/dist/vocabularies/format/package.json: Read -node_modules/ajv/dist/vocabularies/metadata.js: Read -node_modules/ajv/dist/vocabularies/package.json: Read -node_modules/ajv/dist/vocabularies/validation/const.js: Read -node_modules/ajv/dist/vocabularies/validation/enum.js: Read -node_modules/ajv/dist/vocabularies/validation/index.js: Read -node_modules/ajv/dist/vocabularies/validation/limitItems.js: Read -node_modules/ajv/dist/vocabularies/validation/limitLength.js: Read -node_modules/ajv/dist/vocabularies/validation/limitNumber.js: Read -node_modules/ajv/dist/vocabularies/validation/limitProperties.js: Read -node_modules/ajv/dist/vocabularies/validation/multipleOf.js: Read -node_modules/ajv/dist/vocabularies/validation/package.json: Read -node_modules/ajv/dist/vocabularies/validation/pattern.js: Read -node_modules/ajv/dist/vocabularies/validation/required.js: Read -node_modules/ajv/dist/vocabularies/validation/uniqueItems.js: Read -node_modules/ajv/package.json: Read -node_modules/ansis/index.cjs: Read -node_modules/ansis/index.mjs: Read -node_modules/ansis/package.json: Read -node_modules/anymatch/index.js: Read -node_modules/anymatch/package.json: Read -node_modules/aria-hidden/dist/es2015/index.js: Read -node_modules/aria-hidden/dist/es2015/package.json: Read -node_modules/aria-hidden/dist/package.json: Read -node_modules/aria-hidden/package.json: Read -node_modules/at-least-node/index.js: Read -node_modules/at-least-node/package.json: Read -node_modules/attr-accept/dist/es/index.js: Read -node_modules/attr-accept/dist/es/package.json: Read -node_modules/attr-accept/dist/package.json: Read -node_modules/attr-accept/package.json: Read -node_modules/autotrack/autotrack.js: Read -node_modules/autotrack/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/core-js-compat/data.js: Read -node_modules/babel-plugin-polyfill-corejs3/core-js-compat/entries.js: Read -node_modules/babel-plugin-polyfill-corejs3/core-js-compat/get-modules-list-for-target-version.js: Read -node_modules/babel-plugin-polyfill-corejs3/core-js-compat/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/lib/babel-runtime-corejs3-paths.js: Read -node_modules/babel-plugin-polyfill-corejs3/lib/built-in-definitions.js: Read -node_modules/babel-plugin-polyfill-corejs3/lib/index.js: Read -node_modules/babel-plugin-polyfill-corejs3/lib/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/lib/shipped-proposals.js: Read -node_modules/babel-plugin-polyfill-corejs3/lib/usage-filters.js: Read -node_modules/babel-plugin-polyfill-corejs3/lib/utils.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/core/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-compilation-targets/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/debug-utils.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/imports-injector.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/index.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/meta-resolver.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/node/dependencies.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/node/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/normalize-options.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/utils.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/visitors/entry.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/visitors/index.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/visitors/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/lib/visitors/usage.js: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/node_modules/@babel/core/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/node_modules/@babel/helper-compilation-targets/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/node_modules/@babel/helper-plugin-utils/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/node_modules/lodash.debounce/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-plugin-utils/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/core-js-compat/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/lodash.debounce/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/node_modules/resolve/package.json: Read -node_modules/babel-plugin-polyfill-corejs3/package.json: Read -node_modules/balanced-match/index.js: Read -node_modules/balanced-match/package.json: Read -node_modules/binary-extensions/binary-extensions.json: Read -node_modules/binary-extensions/index.js: Read -node_modules/binary-extensions/package.json: Read -node_modules/body-scroll-lock/lib/bodyScrollLock.esm.js: Read -node_modules/body-scroll-lock/lib/package.json: Read -node_modules/body-scroll-lock/package.json: Read -node_modules/braces/index.js: Read -node_modules/braces/lib/compile.js: Read -node_modules/braces/lib/constants.js: Read -node_modules/braces/lib/expand.js: Read -node_modules/braces/lib/package.json: Read -node_modules/braces/lib/parse.js: Read -node_modules/braces/lib/stringify.js: Read -node_modules/braces/lib/utils.js: Read -node_modules/braces/package.json: Read -node_modules/browserslist-to-esbuild/node_modules/browserslist/package.json: Read -node_modules/browserslist-to-esbuild/package.json: Read -node_modules/browserslist-to-esbuild/src/index.js: Read -node_modules/browserslist-to-esbuild/src/package.json: Read -node_modules/browserslist/error.js: Read -node_modules/browserslist/index.js: Read -node_modules/browserslist/node.js: Read -node_modules/browserslist/node_modules/caniuse-lite/package.json: Read -node_modules/browserslist/node_modules/electron-to-chromium/package.json: Read -node_modules/browserslist/node_modules/node-releases/package.json: Read -node_modules/browserslist/package.json: Read -node_modules/browserslist/parse.js: Read -node_modules/bufferutil/package.json: Read -node_modules/call-bind-apply-helpers/actualApply.js: Read -node_modules/call-bind-apply-helpers/applyBind.js: Read -node_modules/call-bind-apply-helpers/functionApply.js: Read -node_modules/call-bind-apply-helpers/functionCall.js: Read -node_modules/call-bind-apply-helpers/index.js: Read -node_modules/call-bind-apply-helpers/package.json: Read -node_modules/call-bind-apply-helpers/reflectApply.js: Read -node_modules/call-bind/index.js: Read -node_modules/call-bind/package.json: Read -node_modules/call-bound/index.js: Read -node_modules/call-bound/package.json: Read -node_modules/caniuse-lite/data/agents.js: Read -node_modules/caniuse-lite/data/browserVersions.js: Read -node_modules/caniuse-lite/data/browsers.js: Read -node_modules/caniuse-lite/data/package.json: Read -node_modules/caniuse-lite/dist/lib/package.json: Read -node_modules/caniuse-lite/dist/lib/statuses.js: Read -node_modules/caniuse-lite/dist/lib/supported.js: Read -node_modules/caniuse-lite/dist/package.json: Read -node_modules/caniuse-lite/dist/unpacker/agents.js: Read -node_modules/caniuse-lite/dist/unpacker/browserVersions.js: Read -node_modules/caniuse-lite/dist/unpacker/browsers.js: Read -node_modules/caniuse-lite/dist/unpacker/feature.js: Read -node_modules/caniuse-lite/dist/unpacker/package.json: Read -node_modules/caniuse-lite/dist/unpacker/region.js: Read -node_modules/caniuse-lite/package.json: Read -node_modules/character-entities-legacy/index.json: Read -node_modules/character-entities-legacy/package.json: Read -node_modules/character-reference-invalid/index.json: Read -node_modules/character-reference-invalid/package.json: Read -node_modules/chevrotain-allstar/lib/all-star-lookahead.js: Read -node_modules/chevrotain-allstar/lib/atn.js: Read -node_modules/chevrotain-allstar/lib/dfa.js: Read -node_modules/chevrotain-allstar/lib/index.js: Read -node_modules/chevrotain-allstar/lib/package.json: Read -node_modules/chevrotain-allstar/package.json: Read -node_modules/chevrotain/lib/package.json: Read -node_modules/chevrotain/lib/src/api.js: Read -node_modules/chevrotain/lib/src/diagrams/package.json: Read -node_modules/chevrotain/lib/src/diagrams/render_public.js: Read -node_modules/chevrotain/lib/src/lang/lang_extensions.js: Read -node_modules/chevrotain/lib/src/lang/package.json: Read -node_modules/chevrotain/lib/src/package.json: Read -node_modules/chevrotain/lib/src/parse/constants.js: Read -node_modules/chevrotain/lib/src/parse/cst/cst.js: Read -node_modules/chevrotain/lib/src/parse/cst/cst_visitor.js: Read -node_modules/chevrotain/lib/src/parse/cst/package.json: Read -node_modules/chevrotain/lib/src/parse/errors_public.js: Read -node_modules/chevrotain/lib/src/parse/exceptions_public.js: Read -node_modules/chevrotain/lib/src/parse/grammar/checks.js: Read -node_modules/chevrotain/lib/src/parse/grammar/first.js: Read -node_modules/chevrotain/lib/src/parse/grammar/follow.js: Read -node_modules/chevrotain/lib/src/parse/grammar/gast/gast_resolver_public.js: Read -node_modules/chevrotain/lib/src/parse/grammar/gast/package.json: Read -node_modules/chevrotain/lib/src/parse/grammar/interpreter.js: Read -node_modules/chevrotain/lib/src/parse/grammar/keys.js: Read -node_modules/chevrotain/lib/src/parse/grammar/llk_lookahead.js: Read -node_modules/chevrotain/lib/src/parse/grammar/lookahead.js: Read -node_modules/chevrotain/lib/src/parse/grammar/package.json: Read -node_modules/chevrotain/lib/src/parse/grammar/resolver.js: Read -node_modules/chevrotain/lib/src/parse/grammar/rest.js: Read -node_modules/chevrotain/lib/src/parse/package.json: Read -node_modules/chevrotain/lib/src/parse/parser/package.json: Read -node_modules/chevrotain/lib/src/parse/parser/parser.js: Read -node_modules/chevrotain/lib/src/parse/parser/traits/context_assist.js: Read -node_modules/chevrotain/lib/src/parse/parser/traits/error_handler.js: Read -node_modules/chevrotain/lib/src/parse/parser/traits/gast_recorder.js: Read -node_modules/chevrotain/lib/src/parse/parser/traits/lexer_adapter.js: Read -node_modules/chevrotain/lib/src/parse/parser/traits/looksahead.js: Read -node_modules/chevrotain/lib/src/parse/parser/traits/package.json: Read -node_modules/chevrotain/lib/src/parse/parser/traits/perf_tracer.js: Read -node_modules/chevrotain/lib/src/parse/parser/traits/recognizer_api.js: Read -node_modules/chevrotain/lib/src/parse/parser/traits/recognizer_engine.js: Read -node_modules/chevrotain/lib/src/parse/parser/traits/recoverable.js: Read -node_modules/chevrotain/lib/src/parse/parser/traits/tree_builder.js: Read -node_modules/chevrotain/lib/src/parse/parser/utils/apply_mixins.js: Read -node_modules/chevrotain/lib/src/parse/parser/utils/package.json: Read -node_modules/chevrotain/lib/src/scan/lexer.js: Read -node_modules/chevrotain/lib/src/scan/lexer_errors_public.js: Read -node_modules/chevrotain/lib/src/scan/lexer_public.js: Read -node_modules/chevrotain/lib/src/scan/package.json: Read -node_modules/chevrotain/lib/src/scan/reg_exp.js: Read -node_modules/chevrotain/lib/src/scan/reg_exp_parser.js: Read -node_modules/chevrotain/lib/src/scan/tokens.js: Read -node_modules/chevrotain/lib/src/scan/tokens_public.js: Read -node_modules/chevrotain/lib/src/version.js: Read -node_modules/chevrotain/package.json: Read -node_modules/chokidar/index.js: Read -node_modules/chokidar/lib/constants.js: Read -node_modules/chokidar/lib/fsevents-handler.js: Read -node_modules/chokidar/lib/nodefs-handler.js: Read -node_modules/chokidar/lib/package.json: Read -node_modules/chokidar/package.json: Read -node_modules/class-validator/esm5/container.js: Read -node_modules/class-validator/esm5/decorator/ValidationOptions.js: Read -node_modules/class-validator/esm5/decorator/array/ArrayContains.js: Read -node_modules/class-validator/esm5/decorator/array/ArrayMaxSize.js: Read -node_modules/class-validator/esm5/decorator/array/ArrayMinSize.js: Read -node_modules/class-validator/esm5/decorator/array/ArrayNotContains.js: Read -node_modules/class-validator/esm5/decorator/array/ArrayNotEmpty.js: Read -node_modules/class-validator/esm5/decorator/array/ArrayUnique.js: Read -node_modules/class-validator/esm5/decorator/array/package.json: Read -node_modules/class-validator/esm5/decorator/common/Allow.js: Read -node_modules/class-validator/esm5/decorator/common/Equals.js: Read -node_modules/class-validator/esm5/decorator/common/IsDefined.js: Read -node_modules/class-validator/esm5/decorator/common/IsEmpty.js: Read -node_modules/class-validator/esm5/decorator/common/IsIn.js: Read -node_modules/class-validator/esm5/decorator/common/IsLatLong.js: Read -node_modules/class-validator/esm5/decorator/common/IsLatitude.js: Read -node_modules/class-validator/esm5/decorator/common/IsLongitude.js: Read -node_modules/class-validator/esm5/decorator/common/IsNotEmpty.js: Read -node_modules/class-validator/esm5/decorator/common/IsNotIn.js: Read -node_modules/class-validator/esm5/decorator/common/IsOptional.js: Read -node_modules/class-validator/esm5/decorator/common/NotEquals.js: Read -node_modules/class-validator/esm5/decorator/common/Validate.js: Read -node_modules/class-validator/esm5/decorator/common/ValidateBy.js: Read -node_modules/class-validator/esm5/decorator/common/ValidateIf.js: Read -node_modules/class-validator/esm5/decorator/common/ValidateNested.js: Read -node_modules/class-validator/esm5/decorator/common/ValidatePromise.js: Read -node_modules/class-validator/esm5/decorator/common/package.json: Read -node_modules/class-validator/esm5/decorator/date/MaxDate.js: Read -node_modules/class-validator/esm5/decorator/date/MinDate.js: Read -node_modules/class-validator/esm5/decorator/date/package.json: Read -node_modules/class-validator/esm5/decorator/decorators.js: Read -node_modules/class-validator/esm5/decorator/number/IsDivisibleBy.js: Read -node_modules/class-validator/esm5/decorator/number/IsNegative.js: Read -node_modules/class-validator/esm5/decorator/number/IsPositive.js: Read -node_modules/class-validator/esm5/decorator/number/Max.js: Read -node_modules/class-validator/esm5/decorator/number/Min.js: Read -node_modules/class-validator/esm5/decorator/number/package.json: Read -node_modules/class-validator/esm5/decorator/object/IsInstance.js: Read -node_modules/class-validator/esm5/decorator/object/IsNotEmptyObject.js: Read -node_modules/class-validator/esm5/decorator/object/package.json: Read -node_modules/class-validator/esm5/decorator/package.json: Read -node_modules/class-validator/esm5/decorator/string/Contains.js: Read -node_modules/class-validator/esm5/decorator/string/IsAlpha.js: Read -node_modules/class-validator/esm5/decorator/string/IsAlphanumeric.js: Read -node_modules/class-validator/esm5/decorator/string/IsAscii.js: Read -node_modules/class-validator/esm5/decorator/string/IsBIC.js: Read -node_modules/class-validator/esm5/decorator/string/IsBase32.js: Read -node_modules/class-validator/esm5/decorator/string/IsBase58.js: Read -node_modules/class-validator/esm5/decorator/string/IsBase64.js: Read -node_modules/class-validator/esm5/decorator/string/IsBooleanString.js: Read -node_modules/class-validator/esm5/decorator/string/IsBtcAddress.js: Read -node_modules/class-validator/esm5/decorator/string/IsByteLength.js: Read -node_modules/class-validator/esm5/decorator/string/IsCreditCard.js: Read -node_modules/class-validator/esm5/decorator/string/IsCurrency.js: Read -node_modules/class-validator/esm5/decorator/string/IsDataURI.js: Read -node_modules/class-validator/esm5/decorator/string/IsDateString.js: Read -node_modules/class-validator/esm5/decorator/string/IsDecimal.js: Read -node_modules/class-validator/esm5/decorator/string/IsEAN.js: Read -node_modules/class-validator/esm5/decorator/string/IsEmail.js: Read -node_modules/class-validator/esm5/decorator/string/IsEthereumAddress.js: Read -node_modules/class-validator/esm5/decorator/string/IsFQDN.js: Read -node_modules/class-validator/esm5/decorator/string/IsFirebasePushId.js: Read -node_modules/class-validator/esm5/decorator/string/IsFullWidth.js: Read -node_modules/class-validator/esm5/decorator/string/IsHSL.js: Read -node_modules/class-validator/esm5/decorator/string/IsHalfWidth.js: Read -node_modules/class-validator/esm5/decorator/string/IsHash.js: Read -node_modules/class-validator/esm5/decorator/string/IsHexColor.js: Read -node_modules/class-validator/esm5/decorator/string/IsHexadecimal.js: Read -node_modules/class-validator/esm5/decorator/string/IsIBAN.js: Read -node_modules/class-validator/esm5/decorator/string/IsIP.js: Read -node_modules/class-validator/esm5/decorator/string/IsISBN.js: Read -node_modules/class-validator/esm5/decorator/string/IsISIN.js: Read -node_modules/class-validator/esm5/decorator/string/IsISO31661Alpha2.js: Read -node_modules/class-validator/esm5/decorator/string/IsISO31661Alpha3.js: Read -node_modules/class-validator/esm5/decorator/string/IsISO8601.js: Read -node_modules/class-validator/esm5/decorator/string/IsISRC.js: Read -node_modules/class-validator/esm5/decorator/string/IsISSN.js: Read -node_modules/class-validator/esm5/decorator/string/IsIdentityCard.js: Read -node_modules/class-validator/esm5/decorator/string/IsJSON.js: Read -node_modules/class-validator/esm5/decorator/string/IsJWT.js: Read -node_modules/class-validator/esm5/decorator/string/IsLocale.js: Read -node_modules/class-validator/esm5/decorator/string/IsLowercase.js: Read -node_modules/class-validator/esm5/decorator/string/IsMacAddress.js: Read -node_modules/class-validator/esm5/decorator/string/IsMagnetURI.js: Read -node_modules/class-validator/esm5/decorator/string/IsMilitaryTime.js: Read -node_modules/class-validator/esm5/decorator/string/IsMimeType.js: Read -node_modules/class-validator/esm5/decorator/string/IsMobilePhone.js: Read -node_modules/class-validator/esm5/decorator/string/IsMongoId.js: Read -node_modules/class-validator/esm5/decorator/string/IsMultibyte.js: Read -node_modules/class-validator/esm5/decorator/string/IsNumberString.js: Read -node_modules/class-validator/esm5/decorator/string/IsOctal.js: Read -node_modules/class-validator/esm5/decorator/string/IsPassportNumber.js: Read -node_modules/class-validator/esm5/decorator/string/IsPhoneNumber.js: Read -node_modules/class-validator/esm5/decorator/string/IsPort.js: Read -node_modules/class-validator/esm5/decorator/string/IsPostalCode.js: Read -node_modules/class-validator/esm5/decorator/string/IsRFC3339.js: Read -node_modules/class-validator/esm5/decorator/string/IsRgbColor.js: Read -node_modules/class-validator/esm5/decorator/string/IsSemVer.js: Read -node_modules/class-validator/esm5/decorator/string/IsStrongPassword.js: Read -node_modules/class-validator/esm5/decorator/string/IsSurrogatePair.js: Read -node_modules/class-validator/esm5/decorator/string/IsTimeZone.js: Read -node_modules/class-validator/esm5/decorator/string/IsUUID.js: Read -node_modules/class-validator/esm5/decorator/string/IsUppercase.js: Read -node_modules/class-validator/esm5/decorator/string/IsUrl.js: Read -node_modules/class-validator/esm5/decorator/string/IsVariableWidth.js: Read -node_modules/class-validator/esm5/decorator/string/Length.js: Read -node_modules/class-validator/esm5/decorator/string/Matches.js: Read -node_modules/class-validator/esm5/decorator/string/MaxLength.js: Read -node_modules/class-validator/esm5/decorator/string/MinLength.js: Read -node_modules/class-validator/esm5/decorator/string/NotContains.js: Read -node_modules/class-validator/esm5/decorator/string/is-iso4217-currency-code.js: Read -node_modules/class-validator/esm5/decorator/string/is-tax-id.js: Read -node_modules/class-validator/esm5/decorator/string/package.json: Read -node_modules/class-validator/esm5/decorator/typechecker/IsArray.js: Read -node_modules/class-validator/esm5/decorator/typechecker/IsBoolean.js: Read -node_modules/class-validator/esm5/decorator/typechecker/IsDate.js: Read -node_modules/class-validator/esm5/decorator/typechecker/IsEnum.js: Read -node_modules/class-validator/esm5/decorator/typechecker/IsInt.js: Read -node_modules/class-validator/esm5/decorator/typechecker/IsNumber.js: Read -node_modules/class-validator/esm5/decorator/typechecker/IsObject.js: Read -node_modules/class-validator/esm5/decorator/typechecker/IsString.js: Read -node_modules/class-validator/esm5/decorator/typechecker/package.json: Read -node_modules/class-validator/esm5/index.js: Read -node_modules/class-validator/esm5/metadata/ConstraintMetadata.js: Read -node_modules/class-validator/esm5/metadata/MetadataStorage.js: Read -node_modules/class-validator/esm5/metadata/ValidationMetadata.js: Read -node_modules/class-validator/esm5/metadata/package.json: Read -node_modules/class-validator/esm5/package.json: Read -node_modules/class-validator/esm5/register-decorator.js: Read -node_modules/class-validator/esm5/utils/convert-to-array.util.js: Read -node_modules/class-validator/esm5/utils/get-global.util.js: Read -node_modules/class-validator/esm5/utils/index.js: Read -node_modules/class-validator/esm5/utils/is-promise.util.js: Read -node_modules/class-validator/esm5/utils/package.json: Read -node_modules/class-validator/esm5/validation-schema/ValidationSchema.js: Read -node_modules/class-validator/esm5/validation-schema/ValidationSchemaToMetadataTransformer.js: Read -node_modules/class-validator/esm5/validation-schema/package.json: Read -node_modules/class-validator/esm5/validation/ValidationArguments.js: Read -node_modules/class-validator/esm5/validation/ValidationError.js: Read -node_modules/class-validator/esm5/validation/ValidationExecutor.js: Read -node_modules/class-validator/esm5/validation/ValidationTypes.js: Read -node_modules/class-validator/esm5/validation/ValidationUtils.js: Read -node_modules/class-validator/esm5/validation/Validator.js: Read -node_modules/class-validator/esm5/validation/ValidatorConstraintInterface.js: Read -node_modules/class-validator/esm5/validation/ValidatorOptions.js: Read -node_modules/class-validator/esm5/validation/package.json: Read -node_modules/class-validator/package.json: Read -node_modules/comma-separated-tokens/index.js: Read -node_modules/comma-separated-tokens/package.json: Read -node_modules/command-score/index.js: Read -node_modules/command-score/package.json: Read -node_modules/common-tags/lib/TemplateTag/TemplateTag.js: Read -node_modules/common-tags/lib/TemplateTag/index.js: Read -node_modules/common-tags/lib/TemplateTag/package.json: Read -node_modules/common-tags/lib/codeBlock/index.js: Read -node_modules/common-tags/lib/codeBlock/package.json: Read -node_modules/common-tags/lib/commaLists/commaLists.js: Read -node_modules/common-tags/lib/commaLists/index.js: Read -node_modules/common-tags/lib/commaLists/package.json: Read -node_modules/common-tags/lib/commaListsAnd/commaListsAnd.js: Read -node_modules/common-tags/lib/commaListsAnd/index.js: Read -node_modules/common-tags/lib/commaListsAnd/package.json: Read -node_modules/common-tags/lib/commaListsOr/commaListsOr.js: Read -node_modules/common-tags/lib/commaListsOr/index.js: Read -node_modules/common-tags/lib/commaListsOr/package.json: Read -node_modules/common-tags/lib/html/html.js: Read -node_modules/common-tags/lib/html/index.js: Read -node_modules/common-tags/lib/html/package.json: Read -node_modules/common-tags/lib/index.js: Read -node_modules/common-tags/lib/inlineArrayTransformer/index.js: Read -node_modules/common-tags/lib/inlineArrayTransformer/inlineArrayTransformer.js: Read -node_modules/common-tags/lib/inlineArrayTransformer/package.json: Read -node_modules/common-tags/lib/inlineLists/index.js: Read -node_modules/common-tags/lib/inlineLists/inlineLists.js: Read -node_modules/common-tags/lib/inlineLists/package.json: Read -node_modules/common-tags/lib/oneLine/index.js: Read -node_modules/common-tags/lib/oneLine/oneLine.js: Read -node_modules/common-tags/lib/oneLine/package.json: Read -node_modules/common-tags/lib/oneLineCommaLists/index.js: Read -node_modules/common-tags/lib/oneLineCommaLists/oneLineCommaLists.js: Read -node_modules/common-tags/lib/oneLineCommaLists/package.json: Read -node_modules/common-tags/lib/oneLineCommaListsAnd/index.js: Read -node_modules/common-tags/lib/oneLineCommaListsAnd/oneLineCommaListsAnd.js: Read -node_modules/common-tags/lib/oneLineCommaListsAnd/package.json: Read -node_modules/common-tags/lib/oneLineCommaListsOr/index.js: Read -node_modules/common-tags/lib/oneLineCommaListsOr/oneLineCommaListsOr.js: Read -node_modules/common-tags/lib/oneLineCommaListsOr/package.json: Read -node_modules/common-tags/lib/oneLineInlineLists/index.js: Read -node_modules/common-tags/lib/oneLineInlineLists/oneLineInlineLists.js: Read -node_modules/common-tags/lib/oneLineInlineLists/package.json: Read -node_modules/common-tags/lib/oneLineTrim/index.js: Read -node_modules/common-tags/lib/oneLineTrim/oneLineTrim.js: Read -node_modules/common-tags/lib/oneLineTrim/package.json: Read -node_modules/common-tags/lib/package.json: Read -node_modules/common-tags/lib/removeNonPrintingValuesTransformer/index.js: Read -node_modules/common-tags/lib/removeNonPrintingValuesTransformer/package.json: Read -node_modules/common-tags/lib/removeNonPrintingValuesTransformer/removeNonPrintingValuesTransformer.js: Read -node_modules/common-tags/lib/replaceResultTransformer/index.js: Read -node_modules/common-tags/lib/replaceResultTransformer/package.json: Read -node_modules/common-tags/lib/replaceResultTransformer/replaceResultTransformer.js: Read -node_modules/common-tags/lib/replaceStringTransformer/index.js: Read -node_modules/common-tags/lib/replaceStringTransformer/package.json: Read -node_modules/common-tags/lib/replaceStringTransformer/replaceStringTransformer.js: Read -node_modules/common-tags/lib/replaceSubstitutionTransformer/index.js: Read -node_modules/common-tags/lib/replaceSubstitutionTransformer/package.json: Read -node_modules/common-tags/lib/replaceSubstitutionTransformer/replaceSubstitutionTransformer.js: Read -node_modules/common-tags/lib/safeHtml/index.js: Read -node_modules/common-tags/lib/safeHtml/package.json: Read -node_modules/common-tags/lib/safeHtml/safeHtml.js: Read -node_modules/common-tags/lib/source/index.js: Read -node_modules/common-tags/lib/source/package.json: Read -node_modules/common-tags/lib/splitStringTransformer/index.js: Read -node_modules/common-tags/lib/splitStringTransformer/package.json: Read -node_modules/common-tags/lib/splitStringTransformer/splitStringTransformer.js: Read -node_modules/common-tags/lib/stripIndent/index.js: Read -node_modules/common-tags/lib/stripIndent/package.json: Read -node_modules/common-tags/lib/stripIndent/stripIndent.js: Read -node_modules/common-tags/lib/stripIndentTransformer/index.js: Read -node_modules/common-tags/lib/stripIndentTransformer/package.json: Read -node_modules/common-tags/lib/stripIndentTransformer/stripIndentTransformer.js: Read -node_modules/common-tags/lib/stripIndents/index.js: Read -node_modules/common-tags/lib/stripIndents/package.json: Read -node_modules/common-tags/lib/stripIndents/stripIndents.js: Read -node_modules/common-tags/lib/trimResultTransformer/index.js: Read -node_modules/common-tags/lib/trimResultTransformer/package.json: Read -node_modules/common-tags/lib/trimResultTransformer/trimResultTransformer.js: Read -node_modules/common-tags/package.json: Read -node_modules/compressorjs/dist/compressor.js: Read -node_modules/compressorjs/dist/package.json: Read -node_modules/compressorjs/package.json: Read -node_modules/compute-scroll-into-view/dist/index.js: Read -node_modules/compute-scroll-into-view/dist/package.json: Read -node_modules/compute-scroll-into-view/package.json: Read -node_modules/concat-map/index.js: Read -node_modules/concat-map/package.json: Read -node_modules/consolidated-events/lib/index.esm.js: Read -node_modules/consolidated-events/lib/package.json: Read -node_modules/consolidated-events/package.json: Read -node_modules/copy-to-clipboard/index.js: Read -node_modules/copy-to-clipboard/package.json: Read -node_modules/core-js-compat/data.json: Read -node_modules/core-js-compat/entries.json: Read -node_modules/core-js-compat/get-modules-list-for-target-version.js: Read -node_modules/core-js-compat/helpers.js: Read -node_modules/core-js-compat/modules-by-versions.json: Read -node_modules/core-js-compat/modules.json: Read -node_modules/core-js-compat/package.json: Read -node_modules/cose-base/cose-base.js: Read -node_modules/cose-base/package.json: Read -node_modules/crc-32/crc32.js: Read -node_modules/crc-32/package.json: Read -node_modules/crypto-js/core.js: Read -node_modules/crypto-js/md5.js: Read -node_modules/crypto-js/package.json: Read -node_modules/crypto-random-string/index.js: Read -node_modules/crypto-random-string/package.json: Read -node_modules/cytoscape-cose-bilkent/cytoscape-cose-bilkent.js: Read -node_modules/cytoscape-cose-bilkent/package.json: Read -node_modules/cytoscape-fcose/cytoscape-fcose.js: Read -node_modules/cytoscape-fcose/node_modules/cose-base/cose-base.js: Read -node_modules/cytoscape-fcose/node_modules/cose-base/package.json: Read -node_modules/cytoscape-fcose/node_modules/layout-base/layout-base.js: Read -node_modules/cytoscape-fcose/node_modules/layout-base/package.json: Read -node_modules/cytoscape-fcose/node_modules/package.json: Read -node_modules/cytoscape-fcose/package.json: Read -node_modules/cytoscape/dist/cytoscape.esm.mjs: Read -node_modules/cytoscape/dist/package.json: Read -node_modules/cytoscape/package.json: Read -node_modules/d3-array/package.json: Read -node_modules/d3-array/src/array.js: Read -node_modules/d3-array/src/ascending.js: Read -node_modules/d3-array/src/bin.js: Read -node_modules/d3-array/src/bisect.js: Read -node_modules/d3-array/src/bisector.js: Read -node_modules/d3-array/src/constant.js: Read -node_modules/d3-array/src/count.js: Read -node_modules/d3-array/src/cross.js: Read -node_modules/d3-array/src/cumsum.js: Read -node_modules/d3-array/src/descending.js: Read -node_modules/d3-array/src/deviation.js: Read -node_modules/d3-array/src/difference.js: Read -node_modules/d3-array/src/disjoint.js: Read -node_modules/d3-array/src/every.js: Read -node_modules/d3-array/src/extent.js: Read -node_modules/d3-array/src/filter.js: Read -node_modules/d3-array/src/fsum.js: Read -node_modules/d3-array/src/greatest.js: Read -node_modules/d3-array/src/greatestIndex.js: Read -node_modules/d3-array/src/group.js: Read -node_modules/d3-array/src/groupSort.js: Read -node_modules/d3-array/src/identity.js: Read -node_modules/d3-array/src/index.js: Read -node_modules/d3-array/src/intersection.js: Read -node_modules/d3-array/src/least.js: Read -node_modules/d3-array/src/leastIndex.js: Read -node_modules/d3-array/src/map.js: Read -node_modules/d3-array/src/max.js: Read -node_modules/d3-array/src/maxIndex.js: Read -node_modules/d3-array/src/mean.js: Read -node_modules/d3-array/src/median.js: Read -node_modules/d3-array/src/merge.js: Read -node_modules/d3-array/src/min.js: Read -node_modules/d3-array/src/minIndex.js: Read -node_modules/d3-array/src/mode.js: Read -node_modules/d3-array/src/nice.js: Read -node_modules/d3-array/src/number.js: Read -node_modules/d3-array/src/package.json: Read -node_modules/d3-array/src/pairs.js: Read -node_modules/d3-array/src/permute.js: Read -node_modules/d3-array/src/quantile.js: Read -node_modules/d3-array/src/quickselect.js: Read -node_modules/d3-array/src/range.js: Read -node_modules/d3-array/src/rank.js: Read -node_modules/d3-array/src/reduce.js: Read -node_modules/d3-array/src/reverse.js: Read -node_modules/d3-array/src/scan.js: Read -node_modules/d3-array/src/shuffle.js: Read -node_modules/d3-array/src/some.js: Read -node_modules/d3-array/src/sort.js: Read -node_modules/d3-array/src/subset.js: Read -node_modules/d3-array/src/sum.js: Read -node_modules/d3-array/src/superset.js: Read -node_modules/d3-array/src/threshold/freedmanDiaconis.js: Read -node_modules/d3-array/src/threshold/package.json: Read -node_modules/d3-array/src/threshold/scott.js: Read -node_modules/d3-array/src/threshold/sturges.js: Read -node_modules/d3-array/src/ticks.js: Read -node_modules/d3-array/src/transpose.js: Read -node_modules/d3-array/src/union.js: Read -node_modules/d3-array/src/variance.js: Read -node_modules/d3-array/src/zip.js: Read -node_modules/d3-axis/package.json: Read -node_modules/d3-axis/src/axis.js: Read -node_modules/d3-axis/src/identity.js: Read -node_modules/d3-axis/src/index.js: Read -node_modules/d3-axis/src/package.json: Read -node_modules/d3-brush/package.json: Read -node_modules/d3-brush/src/brush.js: Read -node_modules/d3-brush/src/constant.js: Read -node_modules/d3-brush/src/event.js: Read -node_modules/d3-brush/src/index.js: Read -node_modules/d3-brush/src/noevent.js: Read -node_modules/d3-brush/src/package.json: Read -node_modules/d3-chord/package.json: Read -node_modules/d3-chord/src/array.js: Read -node_modules/d3-chord/src/chord.js: Read -node_modules/d3-chord/src/constant.js: Read -node_modules/d3-chord/src/index.js: Read -node_modules/d3-chord/src/math.js: Read -node_modules/d3-chord/src/package.json: Read -node_modules/d3-chord/src/ribbon.js: Read -node_modules/d3-color/package.json: Read -node_modules/d3-color/src/color.js: Read -node_modules/d3-color/src/cubehelix.js: Read -node_modules/d3-color/src/define.js: Read -node_modules/d3-color/src/index.js: Read -node_modules/d3-color/src/lab.js: Read -node_modules/d3-color/src/math.js: Read -node_modules/d3-color/src/package.json: Read -node_modules/d3-contour/package.json: Read -node_modules/d3-contour/src/area.js: Read -node_modules/d3-contour/src/array.js: Read -node_modules/d3-contour/src/ascending.js: Read -node_modules/d3-contour/src/blur.js: Read -node_modules/d3-contour/src/constant.js: Read -node_modules/d3-contour/src/contains.js: Read -node_modules/d3-contour/src/contours.js: Read -node_modules/d3-contour/src/density.js: Read -node_modules/d3-contour/src/index.js: Read -node_modules/d3-contour/src/noop.js: Read -node_modules/d3-contour/src/package.json: Read -node_modules/d3-delaunay/package.json: Read -node_modules/d3-delaunay/src/delaunay.js: Read -node_modules/d3-delaunay/src/index.js: Read -node_modules/d3-delaunay/src/package.json: Read -node_modules/d3-delaunay/src/path.js: Read -node_modules/d3-delaunay/src/polygon.js: Read -node_modules/d3-delaunay/src/voronoi.js: Read -node_modules/d3-dispatch/package.json: Read -node_modules/d3-dispatch/src/dispatch.js: Read -node_modules/d3-dispatch/src/index.js: Read -node_modules/d3-dispatch/src/package.json: Read -node_modules/d3-drag/package.json: Read -node_modules/d3-drag/src/constant.js: Read -node_modules/d3-drag/src/drag.js: Read -node_modules/d3-drag/src/event.js: Read -node_modules/d3-drag/src/index.js: Read -node_modules/d3-drag/src/nodrag.js: Read -node_modules/d3-drag/src/noevent.js: Read -node_modules/d3-drag/src/package.json: Read -node_modules/d3-dsv/package.json: Read -node_modules/d3-dsv/src/autoType.js: Read -node_modules/d3-dsv/src/csv.js: Read -node_modules/d3-dsv/src/dsv.js: Read -node_modules/d3-dsv/src/index.js: Read -node_modules/d3-dsv/src/package.json: Read -node_modules/d3-dsv/src/tsv.js: Read -node_modules/d3-ease/package.json: Read -node_modules/d3-ease/src/back.js: Read -node_modules/d3-ease/src/bounce.js: Read -node_modules/d3-ease/src/circle.js: Read -node_modules/d3-ease/src/cubic.js: Read -node_modules/d3-ease/src/elastic.js: Read -node_modules/d3-ease/src/exp.js: Read -node_modules/d3-ease/src/index.js: Read -node_modules/d3-ease/src/linear.js: Read -node_modules/d3-ease/src/math.js: Read -node_modules/d3-ease/src/package.json: Read -node_modules/d3-ease/src/poly.js: Read -node_modules/d3-ease/src/quad.js: Read -node_modules/d3-ease/src/sin.js: Read -node_modules/d3-fetch/node_modules/d3-dsv/package.json: Read -node_modules/d3-fetch/node_modules/package.json: Read -node_modules/d3-fetch/package.json: Read -node_modules/d3-fetch/src/blob.js: Read -node_modules/d3-fetch/src/buffer.js: Read -node_modules/d3-fetch/src/dsv.js: Read -node_modules/d3-fetch/src/image.js: Read -node_modules/d3-fetch/src/index.js: Read -node_modules/d3-fetch/src/json.js: Read -node_modules/d3-fetch/src/package.json: Read -node_modules/d3-fetch/src/text.js: Read -node_modules/d3-fetch/src/xml.js: Read -node_modules/d3-force/package.json: Read -node_modules/d3-force/src/center.js: Read -node_modules/d3-force/src/collide.js: Read -node_modules/d3-force/src/constant.js: Read -node_modules/d3-force/src/index.js: Read -node_modules/d3-force/src/jiggle.js: Read -node_modules/d3-force/src/lcg.js: Read -node_modules/d3-force/src/link.js: Read -node_modules/d3-force/src/manyBody.js: Read -node_modules/d3-force/src/package.json: Read -node_modules/d3-force/src/radial.js: Read -node_modules/d3-force/src/simulation.js: Read -node_modules/d3-force/src/x.js: Read -node_modules/d3-force/src/y.js: Read -node_modules/d3-format/package.json: Read -node_modules/d3-format/src/defaultLocale.js: Read -node_modules/d3-format/src/exponent.js: Read -node_modules/d3-format/src/formatDecimal.js: Read -node_modules/d3-format/src/formatGroup.js: Read -node_modules/d3-format/src/formatNumerals.js: Read -node_modules/d3-format/src/formatPrefixAuto.js: Read -node_modules/d3-format/src/formatRounded.js: Read -node_modules/d3-format/src/formatSpecifier.js: Read -node_modules/d3-format/src/formatTrim.js: Read -node_modules/d3-format/src/formatTypes.js: Read -node_modules/d3-format/src/identity.js: Read -node_modules/d3-format/src/index.js: Read -node_modules/d3-format/src/locale.js: Read -node_modules/d3-format/src/package.json: Read -node_modules/d3-format/src/precisionFixed.js: Read -node_modules/d3-format/src/precisionPrefix.js: Read -node_modules/d3-format/src/precisionRound.js: Read -node_modules/d3-geo/package.json: Read -node_modules/d3-geo/src/area.js: Read -node_modules/d3-geo/src/bounds.js: Read -node_modules/d3-geo/src/cartesian.js: Read -node_modules/d3-geo/src/centroid.js: Read -node_modules/d3-geo/src/circle.js: Read -node_modules/d3-geo/src/clip/antimeridian.js: Read -node_modules/d3-geo/src/clip/buffer.js: Read -node_modules/d3-geo/src/clip/circle.js: Read -node_modules/d3-geo/src/clip/extent.js: Read -node_modules/d3-geo/src/clip/index.js: Read -node_modules/d3-geo/src/clip/line.js: Read -node_modules/d3-geo/src/clip/package.json: Read -node_modules/d3-geo/src/clip/rectangle.js: Read -node_modules/d3-geo/src/clip/rejoin.js: Read -node_modules/d3-geo/src/compose.js: Read -node_modules/d3-geo/src/constant.js: Read -node_modules/d3-geo/src/contains.js: Read -node_modules/d3-geo/src/distance.js: Read -node_modules/d3-geo/src/graticule.js: Read -node_modules/d3-geo/src/identity.js: Read -node_modules/d3-geo/src/index.js: Read -node_modules/d3-geo/src/interpolate.js: Read -node_modules/d3-geo/src/length.js: Read -node_modules/d3-geo/src/math.js: Read -node_modules/d3-geo/src/noop.js: Read -node_modules/d3-geo/src/package.json: Read -node_modules/d3-geo/src/path/area.js: Read -node_modules/d3-geo/src/path/bounds.js: Read -node_modules/d3-geo/src/path/centroid.js: Read -node_modules/d3-geo/src/path/context.js: Read -node_modules/d3-geo/src/path/index.js: Read -node_modules/d3-geo/src/path/measure.js: Read -node_modules/d3-geo/src/path/package.json: Read -node_modules/d3-geo/src/path/string.js: Read -node_modules/d3-geo/src/pointEqual.js: Read -node_modules/d3-geo/src/polygonContains.js: Read -node_modules/d3-geo/src/projection/albers.js: Read -node_modules/d3-geo/src/projection/albersUsa.js: Read -node_modules/d3-geo/src/projection/azimuthal.js: Read -node_modules/d3-geo/src/projection/azimuthalEqualArea.js: Read -node_modules/d3-geo/src/projection/azimuthalEquidistant.js: Read -node_modules/d3-geo/src/projection/conic.js: Read -node_modules/d3-geo/src/projection/conicConformal.js: Read -node_modules/d3-geo/src/projection/conicEqualArea.js: Read -node_modules/d3-geo/src/projection/conicEquidistant.js: Read -node_modules/d3-geo/src/projection/cylindricalEqualArea.js: Read -node_modules/d3-geo/src/projection/equalEarth.js: Read -node_modules/d3-geo/src/projection/equirectangular.js: Read -node_modules/d3-geo/src/projection/fit.js: Read -node_modules/d3-geo/src/projection/gnomonic.js: Read -node_modules/d3-geo/src/projection/identity.js: Read -node_modules/d3-geo/src/projection/index.js: Read -node_modules/d3-geo/src/projection/mercator.js: Read -node_modules/d3-geo/src/projection/naturalEarth1.js: Read -node_modules/d3-geo/src/projection/orthographic.js: Read -node_modules/d3-geo/src/projection/package.json: Read -node_modules/d3-geo/src/projection/resample.js: Read -node_modules/d3-geo/src/projection/stereographic.js: Read -node_modules/d3-geo/src/projection/transverseMercator.js: Read -node_modules/d3-geo/src/rotation.js: Read -node_modules/d3-geo/src/stream.js: Read -node_modules/d3-geo/src/transform.js: Read -node_modules/d3-hierarchy/package.json: Read -node_modules/d3-hierarchy/src/accessors.js: Read -node_modules/d3-hierarchy/src/array.js: Read -node_modules/d3-hierarchy/src/cluster.js: Read -node_modules/d3-hierarchy/src/constant.js: Read -node_modules/d3-hierarchy/src/hierarchy/ancestors.js: Read -node_modules/d3-hierarchy/src/hierarchy/count.js: Read -node_modules/d3-hierarchy/src/hierarchy/descendants.js: Read -node_modules/d3-hierarchy/src/hierarchy/each.js: Read -node_modules/d3-hierarchy/src/hierarchy/eachAfter.js: Read -node_modules/d3-hierarchy/src/hierarchy/eachBefore.js: Read -node_modules/d3-hierarchy/src/hierarchy/find.js: Read -node_modules/d3-hierarchy/src/hierarchy/index.js: Read -node_modules/d3-hierarchy/src/hierarchy/iterator.js: Read -node_modules/d3-hierarchy/src/hierarchy/leaves.js: Read -node_modules/d3-hierarchy/src/hierarchy/links.js: Read -node_modules/d3-hierarchy/src/hierarchy/package.json: Read -node_modules/d3-hierarchy/src/hierarchy/path.js: Read -node_modules/d3-hierarchy/src/hierarchy/sort.js: Read -node_modules/d3-hierarchy/src/hierarchy/sum.js: Read -node_modules/d3-hierarchy/src/index.js: Read -node_modules/d3-hierarchy/src/lcg.js: Read -node_modules/d3-hierarchy/src/pack/enclose.js: Read -node_modules/d3-hierarchy/src/pack/index.js: Read -node_modules/d3-hierarchy/src/pack/package.json: Read -node_modules/d3-hierarchy/src/pack/siblings.js: Read -node_modules/d3-hierarchy/src/package.json: Read -node_modules/d3-hierarchy/src/partition.js: Read -node_modules/d3-hierarchy/src/stratify.js: Read -node_modules/d3-hierarchy/src/tree.js: Read -node_modules/d3-hierarchy/src/treemap/binary.js: Read -node_modules/d3-hierarchy/src/treemap/dice.js: Read -node_modules/d3-hierarchy/src/treemap/index.js: Read -node_modules/d3-hierarchy/src/treemap/package.json: Read -node_modules/d3-hierarchy/src/treemap/resquarify.js: Read -node_modules/d3-hierarchy/src/treemap/round.js: Read -node_modules/d3-hierarchy/src/treemap/slice.js: Read -node_modules/d3-hierarchy/src/treemap/sliceDice.js: Read -node_modules/d3-hierarchy/src/treemap/squarify.js: Read -node_modules/d3-interpolate/package.json: Read -node_modules/d3-interpolate/src/array.js: Read -node_modules/d3-interpolate/src/basis.js: Read -node_modules/d3-interpolate/src/basisClosed.js: Read -node_modules/d3-interpolate/src/color.js: Read -node_modules/d3-interpolate/src/constant.js: Read -node_modules/d3-interpolate/src/cubehelix.js: Read -node_modules/d3-interpolate/src/date.js: Read -node_modules/d3-interpolate/src/discrete.js: Read -node_modules/d3-interpolate/src/hcl.js: Read -node_modules/d3-interpolate/src/hsl.js: Read -node_modules/d3-interpolate/src/hue.js: Read -node_modules/d3-interpolate/src/index.js: Read -node_modules/d3-interpolate/src/lab.js: Read -node_modules/d3-interpolate/src/number.js: Read -node_modules/d3-interpolate/src/numberArray.js: Read -node_modules/d3-interpolate/src/object.js: Read -node_modules/d3-interpolate/src/package.json: Read -node_modules/d3-interpolate/src/piecewise.js: Read -node_modules/d3-interpolate/src/quantize.js: Read -node_modules/d3-interpolate/src/rgb.js: Read -node_modules/d3-interpolate/src/round.js: Read -node_modules/d3-interpolate/src/string.js: Read -node_modules/d3-interpolate/src/transform/decompose.js: Read -node_modules/d3-interpolate/src/transform/index.js: Read -node_modules/d3-interpolate/src/transform/package.json: Read -node_modules/d3-interpolate/src/transform/parse.js: Read -node_modules/d3-interpolate/src/value.js: Read -node_modules/d3-interpolate/src/zoom.js: Read -node_modules/d3-path/package.json: Read -node_modules/d3-path/src/index.js: Read -node_modules/d3-path/src/package.json: Read -node_modules/d3-path/src/path.js: Read -node_modules/d3-polygon/package.json: Read -node_modules/d3-polygon/src/area.js: Read -node_modules/d3-polygon/src/centroid.js: Read -node_modules/d3-polygon/src/contains.js: Read -node_modules/d3-polygon/src/cross.js: Read -node_modules/d3-polygon/src/hull.js: Read -node_modules/d3-polygon/src/index.js: Read -node_modules/d3-polygon/src/length.js: Read -node_modules/d3-polygon/src/package.json: Read -node_modules/d3-quadtree/package.json: Read -node_modules/d3-quadtree/src/add.js: Read -node_modules/d3-quadtree/src/cover.js: Read -node_modules/d3-quadtree/src/data.js: Read -node_modules/d3-quadtree/src/extent.js: Read -node_modules/d3-quadtree/src/find.js: Read -node_modules/d3-quadtree/src/index.js: Read -node_modules/d3-quadtree/src/package.json: Read -node_modules/d3-quadtree/src/quad.js: Read -node_modules/d3-quadtree/src/quadtree.js: Read -node_modules/d3-quadtree/src/remove.js: Read -node_modules/d3-quadtree/src/root.js: Read -node_modules/d3-quadtree/src/size.js: Read -node_modules/d3-quadtree/src/visit.js: Read -node_modules/d3-quadtree/src/visitAfter.js: Read -node_modules/d3-quadtree/src/x.js: Read -node_modules/d3-quadtree/src/y.js: Read -node_modules/d3-random/package.json: Read -node_modules/d3-random/src/bates.js: Read -node_modules/d3-random/src/bernoulli.js: Read -node_modules/d3-random/src/beta.js: Read -node_modules/d3-random/src/binomial.js: Read -node_modules/d3-random/src/cauchy.js: Read -node_modules/d3-random/src/defaultSource.js: Read -node_modules/d3-random/src/exponential.js: Read -node_modules/d3-random/src/gamma.js: Read -node_modules/d3-random/src/geometric.js: Read -node_modules/d3-random/src/index.js: Read -node_modules/d3-random/src/int.js: Read -node_modules/d3-random/src/irwinHall.js: Read -node_modules/d3-random/src/lcg.js: Read -node_modules/d3-random/src/logNormal.js: Read -node_modules/d3-random/src/logistic.js: Read -node_modules/d3-random/src/normal.js: Read -node_modules/d3-random/src/package.json: Read -node_modules/d3-random/src/pareto.js: Read -node_modules/d3-random/src/poisson.js: Read -node_modules/d3-random/src/uniform.js: Read -node_modules/d3-random/src/weibull.js: Read -node_modules/d3-sankey/node_modules/d3-array/package.json: Read -node_modules/d3-sankey/node_modules/d3-array/src/array.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/ascending.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/bin.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/bisect.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/bisector.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/constant.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/count.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/cross.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/cumsum.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/descending.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/deviation.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/difference.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/disjoint.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/every.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/extent.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/filter.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/fsum.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/greatest.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/greatestIndex.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/group.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/groupSort.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/identity.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/index.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/intersection.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/least.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/leastIndex.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/map.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/max.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/maxIndex.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/mean.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/median.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/merge.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/min.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/minIndex.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/nice.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/number.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/package.json: Read -node_modules/d3-sankey/node_modules/d3-array/src/pairs.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/permute.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/quantile.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/quickselect.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/range.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/reduce.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/reverse.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/scan.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/set.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/shuffle.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/some.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/sort.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/subset.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/sum.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/superset.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/threshold/freedmanDiaconis.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/threshold/package.json: Read -node_modules/d3-sankey/node_modules/d3-array/src/threshold/scott.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/threshold/sturges.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/ticks.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/transpose.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/union.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/variance.js: Read -node_modules/d3-sankey/node_modules/d3-array/src/zip.js: Read -node_modules/d3-sankey/node_modules/d3-path/package.json: Read -node_modules/d3-sankey/node_modules/d3-path/src/index.js: Read -node_modules/d3-sankey/node_modules/d3-path/src/package.json: Read -node_modules/d3-sankey/node_modules/d3-path/src/path.js: Read -node_modules/d3-sankey/node_modules/d3-shape/package.json: Read -node_modules/d3-sankey/node_modules/d3-shape/src/arc.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/area.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/areaRadial.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/array.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/constant.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/basis.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/basisClosed.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/basisOpen.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/bundle.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/cardinal.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/cardinalClosed.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/cardinalOpen.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/catmullRom.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/catmullRomClosed.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/catmullRomOpen.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/linear.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/linearClosed.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/monotone.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/natural.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/package.json: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/radial.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/curve/step.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/descending.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/identity.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/index.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/line.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/lineRadial.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/link/index.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/link/package.json: Read -node_modules/d3-sankey/node_modules/d3-shape/src/math.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/noop.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/offset/diverging.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/offset/expand.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/offset/none.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/offset/package.json: Read -node_modules/d3-sankey/node_modules/d3-shape/src/offset/silhouette.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/offset/wiggle.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/order/appearance.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/order/ascending.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/order/descending.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/order/insideOut.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/order/none.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/order/package.json: Read -node_modules/d3-sankey/node_modules/d3-shape/src/order/reverse.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/package.json: Read -node_modules/d3-sankey/node_modules/d3-shape/src/pie.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/point.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/pointRadial.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/stack.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/symbol.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/symbol/circle.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/symbol/cross.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/symbol/diamond.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/symbol/package.json: Read -node_modules/d3-sankey/node_modules/d3-shape/src/symbol/square.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/symbol/star.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/symbol/triangle.js: Read -node_modules/d3-sankey/node_modules/d3-shape/src/symbol/wye.js: Read -node_modules/d3-sankey/node_modules/internmap/package.json: Read -node_modules/d3-sankey/node_modules/internmap/src/index.js: Read -node_modules/d3-sankey/node_modules/internmap/src/package.json: Read -node_modules/d3-sankey/node_modules/package.json: Read -node_modules/d3-sankey/package.json: Read -node_modules/d3-sankey/src/align.js: Read -node_modules/d3-sankey/src/constant.js: Read -node_modules/d3-sankey/src/index.js: Read -node_modules/d3-sankey/src/package.json: Read -node_modules/d3-sankey/src/sankey.js: Read -node_modules/d3-sankey/src/sankeyLinkHorizontal.js: Read -node_modules/d3-scale-chromatic/package.json: Read -node_modules/d3-scale-chromatic/src/categorical/Accent.js: Read -node_modules/d3-scale-chromatic/src/categorical/Dark2.js: Read -node_modules/d3-scale-chromatic/src/categorical/Paired.js: Read -node_modules/d3-scale-chromatic/src/categorical/Pastel1.js: Read -node_modules/d3-scale-chromatic/src/categorical/Pastel2.js: Read -node_modules/d3-scale-chromatic/src/categorical/Set1.js: Read -node_modules/d3-scale-chromatic/src/categorical/Set2.js: Read -node_modules/d3-scale-chromatic/src/categorical/Set3.js: Read -node_modules/d3-scale-chromatic/src/categorical/Tableau10.js: Read -node_modules/d3-scale-chromatic/src/categorical/category10.js: Read -node_modules/d3-scale-chromatic/src/categorical/package.json: Read -node_modules/d3-scale-chromatic/src/colors.js: Read -node_modules/d3-scale-chromatic/src/diverging/BrBG.js: Read -node_modules/d3-scale-chromatic/src/diverging/PRGn.js: Read -node_modules/d3-scale-chromatic/src/diverging/PiYG.js: Read -node_modules/d3-scale-chromatic/src/diverging/PuOr.js: Read -node_modules/d3-scale-chromatic/src/diverging/RdBu.js: Read -node_modules/d3-scale-chromatic/src/diverging/RdGy.js: Read -node_modules/d3-scale-chromatic/src/diverging/RdYlBu.js: Read -node_modules/d3-scale-chromatic/src/diverging/RdYlGn.js: Read -node_modules/d3-scale-chromatic/src/diverging/Spectral.js: Read -node_modules/d3-scale-chromatic/src/diverging/package.json: Read -node_modules/d3-scale-chromatic/src/index.js: Read -node_modules/d3-scale-chromatic/src/package.json: Read -node_modules/d3-scale-chromatic/src/ramp.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/BuGn.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/BuPu.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/GnBu.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/OrRd.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/PuBu.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/PuBuGn.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/PuRd.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/RdPu.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/YlGn.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/YlGnBu.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/YlOrBr.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/YlOrRd.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/cividis.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/cubehelix.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/package.json: Read -node_modules/d3-scale-chromatic/src/sequential-multi/rainbow.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/sinebow.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/turbo.js: Read -node_modules/d3-scale-chromatic/src/sequential-multi/viridis.js: Read -node_modules/d3-scale-chromatic/src/sequential-single/Blues.js: Read -node_modules/d3-scale-chromatic/src/sequential-single/Greens.js: Read -node_modules/d3-scale-chromatic/src/sequential-single/Greys.js: Read -node_modules/d3-scale-chromatic/src/sequential-single/Oranges.js: Read -node_modules/d3-scale-chromatic/src/sequential-single/Purples.js: Read -node_modules/d3-scale-chromatic/src/sequential-single/Reds.js: Read -node_modules/d3-scale-chromatic/src/sequential-single/package.json: Read -node_modules/d3-scale/package.json: Read -node_modules/d3-scale/src/band.js: Read -node_modules/d3-scale/src/constant.js: Read -node_modules/d3-scale/src/continuous.js: Read -node_modules/d3-scale/src/diverging.js: Read -node_modules/d3-scale/src/identity.js: Read -node_modules/d3-scale/src/index.js: Read -node_modules/d3-scale/src/init.js: Read -node_modules/d3-scale/src/linear.js: Read -node_modules/d3-scale/src/log.js: Read -node_modules/d3-scale/src/nice.js: Read -node_modules/d3-scale/src/number.js: Read -node_modules/d3-scale/src/ordinal.js: Read -node_modules/d3-scale/src/package.json: Read -node_modules/d3-scale/src/pow.js: Read -node_modules/d3-scale/src/quantile.js: Read -node_modules/d3-scale/src/quantize.js: Read -node_modules/d3-scale/src/radial.js: Read -node_modules/d3-scale/src/sequential.js: Read -node_modules/d3-scale/src/sequentialQuantile.js: Read -node_modules/d3-scale/src/symlog.js: Read -node_modules/d3-scale/src/threshold.js: Read -node_modules/d3-scale/src/tickFormat.js: Read -node_modules/d3-scale/src/time.js: Read -node_modules/d3-scale/src/utcTime.js: Read -node_modules/d3-selection/package.json: Read -node_modules/d3-selection/src/array.js: Read -node_modules/d3-selection/src/constant.js: Read -node_modules/d3-selection/src/create.js: Read -node_modules/d3-selection/src/creator.js: Read -node_modules/d3-selection/src/index.js: Read -node_modules/d3-selection/src/local.js: Read -node_modules/d3-selection/src/matcher.js: Read -node_modules/d3-selection/src/namespace.js: Read -node_modules/d3-selection/src/namespaces.js: Read -node_modules/d3-selection/src/package.json: Read -node_modules/d3-selection/src/pointer.js: Read -node_modules/d3-selection/src/pointers.js: Read -node_modules/d3-selection/src/select.js: Read -node_modules/d3-selection/src/selectAll.js: Read -node_modules/d3-selection/src/selection/append.js: Read -node_modules/d3-selection/src/selection/attr.js: Read -node_modules/d3-selection/src/selection/call.js: Read -node_modules/d3-selection/src/selection/classed.js: Read -node_modules/d3-selection/src/selection/clone.js: Read -node_modules/d3-selection/src/selection/data.js: Read -node_modules/d3-selection/src/selection/datum.js: Read -node_modules/d3-selection/src/selection/dispatch.js: Read -node_modules/d3-selection/src/selection/each.js: Read -node_modules/d3-selection/src/selection/empty.js: Read -node_modules/d3-selection/src/selection/enter.js: Read -node_modules/d3-selection/src/selection/exit.js: Read -node_modules/d3-selection/src/selection/filter.js: Read -node_modules/d3-selection/src/selection/html.js: Read -node_modules/d3-selection/src/selection/index.js: Read -node_modules/d3-selection/src/selection/insert.js: Read -node_modules/d3-selection/src/selection/iterator.js: Read -node_modules/d3-selection/src/selection/join.js: Read -node_modules/d3-selection/src/selection/lower.js: Read -node_modules/d3-selection/src/selection/merge.js: Read -node_modules/d3-selection/src/selection/node.js: Read -node_modules/d3-selection/src/selection/nodes.js: Read -node_modules/d3-selection/src/selection/on.js: Read -node_modules/d3-selection/src/selection/order.js: Read -node_modules/d3-selection/src/selection/package.json: Read -node_modules/d3-selection/src/selection/property.js: Read -node_modules/d3-selection/src/selection/raise.js: Read -node_modules/d3-selection/src/selection/remove.js: Read -node_modules/d3-selection/src/selection/select.js: Read -node_modules/d3-selection/src/selection/selectAll.js: Read -node_modules/d3-selection/src/selection/selectChild.js: Read -node_modules/d3-selection/src/selection/selectChildren.js: Read -node_modules/d3-selection/src/selection/size.js: Read -node_modules/d3-selection/src/selection/sort.js: Read -node_modules/d3-selection/src/selection/sparse.js: Read -node_modules/d3-selection/src/selection/style.js: Read -node_modules/d3-selection/src/selection/text.js: Read -node_modules/d3-selection/src/selector.js: Read -node_modules/d3-selection/src/selectorAll.js: Read -node_modules/d3-selection/src/sourceEvent.js: Read -node_modules/d3-selection/src/window.js: Read -node_modules/d3-shape/package.json: Read -node_modules/d3-shape/src/arc.js: Read -node_modules/d3-shape/src/area.js: Read -node_modules/d3-shape/src/areaRadial.js: Read -node_modules/d3-shape/src/array.js: Read -node_modules/d3-shape/src/constant.js: Read -node_modules/d3-shape/src/curve/basis.js: Read -node_modules/d3-shape/src/curve/basisClosed.js: Read -node_modules/d3-shape/src/curve/basisOpen.js: Read -node_modules/d3-shape/src/curve/bump.js: Read -node_modules/d3-shape/src/curve/bundle.js: Read -node_modules/d3-shape/src/curve/cardinal.js: Read -node_modules/d3-shape/src/curve/cardinalClosed.js: Read -node_modules/d3-shape/src/curve/cardinalOpen.js: Read -node_modules/d3-shape/src/curve/catmullRom.js: Read -node_modules/d3-shape/src/curve/catmullRomClosed.js: Read -node_modules/d3-shape/src/curve/catmullRomOpen.js: Read -node_modules/d3-shape/src/curve/linear.js: Read -node_modules/d3-shape/src/curve/linearClosed.js: Read -node_modules/d3-shape/src/curve/monotone.js: Read -node_modules/d3-shape/src/curve/natural.js: Read -node_modules/d3-shape/src/curve/package.json: Read -node_modules/d3-shape/src/curve/radial.js: Read -node_modules/d3-shape/src/curve/step.js: Read -node_modules/d3-shape/src/descending.js: Read -node_modules/d3-shape/src/identity.js: Read -node_modules/d3-shape/src/index.js: Read -node_modules/d3-shape/src/line.js: Read -node_modules/d3-shape/src/lineRadial.js: Read -node_modules/d3-shape/src/link.js: Read -node_modules/d3-shape/src/math.js: Read -node_modules/d3-shape/src/noop.js: Read -node_modules/d3-shape/src/offset/diverging.js: Read -node_modules/d3-shape/src/offset/expand.js: Read -node_modules/d3-shape/src/offset/none.js: Read -node_modules/d3-shape/src/offset/package.json: Read -node_modules/d3-shape/src/offset/silhouette.js: Read -node_modules/d3-shape/src/offset/wiggle.js: Read -node_modules/d3-shape/src/order/appearance.js: Read -node_modules/d3-shape/src/order/ascending.js: Read -node_modules/d3-shape/src/order/descending.js: Read -node_modules/d3-shape/src/order/insideOut.js: Read -node_modules/d3-shape/src/order/none.js: Read -node_modules/d3-shape/src/order/package.json: Read -node_modules/d3-shape/src/order/reverse.js: Read -node_modules/d3-shape/src/package.json: Read -node_modules/d3-shape/src/pie.js: Read -node_modules/d3-shape/src/point.js: Read -node_modules/d3-shape/src/pointRadial.js: Read -node_modules/d3-shape/src/stack.js: Read -node_modules/d3-shape/src/symbol.js: Read -node_modules/d3-shape/src/symbol/asterisk.js: Read -node_modules/d3-shape/src/symbol/circle.js: Read -node_modules/d3-shape/src/symbol/cross.js: Read -node_modules/d3-shape/src/symbol/diamond.js: Read -node_modules/d3-shape/src/symbol/diamond2.js: Read -node_modules/d3-shape/src/symbol/package.json: Read -node_modules/d3-shape/src/symbol/plus.js: Read -node_modules/d3-shape/src/symbol/square.js: Read -node_modules/d3-shape/src/symbol/square2.js: Read -node_modules/d3-shape/src/symbol/star.js: Read -node_modules/d3-shape/src/symbol/triangle.js: Read -node_modules/d3-shape/src/symbol/triangle2.js: Read -node_modules/d3-shape/src/symbol/wye.js: Read -node_modules/d3-shape/src/symbol/x.js: Read -node_modules/d3-time-format/package.json: Read -node_modules/d3-time-format/src/defaultLocale.js: Read -node_modules/d3-time-format/src/index.js: Read -node_modules/d3-time-format/src/isoFormat.js: Read -node_modules/d3-time-format/src/isoParse.js: Read -node_modules/d3-time-format/src/locale.js: Read -node_modules/d3-time-format/src/package.json: Read -node_modules/d3-time/package.json: Read -node_modules/d3-time/src/day.js: Read -node_modules/d3-time/src/duration.js: Read -node_modules/d3-time/src/hour.js: Read -node_modules/d3-time/src/index.js: Read -node_modules/d3-time/src/interval.js: Read -node_modules/d3-time/src/millisecond.js: Read -node_modules/d3-time/src/minute.js: Read -node_modules/d3-time/src/month.js: Read -node_modules/d3-time/src/package.json: Read -node_modules/d3-time/src/second.js: Read -node_modules/d3-time/src/ticks.js: Read -node_modules/d3-time/src/utcDay.js: Read -node_modules/d3-time/src/utcHour.js: Read -node_modules/d3-time/src/utcMinute.js: Read -node_modules/d3-time/src/utcMonth.js: Read -node_modules/d3-time/src/utcWeek.js: Read -node_modules/d3-time/src/utcYear.js: Read -node_modules/d3-time/src/week.js: Read -node_modules/d3-time/src/year.js: Read -node_modules/d3-timer/package.json: Read -node_modules/d3-timer/src/index.js: Read -node_modules/d3-timer/src/interval.js: Read -node_modules/d3-timer/src/package.json: Read -node_modules/d3-timer/src/timeout.js: Read -node_modules/d3-timer/src/timer.js: Read -node_modules/d3-transition/package.json: Read -node_modules/d3-transition/src/active.js: Read -node_modules/d3-transition/src/index.js: Read -node_modules/d3-transition/src/interrupt.js: Read -node_modules/d3-transition/src/package.json: Read -node_modules/d3-transition/src/selection/index.js: Read -node_modules/d3-transition/src/selection/interrupt.js: Read -node_modules/d3-transition/src/selection/package.json: Read -node_modules/d3-transition/src/selection/transition.js: Read -node_modules/d3-transition/src/transition/attr.js: Read -node_modules/d3-transition/src/transition/attrTween.js: Read -node_modules/d3-transition/src/transition/delay.js: Read -node_modules/d3-transition/src/transition/duration.js: Read -node_modules/d3-transition/src/transition/ease.js: Read -node_modules/d3-transition/src/transition/easeVarying.js: Read -node_modules/d3-transition/src/transition/end.js: Read -node_modules/d3-transition/src/transition/filter.js: Read -node_modules/d3-transition/src/transition/index.js: Read -node_modules/d3-transition/src/transition/interpolate.js: Read -node_modules/d3-transition/src/transition/merge.js: Read -node_modules/d3-transition/src/transition/on.js: Read -node_modules/d3-transition/src/transition/package.json: Read -node_modules/d3-transition/src/transition/remove.js: Read -node_modules/d3-transition/src/transition/schedule.js: Read -node_modules/d3-transition/src/transition/select.js: Read -node_modules/d3-transition/src/transition/selectAll.js: Read -node_modules/d3-transition/src/transition/selection.js: Read -node_modules/d3-transition/src/transition/style.js: Read -node_modules/d3-transition/src/transition/styleTween.js: Read -node_modules/d3-transition/src/transition/text.js: Read -node_modules/d3-transition/src/transition/textTween.js: Read -node_modules/d3-transition/src/transition/transition.js: Read -node_modules/d3-transition/src/transition/tween.js: Read -node_modules/d3-zoom/package.json: Read -node_modules/d3-zoom/src/constant.js: Read -node_modules/d3-zoom/src/event.js: Read -node_modules/d3-zoom/src/index.js: Read -node_modules/d3-zoom/src/noevent.js: Read -node_modules/d3-zoom/src/package.json: Read -node_modules/d3-zoom/src/transform.js: Read -node_modules/d3-zoom/src/zoom.js: Read -node_modules/d3/node_modules/d3-array/package.json: Read -node_modules/d3/node_modules/d3-axis/package.json: Read -node_modules/d3/node_modules/d3-brush/package.json: Read -node_modules/d3/node_modules/d3-chord/package.json: Read -node_modules/d3/node_modules/d3-color/package.json: Read -node_modules/d3/node_modules/d3-contour/package.json: Read -node_modules/d3/node_modules/d3-delaunay/package.json: Read -node_modules/d3/node_modules/d3-dispatch/package.json: Read -node_modules/d3/node_modules/d3-drag/package.json: Read -node_modules/d3/node_modules/d3-dsv/package.json: Read -node_modules/d3/node_modules/d3-ease/package.json: Read -node_modules/d3/node_modules/d3-fetch/package.json: Read -node_modules/d3/node_modules/d3-force/package.json: Read -node_modules/d3/node_modules/d3-format/package.json: Read -node_modules/d3/node_modules/d3-geo/package.json: Read -node_modules/d3/node_modules/d3-hierarchy/package.json: Read -node_modules/d3/node_modules/d3-interpolate/package.json: Read -node_modules/d3/node_modules/d3-path/package.json: Read -node_modules/d3/node_modules/d3-polygon/package.json: Read -node_modules/d3/node_modules/d3-quadtree/package.json: Read -node_modules/d3/node_modules/d3-random/package.json: Read -node_modules/d3/node_modules/d3-scale-chromatic/package.json: Read -node_modules/d3/node_modules/d3-scale/package.json: Read -node_modules/d3/node_modules/d3-selection/package.json: Read -node_modules/d3/node_modules/d3-shape/package.json: Read -node_modules/d3/node_modules/d3-time-format/package.json: Read -node_modules/d3/node_modules/d3-time/package.json: Read -node_modules/d3/node_modules/d3-timer/package.json: Read -node_modules/d3/node_modules/d3-transition/package.json: Read -node_modules/d3/node_modules/d3-zoom/package.json: Read -node_modules/d3/node_modules/package.json: Read -node_modules/d3/package.json: Read -node_modules/d3/src/index.js: Read -node_modules/d3/src/package.json: Read -node_modules/dagre-d3-es/package.json: Read -node_modules/dagre-d3-es/src/dagre/acyclic.js: Read -node_modules/dagre-d3-es/src/dagre/add-border-segments.js: Read -node_modules/dagre-d3-es/src/dagre/coordinate-system.js: Read -node_modules/dagre-d3-es/src/dagre/data/list.js: Read -node_modules/dagre-d3-es/src/dagre/data/package.json: Read -node_modules/dagre-d3-es/src/dagre/greedy-fas.js: Read -node_modules/dagre-d3-es/src/dagre/index.js: Read -node_modules/dagre-d3-es/src/dagre/layout.js: Read -node_modules/dagre-d3-es/src/dagre/nesting-graph.js: Read -node_modules/dagre-d3-es/src/dagre/normalize.js: Read -node_modules/dagre-d3-es/src/dagre/order/add-subgraph-constraints.js: Read -node_modules/dagre-d3-es/src/dagre/order/barycenter.js: Read -node_modules/dagre-d3-es/src/dagre/order/build-layer-graph.js: Read -node_modules/dagre-d3-es/src/dagre/order/cross-count.js: Read -node_modules/dagre-d3-es/src/dagre/order/index.js: Read -node_modules/dagre-d3-es/src/dagre/order/init-order.js: Read -node_modules/dagre-d3-es/src/dagre/order/package.json: Read -node_modules/dagre-d3-es/src/dagre/order/resolve-conflicts.js: Read -node_modules/dagre-d3-es/src/dagre/order/sort-subgraph.js: Read -node_modules/dagre-d3-es/src/dagre/order/sort.js: Read -node_modules/dagre-d3-es/src/dagre/package.json: Read -node_modules/dagre-d3-es/src/dagre/parent-dummy-chains.js: Read -node_modules/dagre-d3-es/src/dagre/position/bk.js: Read -node_modules/dagre-d3-es/src/dagre/position/index.js: Read -node_modules/dagre-d3-es/src/dagre/position/package.json: Read -node_modules/dagre-d3-es/src/dagre/rank/feasible-tree.js: Read -node_modules/dagre-d3-es/src/dagre/rank/index.js: Read -node_modules/dagre-d3-es/src/dagre/rank/network-simplex.js: Read -node_modules/dagre-d3-es/src/dagre/rank/package.json: Read -node_modules/dagre-d3-es/src/dagre/rank/util.js: Read -node_modules/dagre-d3-es/src/dagre/util.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/components.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/dfs.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/dijkstra-all.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/dijkstra.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/find-cycles.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/floyd-warshall.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/index.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/is-acyclic.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/package.json: Read -node_modules/dagre-d3-es/src/graphlib/alg/postorder.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/preorder.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/prim.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/tarjan.js: Read -node_modules/dagre-d3-es/src/graphlib/alg/topsort.js: Read -node_modules/dagre-d3-es/src/graphlib/data/package.json: Read -node_modules/dagre-d3-es/src/graphlib/data/priority-queue.js: Read -node_modules/dagre-d3-es/src/graphlib/graph.js: Read -node_modules/dagre-d3-es/src/graphlib/index.js: Read -node_modules/dagre-d3-es/src/graphlib/json.js: Read -node_modules/dagre-d3-es/src/graphlib/package.json: Read -node_modules/dagre-d3-es/src/package.json: Read -node_modules/date-fns/_lib/addLeadingZeros.mjs: Read -node_modules/date-fns/_lib/defaultLocale.mjs: Read -node_modules/date-fns/_lib/defaultOptions.mjs: Read -node_modules/date-fns/_lib/format/formatters.mjs: Read -node_modules/date-fns/_lib/format/lightFormatters.mjs: Read -node_modules/date-fns/_lib/format/longFormatters.mjs: Read -node_modules/date-fns/_lib/format/package.json: Read -node_modules/date-fns/_lib/getRoundingMethod.mjs: Read -node_modules/date-fns/_lib/getTimezoneOffsetInMilliseconds.mjs: Read -node_modules/date-fns/_lib/package.json: Read -node_modules/date-fns/_lib/protectedTokens.mjs: Read -node_modules/date-fns/add.mjs: Read -node_modules/date-fns/addBusinessDays.mjs: Read -node_modules/date-fns/addDays.mjs: Read -node_modules/date-fns/addHours.mjs: Read -node_modules/date-fns/addISOWeekYears.mjs: Read -node_modules/date-fns/addMilliseconds.mjs: Read -node_modules/date-fns/addMinutes.mjs: Read -node_modules/date-fns/addMonths.mjs: Read -node_modules/date-fns/addQuarters.mjs: Read -node_modules/date-fns/addSeconds.mjs: Read -node_modules/date-fns/addWeeks.mjs: Read -node_modules/date-fns/addYears.mjs: Read -node_modules/date-fns/areIntervalsOverlapping.mjs: Read -node_modules/date-fns/clamp.mjs: Read -node_modules/date-fns/closestIndexTo.mjs: Read -node_modules/date-fns/closestTo.mjs: Read -node_modules/date-fns/compareAsc.mjs: Read -node_modules/date-fns/compareDesc.mjs: Read -node_modules/date-fns/constants.mjs: Read -node_modules/date-fns/constructFrom.mjs: Read -node_modules/date-fns/constructNow.mjs: Read -node_modules/date-fns/daysToWeeks.mjs: Read -node_modules/date-fns/differenceInBusinessDays.mjs: Read -node_modules/date-fns/differenceInCalendarDays.mjs: Read -node_modules/date-fns/differenceInCalendarISOWeekYears.mjs: Read -node_modules/date-fns/differenceInCalendarISOWeeks.mjs: Read -node_modules/date-fns/differenceInCalendarMonths.mjs: Read -node_modules/date-fns/differenceInCalendarQuarters.mjs: Read -node_modules/date-fns/differenceInCalendarWeeks.mjs: Read -node_modules/date-fns/differenceInCalendarYears.mjs: Read -node_modules/date-fns/differenceInDays.mjs: Read -node_modules/date-fns/differenceInHours.mjs: Read -node_modules/date-fns/differenceInISOWeekYears.mjs: Read -node_modules/date-fns/differenceInMilliseconds.mjs: Read -node_modules/date-fns/differenceInMinutes.mjs: Read -node_modules/date-fns/differenceInMonths.mjs: Read -node_modules/date-fns/differenceInQuarters.mjs: Read -node_modules/date-fns/differenceInSeconds.mjs: Read -node_modules/date-fns/differenceInWeeks.mjs: Read -node_modules/date-fns/differenceInYears.mjs: Read -node_modules/date-fns/eachDayOfInterval.mjs: Read -node_modules/date-fns/eachHourOfInterval.mjs: Read -node_modules/date-fns/eachMinuteOfInterval.mjs: Read -node_modules/date-fns/eachMonthOfInterval.mjs: Read -node_modules/date-fns/eachQuarterOfInterval.mjs: Read -node_modules/date-fns/eachWeekOfInterval.mjs: Read -node_modules/date-fns/eachWeekendOfInterval.mjs: Read -node_modules/date-fns/eachWeekendOfMonth.mjs: Read -node_modules/date-fns/eachWeekendOfYear.mjs: Read -node_modules/date-fns/eachYearOfInterval.mjs: Read -node_modules/date-fns/endOfDay.mjs: Read -node_modules/date-fns/endOfDecade.mjs: Read -node_modules/date-fns/endOfHour.mjs: Read -node_modules/date-fns/endOfISOWeek.mjs: Read -node_modules/date-fns/endOfISOWeekYear.mjs: Read -node_modules/date-fns/endOfMinute.mjs: Read -node_modules/date-fns/endOfMonth.mjs: Read -node_modules/date-fns/endOfQuarter.mjs: Read -node_modules/date-fns/endOfSecond.mjs: Read -node_modules/date-fns/endOfToday.mjs: Read -node_modules/date-fns/endOfTomorrow.mjs: Read -node_modules/date-fns/endOfWeek.mjs: Read -node_modules/date-fns/endOfYear.mjs: Read -node_modules/date-fns/endOfYesterday.mjs: Read -node_modules/date-fns/format.mjs: Read -node_modules/date-fns/formatDistance.mjs: Read -node_modules/date-fns/formatDistanceStrict.mjs: Read -node_modules/date-fns/formatDistanceToNow.mjs: Read -node_modules/date-fns/formatDistanceToNowStrict.mjs: Read -node_modules/date-fns/formatDuration.mjs: Read -node_modules/date-fns/formatISO.mjs: Read -node_modules/date-fns/formatISO9075.mjs: Read -node_modules/date-fns/formatISODuration.mjs: Read -node_modules/date-fns/formatRFC3339.mjs: Read -node_modules/date-fns/formatRFC7231.mjs: Read -node_modules/date-fns/formatRelative.mjs: Read -node_modules/date-fns/fromUnixTime.mjs: Read -node_modules/date-fns/getDate.mjs: Read -node_modules/date-fns/getDay.mjs: Read -node_modules/date-fns/getDayOfYear.mjs: Read -node_modules/date-fns/getDaysInMonth.mjs: Read -node_modules/date-fns/getDaysInYear.mjs: Read -node_modules/date-fns/getDecade.mjs: Read -node_modules/date-fns/getDefaultOptions.mjs: Read -node_modules/date-fns/getHours.mjs: Read -node_modules/date-fns/getISODay.mjs: Read -node_modules/date-fns/getISOWeek.mjs: Read -node_modules/date-fns/getISOWeekYear.mjs: Read -node_modules/date-fns/getISOWeeksInYear.mjs: Read -node_modules/date-fns/getMilliseconds.mjs: Read -node_modules/date-fns/getMinutes.mjs: Read -node_modules/date-fns/getMonth.mjs: Read -node_modules/date-fns/getOverlappingDaysInIntervals.mjs: Read -node_modules/date-fns/getQuarter.mjs: Read -node_modules/date-fns/getSeconds.mjs: Read -node_modules/date-fns/getTime.mjs: Read -node_modules/date-fns/getUnixTime.mjs: Read -node_modules/date-fns/getWeek.mjs: Read -node_modules/date-fns/getWeekOfMonth.mjs: Read -node_modules/date-fns/getWeekYear.mjs: Read -node_modules/date-fns/getWeeksInMonth.mjs: Read -node_modules/date-fns/getYear.mjs: Read -node_modules/date-fns/hoursToMilliseconds.mjs: Read -node_modules/date-fns/hoursToMinutes.mjs: Read -node_modules/date-fns/hoursToSeconds.mjs: Read -node_modules/date-fns/index.mjs: Read -node_modules/date-fns/interval.mjs: Read -node_modules/date-fns/intervalToDuration.mjs: Read -node_modules/date-fns/intlFormat.mjs: Read -node_modules/date-fns/intlFormatDistance.mjs: Read -node_modules/date-fns/isAfter.mjs: Read -node_modules/date-fns/isBefore.mjs: Read -node_modules/date-fns/isDate.mjs: Read -node_modules/date-fns/isEqual.mjs: Read -node_modules/date-fns/isExists.mjs: Read -node_modules/date-fns/isFirstDayOfMonth.mjs: Read -node_modules/date-fns/isFriday.mjs: Read -node_modules/date-fns/isFuture.mjs: Read -node_modules/date-fns/isLastDayOfMonth.mjs: Read -node_modules/date-fns/isLeapYear.mjs: Read -node_modules/date-fns/isMatch.mjs: Read -node_modules/date-fns/isMonday.mjs: Read -node_modules/date-fns/isPast.mjs: Read -node_modules/date-fns/isSameDay.mjs: Read -node_modules/date-fns/isSameHour.mjs: Read -node_modules/date-fns/isSameISOWeek.mjs: Read -node_modules/date-fns/isSameISOWeekYear.mjs: Read -node_modules/date-fns/isSameMinute.mjs: Read -node_modules/date-fns/isSameMonth.mjs: Read -node_modules/date-fns/isSameQuarter.mjs: Read -node_modules/date-fns/isSameSecond.mjs: Read -node_modules/date-fns/isSameWeek.mjs: Read -node_modules/date-fns/isSameYear.mjs: Read -node_modules/date-fns/isSaturday.mjs: Read -node_modules/date-fns/isSunday.mjs: Read -node_modules/date-fns/isThisHour.mjs: Read -node_modules/date-fns/isThisISOWeek.mjs: Read -node_modules/date-fns/isThisMinute.mjs: Read -node_modules/date-fns/isThisMonth.mjs: Read -node_modules/date-fns/isThisQuarter.mjs: Read -node_modules/date-fns/isThisSecond.mjs: Read -node_modules/date-fns/isThisWeek.mjs: Read -node_modules/date-fns/isThisYear.mjs: Read -node_modules/date-fns/isThursday.mjs: Read -node_modules/date-fns/isToday.mjs: Read -node_modules/date-fns/isTomorrow.mjs: Read -node_modules/date-fns/isTuesday.mjs: Read -node_modules/date-fns/isValid.mjs: Read -node_modules/date-fns/isWednesday.mjs: Read -node_modules/date-fns/isWeekend.mjs: Read -node_modules/date-fns/isWithinInterval.mjs: Read -node_modules/date-fns/isYesterday.mjs: Read -node_modules/date-fns/lastDayOfDecade.mjs: Read -node_modules/date-fns/lastDayOfISOWeek.mjs: Read -node_modules/date-fns/lastDayOfISOWeekYear.mjs: Read -node_modules/date-fns/lastDayOfMonth.mjs: Read -node_modules/date-fns/lastDayOfQuarter.mjs: Read -node_modules/date-fns/lastDayOfWeek.mjs: Read -node_modules/date-fns/lastDayOfYear.mjs: Read -node_modules/date-fns/lightFormat.mjs: Read -node_modules/date-fns/locale.mjs: Read -node_modules/date-fns/locale/_lib/buildFormatLongFn.mjs: Read -node_modules/date-fns/locale/_lib/buildLocalizeFn.mjs: Read -node_modules/date-fns/locale/_lib/buildMatchFn.mjs: Read -node_modules/date-fns/locale/_lib/buildMatchPatternFn.mjs: Read -node_modules/date-fns/locale/_lib/package.json: Read -node_modules/date-fns/locale/af.mjs: Read -node_modules/date-fns/locale/af/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/af/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/af/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/af/_lib/localize.mjs: Read -node_modules/date-fns/locale/af/_lib/match.mjs: Read -node_modules/date-fns/locale/af/_lib/package.json: Read -node_modules/date-fns/locale/af/package.json: Read -node_modules/date-fns/locale/ar-DZ.mjs: Read -node_modules/date-fns/locale/ar-DZ/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ar-DZ/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ar-DZ/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ar-DZ/_lib/localize.mjs: Read -node_modules/date-fns/locale/ar-DZ/_lib/match.mjs: Read -node_modules/date-fns/locale/ar-DZ/_lib/package.json: Read -node_modules/date-fns/locale/ar-DZ/package.json: Read -node_modules/date-fns/locale/ar-EG.mjs: Read -node_modules/date-fns/locale/ar-EG/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ar-EG/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ar-EG/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ar-EG/_lib/localize.mjs: Read -node_modules/date-fns/locale/ar-EG/_lib/match.mjs: Read -node_modules/date-fns/locale/ar-EG/_lib/package.json: Read -node_modules/date-fns/locale/ar-EG/package.json: Read -node_modules/date-fns/locale/ar-MA.mjs: Read -node_modules/date-fns/locale/ar-MA/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ar-MA/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ar-MA/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ar-MA/_lib/localize.mjs: Read -node_modules/date-fns/locale/ar-MA/_lib/match.mjs: Read -node_modules/date-fns/locale/ar-MA/_lib/package.json: Read -node_modules/date-fns/locale/ar-MA/package.json: Read -node_modules/date-fns/locale/ar-SA.mjs: Read -node_modules/date-fns/locale/ar-SA/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ar-SA/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ar-SA/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ar-SA/_lib/localize.mjs: Read -node_modules/date-fns/locale/ar-SA/_lib/match.mjs: Read -node_modules/date-fns/locale/ar-SA/_lib/package.json: Read -node_modules/date-fns/locale/ar-SA/package.json: Read -node_modules/date-fns/locale/ar-TN.mjs: Read -node_modules/date-fns/locale/ar-TN/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ar-TN/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ar-TN/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ar-TN/_lib/localize.mjs: Read -node_modules/date-fns/locale/ar-TN/_lib/match.mjs: Read -node_modules/date-fns/locale/ar-TN/_lib/package.json: Read -node_modules/date-fns/locale/ar-TN/package.json: Read -node_modules/date-fns/locale/ar.mjs: Read -node_modules/date-fns/locale/ar/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ar/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ar/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ar/_lib/localize.mjs: Read -node_modules/date-fns/locale/ar/_lib/match.mjs: Read -node_modules/date-fns/locale/ar/_lib/package.json: Read -node_modules/date-fns/locale/ar/package.json: Read -node_modules/date-fns/locale/az.mjs: Read -node_modules/date-fns/locale/az/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/az/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/az/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/az/_lib/localize.mjs: Read -node_modules/date-fns/locale/az/_lib/match.mjs: Read -node_modules/date-fns/locale/az/_lib/package.json: Read -node_modules/date-fns/locale/az/package.json: Read -node_modules/date-fns/locale/be-tarask.mjs: Read -node_modules/date-fns/locale/be-tarask/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/be-tarask/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/be-tarask/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/be-tarask/_lib/localize.mjs: Read -node_modules/date-fns/locale/be-tarask/_lib/match.mjs: Read -node_modules/date-fns/locale/be-tarask/_lib/package.json: Read -node_modules/date-fns/locale/be-tarask/package.json: Read -node_modules/date-fns/locale/be.mjs: Read -node_modules/date-fns/locale/be/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/be/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/be/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/be/_lib/localize.mjs: Read -node_modules/date-fns/locale/be/_lib/match.mjs: Read -node_modules/date-fns/locale/be/_lib/package.json: Read -node_modules/date-fns/locale/be/package.json: Read -node_modules/date-fns/locale/bg.mjs: Read -node_modules/date-fns/locale/bg/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/bg/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/bg/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/bg/_lib/localize.mjs: Read -node_modules/date-fns/locale/bg/_lib/match.mjs: Read -node_modules/date-fns/locale/bg/_lib/package.json: Read -node_modules/date-fns/locale/bg/package.json: Read -node_modules/date-fns/locale/bn.mjs: Read -node_modules/date-fns/locale/bn/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/bn/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/bn/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/bn/_lib/localize.mjs: Read -node_modules/date-fns/locale/bn/_lib/match.mjs: Read -node_modules/date-fns/locale/bn/_lib/package.json: Read -node_modules/date-fns/locale/bn/package.json: Read -node_modules/date-fns/locale/bs.mjs: Read -node_modules/date-fns/locale/bs/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/bs/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/bs/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/bs/_lib/localize.mjs: Read -node_modules/date-fns/locale/bs/_lib/match.mjs: Read -node_modules/date-fns/locale/bs/_lib/package.json: Read -node_modules/date-fns/locale/bs/package.json: Read -node_modules/date-fns/locale/ca.mjs: Read -node_modules/date-fns/locale/ca/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ca/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ca/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ca/_lib/localize.mjs: Read -node_modules/date-fns/locale/ca/_lib/match.mjs: Read -node_modules/date-fns/locale/ca/_lib/package.json: Read -node_modules/date-fns/locale/ca/package.json: Read -node_modules/date-fns/locale/ckb.mjs: Read -node_modules/date-fns/locale/ckb/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ckb/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ckb/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ckb/_lib/localize.mjs: Read -node_modules/date-fns/locale/ckb/_lib/match.mjs: Read -node_modules/date-fns/locale/ckb/_lib/package.json: Read -node_modules/date-fns/locale/ckb/package.json: Read -node_modules/date-fns/locale/cs.mjs: Read -node_modules/date-fns/locale/cs/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/cs/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/cs/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/cs/_lib/localize.mjs: Read -node_modules/date-fns/locale/cs/_lib/match.mjs: Read -node_modules/date-fns/locale/cs/_lib/package.json: Read -node_modules/date-fns/locale/cs/package.json: Read -node_modules/date-fns/locale/cy.mjs: Read -node_modules/date-fns/locale/cy/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/cy/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/cy/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/cy/_lib/localize.mjs: Read -node_modules/date-fns/locale/cy/_lib/match.mjs: Read -node_modules/date-fns/locale/cy/_lib/package.json: Read -node_modules/date-fns/locale/cy/package.json: Read -node_modules/date-fns/locale/da.mjs: Read -node_modules/date-fns/locale/da/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/da/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/da/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/da/_lib/localize.mjs: Read -node_modules/date-fns/locale/da/_lib/match.mjs: Read -node_modules/date-fns/locale/da/_lib/package.json: Read -node_modules/date-fns/locale/da/package.json: Read -node_modules/date-fns/locale/de-AT.mjs: Read -node_modules/date-fns/locale/de-AT/_lib/localize.mjs: Read -node_modules/date-fns/locale/de-AT/_lib/package.json: Read -node_modules/date-fns/locale/de-AT/package.json: Read -node_modules/date-fns/locale/de.mjs: Read -node_modules/date-fns/locale/de/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/de/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/de/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/de/_lib/localize.mjs: Read -node_modules/date-fns/locale/de/_lib/match.mjs: Read -node_modules/date-fns/locale/de/_lib/package.json: Read -node_modules/date-fns/locale/de/package.json: Read -node_modules/date-fns/locale/el.mjs: Read -node_modules/date-fns/locale/el/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/el/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/el/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/el/_lib/localize.mjs: Read -node_modules/date-fns/locale/el/_lib/match.mjs: Read -node_modules/date-fns/locale/el/_lib/package.json: Read -node_modules/date-fns/locale/el/package.json: Read -node_modules/date-fns/locale/en-AU.mjs: Read -node_modules/date-fns/locale/en-AU/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/en-AU/_lib/package.json: Read -node_modules/date-fns/locale/en-AU/package.json: Read -node_modules/date-fns/locale/en-CA.mjs: Read -node_modules/date-fns/locale/en-CA/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/en-CA/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/en-CA/_lib/package.json: Read -node_modules/date-fns/locale/en-CA/package.json: Read -node_modules/date-fns/locale/en-GB.mjs: Read -node_modules/date-fns/locale/en-GB/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/en-GB/_lib/package.json: Read -node_modules/date-fns/locale/en-GB/package.json: Read -node_modules/date-fns/locale/en-IE.mjs: Read -node_modules/date-fns/locale/en-IN.mjs: Read -node_modules/date-fns/locale/en-IN/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/en-IN/_lib/package.json: Read -node_modules/date-fns/locale/en-IN/package.json: Read -node_modules/date-fns/locale/en-NZ.mjs: Read -node_modules/date-fns/locale/en-NZ/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/en-NZ/_lib/package.json: Read -node_modules/date-fns/locale/en-NZ/package.json: Read -node_modules/date-fns/locale/en-US.mjs: Read -node_modules/date-fns/locale/en-US/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/en-US/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/en-US/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/en-US/_lib/localize.mjs: Read -node_modules/date-fns/locale/en-US/_lib/match.mjs: Read -node_modules/date-fns/locale/en-US/_lib/package.json: Read -node_modules/date-fns/locale/en-US/package.json: Read -node_modules/date-fns/locale/en-ZA.mjs: Read -node_modules/date-fns/locale/en-ZA/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/en-ZA/_lib/package.json: Read -node_modules/date-fns/locale/en-ZA/package.json: Read -node_modules/date-fns/locale/eo.mjs: Read -node_modules/date-fns/locale/eo/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/eo/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/eo/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/eo/_lib/localize.mjs: Read -node_modules/date-fns/locale/eo/_lib/match.mjs: Read -node_modules/date-fns/locale/eo/_lib/package.json: Read -node_modules/date-fns/locale/eo/package.json: Read -node_modules/date-fns/locale/es.mjs: Read -node_modules/date-fns/locale/es/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/es/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/es/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/es/_lib/localize.mjs: Read -node_modules/date-fns/locale/es/_lib/match.mjs: Read -node_modules/date-fns/locale/es/_lib/package.json: Read -node_modules/date-fns/locale/es/package.json: Read -node_modules/date-fns/locale/et.mjs: Read -node_modules/date-fns/locale/et/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/et/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/et/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/et/_lib/localize.mjs: Read -node_modules/date-fns/locale/et/_lib/match.mjs: Read -node_modules/date-fns/locale/et/_lib/package.json: Read -node_modules/date-fns/locale/et/package.json: Read -node_modules/date-fns/locale/eu.mjs: Read -node_modules/date-fns/locale/eu/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/eu/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/eu/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/eu/_lib/localize.mjs: Read -node_modules/date-fns/locale/eu/_lib/match.mjs: Read -node_modules/date-fns/locale/eu/_lib/package.json: Read -node_modules/date-fns/locale/eu/package.json: Read -node_modules/date-fns/locale/fa-IR.mjs: Read -node_modules/date-fns/locale/fa-IR/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/fa-IR/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/fa-IR/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/fa-IR/_lib/localize.mjs: Read -node_modules/date-fns/locale/fa-IR/_lib/match.mjs: Read -node_modules/date-fns/locale/fa-IR/_lib/package.json: Read -node_modules/date-fns/locale/fa-IR/package.json: Read -node_modules/date-fns/locale/fi.mjs: Read -node_modules/date-fns/locale/fi/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/fi/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/fi/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/fi/_lib/localize.mjs: Read -node_modules/date-fns/locale/fi/_lib/match.mjs: Read -node_modules/date-fns/locale/fi/_lib/package.json: Read -node_modules/date-fns/locale/fi/package.json: Read -node_modules/date-fns/locale/fr-CA.mjs: Read -node_modules/date-fns/locale/fr-CA/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/fr-CA/_lib/package.json: Read -node_modules/date-fns/locale/fr-CA/package.json: Read -node_modules/date-fns/locale/fr-CH.mjs: Read -node_modules/date-fns/locale/fr-CH/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/fr-CH/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/fr-CH/_lib/package.json: Read -node_modules/date-fns/locale/fr-CH/package.json: Read -node_modules/date-fns/locale/fr.mjs: Read -node_modules/date-fns/locale/fr/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/fr/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/fr/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/fr/_lib/localize.mjs: Read -node_modules/date-fns/locale/fr/_lib/match.mjs: Read -node_modules/date-fns/locale/fr/_lib/package.json: Read -node_modules/date-fns/locale/fr/package.json: Read -node_modules/date-fns/locale/fy.mjs: Read -node_modules/date-fns/locale/fy/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/fy/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/fy/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/fy/_lib/localize.mjs: Read -node_modules/date-fns/locale/fy/_lib/match.mjs: Read -node_modules/date-fns/locale/fy/_lib/package.json: Read -node_modules/date-fns/locale/fy/package.json: Read -node_modules/date-fns/locale/gd.mjs: Read -node_modules/date-fns/locale/gd/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/gd/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/gd/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/gd/_lib/localize.mjs: Read -node_modules/date-fns/locale/gd/_lib/match.mjs: Read -node_modules/date-fns/locale/gd/_lib/package.json: Read -node_modules/date-fns/locale/gd/package.json: Read -node_modules/date-fns/locale/gl.mjs: Read -node_modules/date-fns/locale/gl/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/gl/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/gl/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/gl/_lib/localize.mjs: Read -node_modules/date-fns/locale/gl/_lib/match.mjs: Read -node_modules/date-fns/locale/gl/_lib/package.json: Read -node_modules/date-fns/locale/gl/package.json: Read -node_modules/date-fns/locale/gu.mjs: Read -node_modules/date-fns/locale/gu/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/gu/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/gu/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/gu/_lib/localize.mjs: Read -node_modules/date-fns/locale/gu/_lib/match.mjs: Read -node_modules/date-fns/locale/gu/_lib/package.json: Read -node_modules/date-fns/locale/gu/package.json: Read -node_modules/date-fns/locale/he.mjs: Read -node_modules/date-fns/locale/he/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/he/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/he/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/he/_lib/localize.mjs: Read -node_modules/date-fns/locale/he/_lib/match.mjs: Read -node_modules/date-fns/locale/he/_lib/package.json: Read -node_modules/date-fns/locale/he/package.json: Read -node_modules/date-fns/locale/hi.mjs: Read -node_modules/date-fns/locale/hi/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/hi/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/hi/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/hi/_lib/localize.mjs: Read -node_modules/date-fns/locale/hi/_lib/match.mjs: Read -node_modules/date-fns/locale/hi/_lib/package.json: Read -node_modules/date-fns/locale/hi/package.json: Read -node_modules/date-fns/locale/hr.mjs: Read -node_modules/date-fns/locale/hr/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/hr/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/hr/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/hr/_lib/localize.mjs: Read -node_modules/date-fns/locale/hr/_lib/match.mjs: Read -node_modules/date-fns/locale/hr/_lib/package.json: Read -node_modules/date-fns/locale/hr/package.json: Read -node_modules/date-fns/locale/ht.mjs: Read -node_modules/date-fns/locale/ht/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ht/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ht/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ht/_lib/localize.mjs: Read -node_modules/date-fns/locale/ht/_lib/match.mjs: Read -node_modules/date-fns/locale/ht/_lib/package.json: Read -node_modules/date-fns/locale/ht/package.json: Read -node_modules/date-fns/locale/hu.mjs: Read -node_modules/date-fns/locale/hu/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/hu/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/hu/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/hu/_lib/localize.mjs: Read -node_modules/date-fns/locale/hu/_lib/match.mjs: Read -node_modules/date-fns/locale/hu/_lib/package.json: Read -node_modules/date-fns/locale/hu/package.json: Read -node_modules/date-fns/locale/hy.mjs: Read -node_modules/date-fns/locale/hy/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/hy/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/hy/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/hy/_lib/localize.mjs: Read -node_modules/date-fns/locale/hy/_lib/match.mjs: Read -node_modules/date-fns/locale/hy/_lib/package.json: Read -node_modules/date-fns/locale/hy/package.json: Read -node_modules/date-fns/locale/id.mjs: Read -node_modules/date-fns/locale/id/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/id/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/id/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/id/_lib/localize.mjs: Read -node_modules/date-fns/locale/id/_lib/match.mjs: Read -node_modules/date-fns/locale/id/_lib/package.json: Read -node_modules/date-fns/locale/id/package.json: Read -node_modules/date-fns/locale/is.mjs: Read -node_modules/date-fns/locale/is/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/is/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/is/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/is/_lib/localize.mjs: Read -node_modules/date-fns/locale/is/_lib/match.mjs: Read -node_modules/date-fns/locale/is/_lib/package.json: Read -node_modules/date-fns/locale/is/package.json: Read -node_modules/date-fns/locale/it-CH.mjs: Read -node_modules/date-fns/locale/it-CH/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/it-CH/_lib/package.json: Read -node_modules/date-fns/locale/it-CH/package.json: Read -node_modules/date-fns/locale/it.mjs: Read -node_modules/date-fns/locale/it/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/it/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/it/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/it/_lib/localize.mjs: Read -node_modules/date-fns/locale/it/_lib/match.mjs: Read -node_modules/date-fns/locale/it/_lib/package.json: Read -node_modules/date-fns/locale/it/package.json: Read -node_modules/date-fns/locale/ja-Hira.mjs: Read -node_modules/date-fns/locale/ja-Hira/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ja-Hira/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ja-Hira/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ja-Hira/_lib/localize.mjs: Read -node_modules/date-fns/locale/ja-Hira/_lib/match.mjs: Read -node_modules/date-fns/locale/ja-Hira/_lib/package.json: Read -node_modules/date-fns/locale/ja-Hira/package.json: Read -node_modules/date-fns/locale/ja.mjs: Read -node_modules/date-fns/locale/ja/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ja/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ja/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ja/_lib/localize.mjs: Read -node_modules/date-fns/locale/ja/_lib/match.mjs: Read -node_modules/date-fns/locale/ja/_lib/package.json: Read -node_modules/date-fns/locale/ja/package.json: Read -node_modules/date-fns/locale/ka.mjs: Read -node_modules/date-fns/locale/ka/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ka/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ka/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ka/_lib/localize.mjs: Read -node_modules/date-fns/locale/ka/_lib/match.mjs: Read -node_modules/date-fns/locale/ka/_lib/package.json: Read -node_modules/date-fns/locale/ka/package.json: Read -node_modules/date-fns/locale/kk.mjs: Read -node_modules/date-fns/locale/kk/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/kk/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/kk/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/kk/_lib/localize.mjs: Read -node_modules/date-fns/locale/kk/_lib/match.mjs: Read -node_modules/date-fns/locale/kk/_lib/package.json: Read -node_modules/date-fns/locale/kk/package.json: Read -node_modules/date-fns/locale/km.mjs: Read -node_modules/date-fns/locale/km/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/km/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/km/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/km/_lib/localize.mjs: Read -node_modules/date-fns/locale/km/_lib/match.mjs: Read -node_modules/date-fns/locale/km/_lib/package.json: Read -node_modules/date-fns/locale/km/package.json: Read -node_modules/date-fns/locale/kn.mjs: Read -node_modules/date-fns/locale/kn/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/kn/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/kn/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/kn/_lib/localize.mjs: Read -node_modules/date-fns/locale/kn/_lib/match.mjs: Read -node_modules/date-fns/locale/kn/_lib/package.json: Read -node_modules/date-fns/locale/kn/package.json: Read -node_modules/date-fns/locale/ko.mjs: Read -node_modules/date-fns/locale/ko/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ko/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ko/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ko/_lib/localize.mjs: Read -node_modules/date-fns/locale/ko/_lib/match.mjs: Read -node_modules/date-fns/locale/ko/_lib/package.json: Read -node_modules/date-fns/locale/ko/package.json: Read -node_modules/date-fns/locale/lb.mjs: Read -node_modules/date-fns/locale/lb/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/lb/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/lb/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/lb/_lib/localize.mjs: Read -node_modules/date-fns/locale/lb/_lib/match.mjs: Read -node_modules/date-fns/locale/lb/_lib/package.json: Read -node_modules/date-fns/locale/lb/package.json: Read -node_modules/date-fns/locale/lt.mjs: Read -node_modules/date-fns/locale/lt/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/lt/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/lt/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/lt/_lib/localize.mjs: Read -node_modules/date-fns/locale/lt/_lib/match.mjs: Read -node_modules/date-fns/locale/lt/_lib/package.json: Read -node_modules/date-fns/locale/lt/package.json: Read -node_modules/date-fns/locale/lv.mjs: Read -node_modules/date-fns/locale/lv/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/lv/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/lv/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/lv/_lib/localize.mjs: Read -node_modules/date-fns/locale/lv/_lib/match.mjs: Read -node_modules/date-fns/locale/lv/_lib/package.json: Read -node_modules/date-fns/locale/lv/package.json: Read -node_modules/date-fns/locale/mk.mjs: Read -node_modules/date-fns/locale/mk/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/mk/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/mk/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/mk/_lib/localize.mjs: Read -node_modules/date-fns/locale/mk/_lib/match.mjs: Read -node_modules/date-fns/locale/mk/_lib/package.json: Read -node_modules/date-fns/locale/mk/package.json: Read -node_modules/date-fns/locale/mn.mjs: Read -node_modules/date-fns/locale/mn/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/mn/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/mn/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/mn/_lib/localize.mjs: Read -node_modules/date-fns/locale/mn/_lib/match.mjs: Read -node_modules/date-fns/locale/mn/_lib/package.json: Read -node_modules/date-fns/locale/mn/package.json: Read -node_modules/date-fns/locale/ms.mjs: Read -node_modules/date-fns/locale/ms/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ms/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ms/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ms/_lib/localize.mjs: Read -node_modules/date-fns/locale/ms/_lib/match.mjs: Read -node_modules/date-fns/locale/ms/_lib/package.json: Read -node_modules/date-fns/locale/ms/package.json: Read -node_modules/date-fns/locale/mt.mjs: Read -node_modules/date-fns/locale/mt/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/mt/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/mt/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/mt/_lib/localize.mjs: Read -node_modules/date-fns/locale/mt/_lib/match.mjs: Read -node_modules/date-fns/locale/mt/_lib/package.json: Read -node_modules/date-fns/locale/mt/package.json: Read -node_modules/date-fns/locale/nb.mjs: Read -node_modules/date-fns/locale/nb/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/nb/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/nb/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/nb/_lib/localize.mjs: Read -node_modules/date-fns/locale/nb/_lib/match.mjs: Read -node_modules/date-fns/locale/nb/_lib/package.json: Read -node_modules/date-fns/locale/nb/package.json: Read -node_modules/date-fns/locale/nl-BE.mjs: Read -node_modules/date-fns/locale/nl-BE/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/nl-BE/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/nl-BE/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/nl-BE/_lib/localize.mjs: Read -node_modules/date-fns/locale/nl-BE/_lib/match.mjs: Read -node_modules/date-fns/locale/nl-BE/_lib/package.json: Read -node_modules/date-fns/locale/nl-BE/package.json: Read -node_modules/date-fns/locale/nl.mjs: Read -node_modules/date-fns/locale/nl/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/nl/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/nl/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/nl/_lib/localize.mjs: Read -node_modules/date-fns/locale/nl/_lib/match.mjs: Read -node_modules/date-fns/locale/nl/_lib/package.json: Read -node_modules/date-fns/locale/nl/package.json: Read -node_modules/date-fns/locale/nn.mjs: Read -node_modules/date-fns/locale/nn/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/nn/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/nn/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/nn/_lib/localize.mjs: Read -node_modules/date-fns/locale/nn/_lib/match.mjs: Read -node_modules/date-fns/locale/nn/_lib/package.json: Read -node_modules/date-fns/locale/nn/package.json: Read -node_modules/date-fns/locale/oc.mjs: Read -node_modules/date-fns/locale/oc/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/oc/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/oc/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/oc/_lib/localize.mjs: Read -node_modules/date-fns/locale/oc/_lib/match.mjs: Read -node_modules/date-fns/locale/oc/_lib/package.json: Read -node_modules/date-fns/locale/oc/package.json: Read -node_modules/date-fns/locale/package.json: Read -node_modules/date-fns/locale/pl.mjs: Read -node_modules/date-fns/locale/pl/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/pl/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/pl/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/pl/_lib/localize.mjs: Read -node_modules/date-fns/locale/pl/_lib/match.mjs: Read -node_modules/date-fns/locale/pl/_lib/package.json: Read -node_modules/date-fns/locale/pl/package.json: Read -node_modules/date-fns/locale/pt-BR.mjs: Read -node_modules/date-fns/locale/pt-BR/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/pt-BR/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/pt-BR/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/pt-BR/_lib/localize.mjs: Read -node_modules/date-fns/locale/pt-BR/_lib/match.mjs: Read -node_modules/date-fns/locale/pt-BR/_lib/package.json: Read -node_modules/date-fns/locale/pt-BR/package.json: Read -node_modules/date-fns/locale/pt.mjs: Read -node_modules/date-fns/locale/pt/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/pt/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/pt/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/pt/_lib/localize.mjs: Read -node_modules/date-fns/locale/pt/_lib/match.mjs: Read -node_modules/date-fns/locale/pt/_lib/package.json: Read -node_modules/date-fns/locale/pt/package.json: Read -node_modules/date-fns/locale/ro.mjs: Read -node_modules/date-fns/locale/ro/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ro/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ro/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ro/_lib/localize.mjs: Read -node_modules/date-fns/locale/ro/_lib/match.mjs: Read -node_modules/date-fns/locale/ro/_lib/package.json: Read -node_modules/date-fns/locale/ro/package.json: Read -node_modules/date-fns/locale/ru.mjs: Read -node_modules/date-fns/locale/ru/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ru/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ru/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ru/_lib/localize.mjs: Read -node_modules/date-fns/locale/ru/_lib/match.mjs: Read -node_modules/date-fns/locale/ru/_lib/package.json: Read -node_modules/date-fns/locale/ru/package.json: Read -node_modules/date-fns/locale/se.mjs: Read -node_modules/date-fns/locale/se/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/se/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/se/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/se/_lib/localize.mjs: Read -node_modules/date-fns/locale/se/_lib/match.mjs: Read -node_modules/date-fns/locale/se/_lib/package.json: Read -node_modules/date-fns/locale/se/package.json: Read -node_modules/date-fns/locale/sk.mjs: Read -node_modules/date-fns/locale/sk/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/sk/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/sk/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/sk/_lib/localize.mjs: Read -node_modules/date-fns/locale/sk/_lib/match.mjs: Read -node_modules/date-fns/locale/sk/_lib/package.json: Read -node_modules/date-fns/locale/sk/package.json: Read -node_modules/date-fns/locale/sl.mjs: Read -node_modules/date-fns/locale/sl/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/sl/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/sl/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/sl/_lib/localize.mjs: Read -node_modules/date-fns/locale/sl/_lib/match.mjs: Read -node_modules/date-fns/locale/sl/_lib/package.json: Read -node_modules/date-fns/locale/sl/package.json: Read -node_modules/date-fns/locale/sq.mjs: Read -node_modules/date-fns/locale/sq/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/sq/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/sq/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/sq/_lib/localize.mjs: Read -node_modules/date-fns/locale/sq/_lib/match.mjs: Read -node_modules/date-fns/locale/sq/_lib/package.json: Read -node_modules/date-fns/locale/sq/package.json: Read -node_modules/date-fns/locale/sr-Latn.mjs: Read -node_modules/date-fns/locale/sr-Latn/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/sr-Latn/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/sr-Latn/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/sr-Latn/_lib/localize.mjs: Read -node_modules/date-fns/locale/sr-Latn/_lib/match.mjs: Read -node_modules/date-fns/locale/sr-Latn/_lib/package.json: Read -node_modules/date-fns/locale/sr-Latn/package.json: Read -node_modules/date-fns/locale/sr.mjs: Read -node_modules/date-fns/locale/sr/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/sr/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/sr/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/sr/_lib/localize.mjs: Read -node_modules/date-fns/locale/sr/_lib/match.mjs: Read -node_modules/date-fns/locale/sr/_lib/package.json: Read -node_modules/date-fns/locale/sr/package.json: Read -node_modules/date-fns/locale/sv.mjs: Read -node_modules/date-fns/locale/sv/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/sv/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/sv/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/sv/_lib/localize.mjs: Read -node_modules/date-fns/locale/sv/_lib/match.mjs: Read -node_modules/date-fns/locale/sv/_lib/package.json: Read -node_modules/date-fns/locale/sv/package.json: Read -node_modules/date-fns/locale/ta.mjs: Read -node_modules/date-fns/locale/ta/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ta/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ta/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ta/_lib/localize.mjs: Read -node_modules/date-fns/locale/ta/_lib/match.mjs: Read -node_modules/date-fns/locale/ta/_lib/package.json: Read -node_modules/date-fns/locale/ta/package.json: Read -node_modules/date-fns/locale/te.mjs: Read -node_modules/date-fns/locale/te/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/te/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/te/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/te/_lib/localize.mjs: Read -node_modules/date-fns/locale/te/_lib/match.mjs: Read -node_modules/date-fns/locale/te/_lib/package.json: Read -node_modules/date-fns/locale/te/package.json: Read -node_modules/date-fns/locale/th.mjs: Read -node_modules/date-fns/locale/th/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/th/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/th/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/th/_lib/localize.mjs: Read -node_modules/date-fns/locale/th/_lib/match.mjs: Read -node_modules/date-fns/locale/th/_lib/package.json: Read -node_modules/date-fns/locale/th/package.json: Read -node_modules/date-fns/locale/tr.mjs: Read -node_modules/date-fns/locale/tr/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/tr/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/tr/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/tr/_lib/localize.mjs: Read -node_modules/date-fns/locale/tr/_lib/match.mjs: Read -node_modules/date-fns/locale/tr/_lib/package.json: Read -node_modules/date-fns/locale/tr/package.json: Read -node_modules/date-fns/locale/ug.mjs: Read -node_modules/date-fns/locale/ug/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/ug/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/ug/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/ug/_lib/localize.mjs: Read -node_modules/date-fns/locale/ug/_lib/match.mjs: Read -node_modules/date-fns/locale/ug/_lib/package.json: Read -node_modules/date-fns/locale/ug/package.json: Read -node_modules/date-fns/locale/uk.mjs: Read -node_modules/date-fns/locale/uk/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/uk/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/uk/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/uk/_lib/localize.mjs: Read -node_modules/date-fns/locale/uk/_lib/match.mjs: Read -node_modules/date-fns/locale/uk/_lib/package.json: Read -node_modules/date-fns/locale/uk/package.json: Read -node_modules/date-fns/locale/uz-Cyrl.mjs: Read -node_modules/date-fns/locale/uz-Cyrl/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/uz-Cyrl/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/uz-Cyrl/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/uz-Cyrl/_lib/localize.mjs: Read -node_modules/date-fns/locale/uz-Cyrl/_lib/match.mjs: Read -node_modules/date-fns/locale/uz-Cyrl/_lib/package.json: Read -node_modules/date-fns/locale/uz-Cyrl/package.json: Read -node_modules/date-fns/locale/uz.mjs: Read -node_modules/date-fns/locale/uz/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/uz/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/uz/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/uz/_lib/localize.mjs: Read -node_modules/date-fns/locale/uz/_lib/match.mjs: Read -node_modules/date-fns/locale/uz/_lib/package.json: Read -node_modules/date-fns/locale/uz/package.json: Read -node_modules/date-fns/locale/vi.mjs: Read -node_modules/date-fns/locale/vi/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/vi/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/vi/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/vi/_lib/localize.mjs: Read -node_modules/date-fns/locale/vi/_lib/match.mjs: Read -node_modules/date-fns/locale/vi/_lib/package.json: Read -node_modules/date-fns/locale/vi/package.json: Read -node_modules/date-fns/locale/zh-CN.mjs: Read -node_modules/date-fns/locale/zh-CN/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/zh-CN/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/zh-CN/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/zh-CN/_lib/localize.mjs: Read -node_modules/date-fns/locale/zh-CN/_lib/match.mjs: Read -node_modules/date-fns/locale/zh-CN/_lib/package.json: Read -node_modules/date-fns/locale/zh-CN/package.json: Read -node_modules/date-fns/locale/zh-HK.mjs: Read -node_modules/date-fns/locale/zh-HK/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/zh-HK/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/zh-HK/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/zh-HK/_lib/localize.mjs: Read -node_modules/date-fns/locale/zh-HK/_lib/match.mjs: Read -node_modules/date-fns/locale/zh-HK/_lib/package.json: Read -node_modules/date-fns/locale/zh-HK/package.json: Read -node_modules/date-fns/locale/zh-TW.mjs: Read -node_modules/date-fns/locale/zh-TW/_lib/formatDistance.mjs: Read -node_modules/date-fns/locale/zh-TW/_lib/formatLong.mjs: Read -node_modules/date-fns/locale/zh-TW/_lib/formatRelative.mjs: Read -node_modules/date-fns/locale/zh-TW/_lib/localize.mjs: Read -node_modules/date-fns/locale/zh-TW/_lib/match.mjs: Read -node_modules/date-fns/locale/zh-TW/_lib/package.json: Read -node_modules/date-fns/locale/zh-TW/package.json: Read -node_modules/date-fns/max.mjs: Read -node_modules/date-fns/milliseconds.mjs: Read -node_modules/date-fns/millisecondsToHours.mjs: Read -node_modules/date-fns/millisecondsToMinutes.mjs: Read -node_modules/date-fns/millisecondsToSeconds.mjs: Read -node_modules/date-fns/min.mjs: Read -node_modules/date-fns/minutesToHours.mjs: Read -node_modules/date-fns/minutesToMilliseconds.mjs: Read -node_modules/date-fns/minutesToSeconds.mjs: Read -node_modules/date-fns/monthsToQuarters.mjs: Read -node_modules/date-fns/monthsToYears.mjs: Read -node_modules/date-fns/nextDay.mjs: Read -node_modules/date-fns/nextFriday.mjs: Read -node_modules/date-fns/nextMonday.mjs: Read -node_modules/date-fns/nextSaturday.mjs: Read -node_modules/date-fns/nextSunday.mjs: Read -node_modules/date-fns/nextThursday.mjs: Read -node_modules/date-fns/nextTuesday.mjs: Read -node_modules/date-fns/nextWednesday.mjs: Read -node_modules/date-fns/package.json: Read -node_modules/date-fns/parse.mjs: Read -node_modules/date-fns/parse/_lib/Parser.mjs: Read -node_modules/date-fns/parse/_lib/Setter.mjs: Read -node_modules/date-fns/parse/_lib/constants.mjs: Read -node_modules/date-fns/parse/_lib/package.json: Read -node_modules/date-fns/parse/_lib/parsers.mjs: Read -node_modules/date-fns/parse/_lib/parsers/AMPMMidnightParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/AMPMParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/DateParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/DayOfYearParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/DayParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/DayPeriodParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/EraParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/ExtendedYearParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/FractionOfSecondParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/Hour0To11Parser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/Hour0to23Parser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/Hour1To24Parser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/Hour1to12Parser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/ISODayParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/ISOTimezoneParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/ISOTimezoneWithZParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/ISOWeekParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/ISOWeekYearParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/LocalDayParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/LocalWeekParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/LocalWeekYearParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/MinuteParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/MonthParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/QuarterParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/SecondParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/StandAloneLocalDayParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/StandAloneMonthParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/StandAloneQuarterParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/TimestampMillisecondsParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/TimestampSecondsParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/YearParser.mjs: Read -node_modules/date-fns/parse/_lib/parsers/package.json: Read -node_modules/date-fns/parse/_lib/utils.mjs: Read -node_modules/date-fns/parse/package.json: Read -node_modules/date-fns/parseISO.mjs: Read -node_modules/date-fns/parseJSON.mjs: Read -node_modules/date-fns/previousDay.mjs: Read -node_modules/date-fns/previousFriday.mjs: Read -node_modules/date-fns/previousMonday.mjs: Read -node_modules/date-fns/previousSaturday.mjs: Read -node_modules/date-fns/previousSunday.mjs: Read -node_modules/date-fns/previousThursday.mjs: Read -node_modules/date-fns/previousTuesday.mjs: Read -node_modules/date-fns/previousWednesday.mjs: Read -node_modules/date-fns/quartersToMonths.mjs: Read -node_modules/date-fns/quartersToYears.mjs: Read -node_modules/date-fns/roundToNearestHours.mjs: Read -node_modules/date-fns/roundToNearestMinutes.mjs: Read -node_modules/date-fns/secondsToHours.mjs: Read -node_modules/date-fns/secondsToMilliseconds.mjs: Read -node_modules/date-fns/secondsToMinutes.mjs: Read -node_modules/date-fns/set.mjs: Read -node_modules/date-fns/setDate.mjs: Read -node_modules/date-fns/setDay.mjs: Read -node_modules/date-fns/setDayOfYear.mjs: Read -node_modules/date-fns/setDefaultOptions.mjs: Read -node_modules/date-fns/setHours.mjs: Read -node_modules/date-fns/setISODay.mjs: Read -node_modules/date-fns/setISOWeek.mjs: Read -node_modules/date-fns/setISOWeekYear.mjs: Read -node_modules/date-fns/setMilliseconds.mjs: Read -node_modules/date-fns/setMinutes.mjs: Read -node_modules/date-fns/setMonth.mjs: Read -node_modules/date-fns/setQuarter.mjs: Read -node_modules/date-fns/setSeconds.mjs: Read -node_modules/date-fns/setWeek.mjs: Read -node_modules/date-fns/setWeekYear.mjs: Read -node_modules/date-fns/setYear.mjs: Read -node_modules/date-fns/startOfDay.mjs: Read -node_modules/date-fns/startOfDecade.mjs: Read -node_modules/date-fns/startOfHour.mjs: Read -node_modules/date-fns/startOfISOWeek.mjs: Read -node_modules/date-fns/startOfISOWeekYear.mjs: Read -node_modules/date-fns/startOfMinute.mjs: Read -node_modules/date-fns/startOfMonth.mjs: Read -node_modules/date-fns/startOfQuarter.mjs: Read -node_modules/date-fns/startOfSecond.mjs: Read -node_modules/date-fns/startOfToday.mjs: Read -node_modules/date-fns/startOfTomorrow.mjs: Read -node_modules/date-fns/startOfWeek.mjs: Read -node_modules/date-fns/startOfWeekYear.mjs: Read -node_modules/date-fns/startOfYear.mjs: Read -node_modules/date-fns/startOfYesterday.mjs: Read -node_modules/date-fns/sub.mjs: Read -node_modules/date-fns/subBusinessDays.mjs: Read -node_modules/date-fns/subDays.mjs: Read -node_modules/date-fns/subHours.mjs: Read -node_modules/date-fns/subISOWeekYears.mjs: Read -node_modules/date-fns/subMilliseconds.mjs: Read -node_modules/date-fns/subMinutes.mjs: Read -node_modules/date-fns/subMonths.mjs: Read -node_modules/date-fns/subQuarters.mjs: Read -node_modules/date-fns/subSeconds.mjs: Read -node_modules/date-fns/subWeeks.mjs: Read -node_modules/date-fns/subYears.mjs: Read -node_modules/date-fns/toDate.mjs: Read -node_modules/date-fns/transpose.mjs: Read -node_modules/date-fns/weeksToDays.mjs: Read -node_modules/date-fns/yearsToDays.mjs: Read -node_modules/date-fns/yearsToMonths.mjs: Read -node_modules/date-fns/yearsToQuarters.mjs: Read -node_modules/dayjs/dayjs.min.js: Read -node_modules/dayjs/package.json: Read -node_modules/dayjs/plugin/advancedFormat.js: Read -node_modules/dayjs/plugin/customParseFormat.js: Read -node_modules/dayjs/plugin/isoWeek.js: Read -node_modules/dayjs/plugin/package.json: Read -node_modules/debug/package.json: Read -node_modules/debug/src/browser.js: Read -node_modules/debug/src/common.js: Read -node_modules/debug/src/index.js: Read -node_modules/debug/src/node.js: Read -node_modules/debug/src/package.json: Read -node_modules/decode-uri-component/index.js: Read -node_modules/decode-uri-component/package.json: Read -node_modules/deepmerge/dist/cjs.js: Read -node_modules/deepmerge/dist/package.json: Read -node_modules/deepmerge/package.json: Read -node_modules/define-data-property/index.js: Read -node_modules/define-data-property/package.json: Read -node_modules/define-properties/index.js: Read -node_modules/define-properties/package.json: Read -node_modules/delaunator/index.js: Read -node_modules/delaunator/package.json: Read -node_modules/detect-libc/lib/detect-libc.js: Read -node_modules/detect-libc/lib/filesystem.js: Read -node_modules/detect-libc/lib/package.json: Read -node_modules/detect-libc/lib/process.js: Read -node_modules/detect-libc/package.json: Read -node_modules/detect-node-es/esm/browser.js: Read -node_modules/detect-node-es/esm/package.json: Read -node_modules/detect-node-es/package.json: Read -node_modules/diff/lib/index.mjs: Read -node_modules/diff/lib/package.json: Read -node_modules/diff/package.json: Read -node_modules/dnd-core/dist/actions/dragDrop/beginDrag.js: Read -node_modules/dnd-core/dist/actions/dragDrop/drop.js: Read -node_modules/dnd-core/dist/actions/dragDrop/endDrag.js: Read -node_modules/dnd-core/dist/actions/dragDrop/hover.js: Read -node_modules/dnd-core/dist/actions/dragDrop/index.js: Read -node_modules/dnd-core/dist/actions/dragDrop/local/package.json: Read -node_modules/dnd-core/dist/actions/dragDrop/local/setClientOffset.js: Read -node_modules/dnd-core/dist/actions/dragDrop/package.json: Read -node_modules/dnd-core/dist/actions/dragDrop/publishDragSource.js: Read -node_modules/dnd-core/dist/actions/dragDrop/types.js: Read -node_modules/dnd-core/dist/actions/package.json: Read -node_modules/dnd-core/dist/actions/registry.js: Read -node_modules/dnd-core/dist/classes/DragDropManagerImpl.js: Read -node_modules/dnd-core/dist/classes/DragDropMonitorImpl.js: Read -node_modules/dnd-core/dist/classes/HandlerRegistryImpl.js: Read -node_modules/dnd-core/dist/classes/package.json: Read -node_modules/dnd-core/dist/contracts.js: Read -node_modules/dnd-core/dist/createDragDropManager.js: Read -node_modules/dnd-core/dist/index.js: Read -node_modules/dnd-core/dist/interfaces.js: Read -node_modules/dnd-core/dist/package.json: Read -node_modules/dnd-core/dist/reducers/dirtyHandlerIds.js: Read -node_modules/dnd-core/dist/reducers/dragOffset.js: Read -node_modules/dnd-core/dist/reducers/dragOperation.js: Read -node_modules/dnd-core/dist/reducers/index.js: Read -node_modules/dnd-core/dist/reducers/package.json: Read -node_modules/dnd-core/dist/reducers/refCount.js: Read -node_modules/dnd-core/dist/reducers/stateId.js: Read -node_modules/dnd-core/dist/utils/coords.js: Read -node_modules/dnd-core/dist/utils/dirtiness.js: Read -node_modules/dnd-core/dist/utils/equality.js: Read -node_modules/dnd-core/dist/utils/getNextUniqueId.js: Read -node_modules/dnd-core/dist/utils/js_utils.js: Read -node_modules/dnd-core/dist/utils/matchesType.js: Read -node_modules/dnd-core/dist/utils/package.json: Read -node_modules/dnd-core/package.json: Read -node_modules/dompurify/dist/package.json: Read -node_modules/dompurify/dist/purify.es.mjs: Read -node_modules/dompurify/package.json: Read -node_modules/dotenv/lib/main.js: Read -node_modules/dotenv/lib/package.json: Read -node_modules/dotenv/package.json: Read -node_modules/dunder-proto/get.js: Read -node_modules/dunder-proto/package.json: Read -node_modules/ejs/lib/ejs.js: Read -node_modules/ejs/lib/package.json: Read -node_modules/ejs/lib/utils.js: Read -node_modules/ejs/package.json: Read -node_modules/electron-to-chromium/package.json: Read -node_modules/electron-to-chromium/versions.js: Read -node_modules/emoji-mart/dist/module.js: Read -node_modules/emoji-mart/dist/package.json: Read -node_modules/emoji-mart/package.json: Read -node_modules/emoji-regex/index.mjs: Read -node_modules/emoji-regex/package.json: Read -node_modules/engine.io-client/build/esm/contrib/has-cors.js: Read -node_modules/engine.io-client/build/esm/contrib/package.json: Read -node_modules/engine.io-client/build/esm/contrib/parseqs.js: Read -node_modules/engine.io-client/build/esm/contrib/parseuri.js: Read -node_modules/engine.io-client/build/esm/globals.js: Read -node_modules/engine.io-client/build/esm/index.js: Read -node_modules/engine.io-client/build/esm/package.json: Read -node_modules/engine.io-client/build/esm/socket.js: Read -node_modules/engine.io-client/build/esm/transport.js: Read -node_modules/engine.io-client/build/esm/transports/index.js: Read -node_modules/engine.io-client/build/esm/transports/package.json: Read -node_modules/engine.io-client/build/esm/transports/polling-fetch.js: Read -node_modules/engine.io-client/build/esm/transports/polling-xhr.js: Read -node_modules/engine.io-client/build/esm/transports/polling.js: Read -node_modules/engine.io-client/build/esm/transports/websocket.js: Read -node_modules/engine.io-client/build/esm/transports/webtransport.js: Read -node_modules/engine.io-client/build/esm/util.js: Read -node_modules/engine.io-client/build/package.json: Read -node_modules/engine.io-client/node_modules/engine.io-parser/package.json: Read -node_modules/engine.io-client/node_modules/package.json: Read -node_modules/engine.io-client/package.json: Read -node_modules/engine.io-parser/build/esm/commons.js: Read -node_modules/engine.io-parser/build/esm/contrib/base64-arraybuffer.js: Read -node_modules/engine.io-parser/build/esm/contrib/package.json: Read -node_modules/engine.io-parser/build/esm/decodePacket.browser.js: Read -node_modules/engine.io-parser/build/esm/encodePacket.browser.js: Read -node_modules/engine.io-parser/build/esm/index.js: Read -node_modules/engine.io-parser/build/esm/package.json: Read -node_modules/engine.io-parser/build/package.json: Read -node_modules/engine.io-parser/package.json: Read -node_modules/entities/lib/esm/decode.js: Read -node_modules/entities/lib/esm/decode_codepoint.js: Read -node_modules/entities/lib/esm/encode.js: Read -node_modules/entities/lib/esm/escape.js: Read -node_modules/entities/lib/esm/generated/decode-data-html.js: Read -node_modules/entities/lib/esm/generated/decode-data-xml.js: Read -node_modules/entities/lib/esm/generated/encode-html.js: Read -node_modules/entities/lib/esm/generated/package.json: Read -node_modules/entities/lib/esm/index.js: Read -node_modules/entities/lib/esm/package.json: Read -node_modules/entities/lib/package.json: Read -node_modules/entities/package.json: Read -node_modules/es-abstract/2024/AdvanceStringIndex.js: Read -node_modules/es-abstract/2024/Call.js: Read -node_modules/es-abstract/2024/CodePointAt.js: Read -node_modules/es-abstract/2024/CreateIterResultObject.js: Read -node_modules/es-abstract/2024/CreateRegExpStringIterator.js: Read -node_modules/es-abstract/2024/DefineMethodProperty.js: Read -node_modules/es-abstract/2024/DefinePropertyOrThrow.js: Read -node_modules/es-abstract/2024/FromPropertyDescriptor.js: Read -node_modules/es-abstract/2024/Get.js: Read -node_modules/es-abstract/2024/GetMethod.js: Read -node_modules/es-abstract/2024/GetV.js: Read -node_modules/es-abstract/2024/IsArray.js: Read -node_modules/es-abstract/2024/IsCallable.js: Read -node_modules/es-abstract/2024/IsConstructor.js: Read -node_modules/es-abstract/2024/IsDataDescriptor.js: Read -node_modules/es-abstract/2024/IsExtensible.js: Read -node_modules/es-abstract/2024/IsRegExp.js: Read -node_modules/es-abstract/2024/OrdinaryObjectCreate.js: Read -node_modules/es-abstract/2024/RegExpExec.js: Read -node_modules/es-abstract/2024/SameValue.js: Read -node_modules/es-abstract/2024/Set.js: Read -node_modules/es-abstract/2024/SpeciesConstructor.js: Read -node_modules/es-abstract/2024/StringToNumber.js: Read -node_modules/es-abstract/2024/ToBoolean.js: Read -node_modules/es-abstract/2024/ToIntegerOrInfinity.js: Read -node_modules/es-abstract/2024/ToLength.js: Read -node_modules/es-abstract/2024/ToNumber.js: Read -node_modules/es-abstract/2024/ToPrimitive.js: Read -node_modules/es-abstract/2024/ToPropertyDescriptor.js: Read -node_modules/es-abstract/2024/ToString.js: Read -node_modules/es-abstract/2024/Type.js: Read -node_modules/es-abstract/2024/UTF16SurrogatePairToCodePoint.js: Read -node_modules/es-abstract/2024/floor.js: Read -node_modules/es-abstract/2024/package.json: Read -node_modules/es-abstract/2024/truncate.js: Read -node_modules/es-abstract/5/Type.js: Read -node_modules/es-abstract/5/package.json: Read -node_modules/es-abstract/GetIntrinsic.js: Read -node_modules/es-abstract/helpers/DefineOwnProperty.js: Read -node_modules/es-abstract/helpers/IsArray.js: Read -node_modules/es-abstract/helpers/forEach.js: Read -node_modules/es-abstract/helpers/fromPropertyDescriptor.js: Read -node_modules/es-abstract/helpers/isLeadingSurrogate.js: Read -node_modules/es-abstract/helpers/isObject.js: Read -node_modules/es-abstract/helpers/isPrimitive.js: Read -node_modules/es-abstract/helpers/isPropertyKey.js: Read -node_modules/es-abstract/helpers/isTrailingSurrogate.js: Read -node_modules/es-abstract/helpers/package.json: Read -node_modules/es-abstract/helpers/records/package.json: Read -node_modules/es-abstract/helpers/records/property-descriptor.js: Read -node_modules/es-abstract/package.json: Read -node_modules/es-define-property/index.js: Read -node_modules/es-define-property/package.json: Read -node_modules/es-errors/eval.js: Read -node_modules/es-errors/index.js: Read -node_modules/es-errors/package.json: Read -node_modules/es-errors/range.js: Read -node_modules/es-errors/ref.js: Read -node_modules/es-errors/syntax.js: Read -node_modules/es-errors/type.js: Read -node_modules/es-errors/uri.js: Read -node_modules/es-object-atoms/RequireObjectCoercible.js: Read -node_modules/es-object-atoms/index.js: Read -node_modules/es-object-atoms/package.json: Read -node_modules/es-set-tostringtag/index.js: Read -node_modules/es-set-tostringtag/package.json: Read -node_modules/es-to-primitive/es2015.js: Read -node_modules/es-to-primitive/helpers/isPrimitive.js: Read -node_modules/es-to-primitive/helpers/package.json: Read -node_modules/es-to-primitive/package.json: Read -node_modules/es6-error/es6/index.js: Read -node_modules/es6-error/es6/package.json: Read -node_modules/es6-error/package.json: Read -node_modules/fast-deep-equal/index.js: Read -node_modules/fast-deep-equal/package.json: Read -node_modules/fast-equals/dist/fast-equals.js: Read -node_modules/fast-equals/dist/package.json: Read -node_modules/fast-equals/package.json: Read -node_modules/fast-glob/out/index.js: Read -node_modules/fast-glob/out/managers/package.json: Read -node_modules/fast-glob/out/managers/tasks.js: Read -node_modules/fast-glob/out/package.json: Read -node_modules/fast-glob/out/providers/async.js: Read -node_modules/fast-glob/out/providers/filters/deep.js: Read -node_modules/fast-glob/out/providers/filters/entry.js: Read -node_modules/fast-glob/out/providers/filters/error.js: Read -node_modules/fast-glob/out/providers/filters/package.json: Read -node_modules/fast-glob/out/providers/matchers/matcher.js: Read -node_modules/fast-glob/out/providers/matchers/package.json: Read -node_modules/fast-glob/out/providers/matchers/partial.js: Read -node_modules/fast-glob/out/providers/package.json: Read -node_modules/fast-glob/out/providers/provider.js: Read -node_modules/fast-glob/out/providers/stream.js: Read -node_modules/fast-glob/out/providers/sync.js: Read -node_modules/fast-glob/out/providers/transformers/entry.js: Read -node_modules/fast-glob/out/providers/transformers/package.json: Read -node_modules/fast-glob/out/readers/async.js: Read -node_modules/fast-glob/out/readers/package.json: Read -node_modules/fast-glob/out/readers/reader.js: Read -node_modules/fast-glob/out/readers/stream.js: Read -node_modules/fast-glob/out/readers/sync.js: Read -node_modules/fast-glob/out/settings.js: Read -node_modules/fast-glob/out/utils/array.js: Read -node_modules/fast-glob/out/utils/errno.js: Read -node_modules/fast-glob/out/utils/fs.js: Read -node_modules/fast-glob/out/utils/index.js: Read -node_modules/fast-glob/out/utils/package.json: Read -node_modules/fast-glob/out/utils/path.js: Read -node_modules/fast-glob/out/utils/pattern.js: Read -node_modules/fast-glob/out/utils/stream.js: Read -node_modules/fast-glob/out/utils/string.js: Read -node_modules/fast-glob/package.json: Read -node_modules/fast-json-stable-stringify/index.js: Read -node_modules/fast-json-stable-stringify/package.json: Read -node_modules/fastq/package.json: Read -node_modules/fastq/queue.js: Read -node_modules/fetch-retry/dist/fetch-retry.umd.js: Read -node_modules/fetch-retry/dist/package.json: Read -node_modules/fetch-retry/package.json: Read -node_modules/file-selector/dist/es5/file-selector.js: Read -node_modules/file-selector/dist/es5/file.js: Read -node_modules/file-selector/dist/es5/index.js: Read -node_modules/file-selector/dist/es5/package.json: Read -node_modules/file-selector/dist/package.json: Read -node_modules/file-selector/package.json: Read -node_modules/fill-range/index.js: Read -node_modules/fill-range/package.json: Read -node_modules/filter-obj/index.js: Read -node_modules/filter-obj/package.json: Read -node_modules/fractional-index/index.js: Read -node_modules/fractional-index/package.json: Read -node_modules/framer-motion/dist/es/animation/animate.js: Read -node_modules/framer-motion/dist/es/animation/animation-controls.js: Read -node_modules/framer-motion/dist/es/animation/package.json: Read -node_modules/framer-motion/dist/es/animation/use-animated-state.js: Read -node_modules/framer-motion/dist/es/animation/use-animation.js: Read -node_modules/framer-motion/dist/es/animation/utils/default-transitions.js: Read -node_modules/framer-motion/dist/es/animation/utils/easing.js: Read -node_modules/framer-motion/dist/es/animation/utils/is-animatable.js: Read -node_modules/framer-motion/dist/es/animation/utils/is-animation-controls.js: Read -node_modules/framer-motion/dist/es/animation/utils/is-keyframes-target.js: Read -node_modules/framer-motion/dist/es/animation/utils/package.json: Read -node_modules/framer-motion/dist/es/animation/utils/transitions.js: Read -node_modules/framer-motion/dist/es/components/AnimatePresence/PresenceChild.js: Read -node_modules/framer-motion/dist/es/components/AnimatePresence/index.js: Read -node_modules/framer-motion/dist/es/components/AnimatePresence/package.json: Read -node_modules/framer-motion/dist/es/components/AnimatePresence/use-presence.js: Read -node_modules/framer-motion/dist/es/components/AnimateSharedLayout/index.js: Read -node_modules/framer-motion/dist/es/components/AnimateSharedLayout/package.json: Read -node_modules/framer-motion/dist/es/components/AnimateSharedLayout/types.js: Read -node_modules/framer-motion/dist/es/components/AnimateSharedLayout/utils/batcher.js: Read -node_modules/framer-motion/dist/es/components/AnimateSharedLayout/utils/crossfader.js: Read -node_modules/framer-motion/dist/es/components/AnimateSharedLayout/utils/package.json: Read -node_modules/framer-motion/dist/es/components/AnimateSharedLayout/utils/rotate.js: Read -node_modules/framer-motion/dist/es/components/AnimateSharedLayout/utils/stack.js: Read -node_modules/framer-motion/dist/es/components/LazyMotion/index.js: Read -node_modules/framer-motion/dist/es/components/LazyMotion/package.json: Read -node_modules/framer-motion/dist/es/components/MotionConfig/index.js: Read -node_modules/framer-motion/dist/es/components/MotionConfig/package.json: Read -node_modules/framer-motion/dist/es/components/package.json: Read -node_modules/framer-motion/dist/es/context/LayoutGroupContext.js: Read -node_modules/framer-motion/dist/es/context/LazyContext.js: Read -node_modules/framer-motion/dist/es/context/MotionConfigContext.js: Read -node_modules/framer-motion/dist/es/context/MotionContext/create.js: Read -node_modules/framer-motion/dist/es/context/MotionContext/index.js: Read -node_modules/framer-motion/dist/es/context/MotionContext/package.json: Read -node_modules/framer-motion/dist/es/context/MotionContext/utils.js: Read -node_modules/framer-motion/dist/es/context/PresenceContext.js: Read -node_modules/framer-motion/dist/es/context/SharedLayoutContext.js: Read -node_modules/framer-motion/dist/es/context/package.json: Read -node_modules/framer-motion/dist/es/events/event-info.js: Read -node_modules/framer-motion/dist/es/events/package.json: Read -node_modules/framer-motion/dist/es/events/use-dom-event.js: Read -node_modules/framer-motion/dist/es/events/use-pointer-event.js: Read -node_modules/framer-motion/dist/es/events/utils.js: Read -node_modules/framer-motion/dist/es/gestures/PanSession.js: Read -node_modules/framer-motion/dist/es/gestures/drag/VisualElementDragControls.js: Read -node_modules/framer-motion/dist/es/gestures/drag/package.json: Read -node_modules/framer-motion/dist/es/gestures/drag/use-drag-controls.js: Read -node_modules/framer-motion/dist/es/gestures/drag/use-drag.js: Read -node_modules/framer-motion/dist/es/gestures/drag/utils/constraints.js: Read -node_modules/framer-motion/dist/es/gestures/drag/utils/lock.js: Read -node_modules/framer-motion/dist/es/gestures/drag/utils/package.json: Read -node_modules/framer-motion/dist/es/gestures/package.json: Read -node_modules/framer-motion/dist/es/gestures/use-focus-gesture.js: Read -node_modules/framer-motion/dist/es/gestures/use-hover-gesture.js: Read -node_modules/framer-motion/dist/es/gestures/use-pan-gesture.js: Read -node_modules/framer-motion/dist/es/gestures/use-tap-gesture.js: Read -node_modules/framer-motion/dist/es/gestures/utils/event-type.js: Read -node_modules/framer-motion/dist/es/gestures/utils/is-node-or-child.js: Read -node_modules/framer-motion/dist/es/gestures/utils/package.json: Read -node_modules/framer-motion/dist/es/index.js: Read -node_modules/framer-motion/dist/es/motion/features/animations.js: Read -node_modules/framer-motion/dist/es/motion/features/definitions.js: Read -node_modules/framer-motion/dist/es/motion/features/drag.js: Read -node_modules/framer-motion/dist/es/motion/features/gestures.js: Read -node_modules/framer-motion/dist/es/motion/features/layout/Animate.js: Read -node_modules/framer-motion/dist/es/motion/features/layout/Measure.js: Read -node_modules/framer-motion/dist/es/motion/features/layout/index.js: Read -node_modules/framer-motion/dist/es/motion/features/layout/package.json: Read -node_modules/framer-motion/dist/es/motion/features/layout/utils.js: Read -node_modules/framer-motion/dist/es/motion/features/package.json: Read -node_modules/framer-motion/dist/es/motion/features/use-features.js: Read -node_modules/framer-motion/dist/es/motion/index.js: Read -node_modules/framer-motion/dist/es/motion/package.json: Read -node_modules/framer-motion/dist/es/motion/utils/is-forced-motion-value.js: Read -node_modules/framer-motion/dist/es/motion/utils/make-renderless-component.js: Read -node_modules/framer-motion/dist/es/motion/utils/package.json: Read -node_modules/framer-motion/dist/es/motion/utils/use-motion-ref.js: Read -node_modules/framer-motion/dist/es/motion/utils/use-visual-element.js: Read -node_modules/framer-motion/dist/es/motion/utils/use-visual-state.js: Read -node_modules/framer-motion/dist/es/motion/utils/valid-prop.js: Read -node_modules/framer-motion/dist/es/package.json: Read -node_modules/framer-motion/dist/es/render/dom/create-visual-element.js: Read -node_modules/framer-motion/dist/es/render/dom/features-animation.js: Read -node_modules/framer-motion/dist/es/render/dom/features-max.js: Read -node_modules/framer-motion/dist/es/render/dom/motion-minimal.js: Read -node_modules/framer-motion/dist/es/render/dom/motion-proxy.js: Read -node_modules/framer-motion/dist/es/render/dom/motion.js: Read -node_modules/framer-motion/dist/es/render/dom/package.json: Read -node_modules/framer-motion/dist/es/render/dom/projection/convert-to-relative.js: Read -node_modules/framer-motion/dist/es/render/dom/projection/default-scale-correctors.js: Read -node_modules/framer-motion/dist/es/render/dom/projection/measure.js: Read -node_modules/framer-motion/dist/es/render/dom/projection/package.json: Read -node_modules/framer-motion/dist/es/render/dom/projection/relative-set.js: Read -node_modules/framer-motion/dist/es/render/dom/projection/scale-correction.js: Read -node_modules/framer-motion/dist/es/render/dom/projection/utils.js: Read -node_modules/framer-motion/dist/es/render/dom/use-render.js: Read -node_modules/framer-motion/dist/es/render/dom/utils/batch-layout.js: Read -node_modules/framer-motion/dist/es/render/dom/utils/camel-to-dash.js: Read -node_modules/framer-motion/dist/es/render/dom/utils/create-config.js: Read -node_modules/framer-motion/dist/es/render/dom/utils/css-variables-conversion.js: Read -node_modules/framer-motion/dist/es/render/dom/utils/filter-props.js: Read -node_modules/framer-motion/dist/es/render/dom/utils/is-css-variable.js: Read -node_modules/framer-motion/dist/es/render/dom/utils/is-svg-component.js: Read -node_modules/framer-motion/dist/es/render/dom/utils/package.json: Read -node_modules/framer-motion/dist/es/render/dom/utils/parse-dom-variant.js: Read -node_modules/framer-motion/dist/es/render/dom/utils/unit-conversion.js: Read -node_modules/framer-motion/dist/es/render/dom/value-types/animatable-none.js: Read -node_modules/framer-motion/dist/es/render/dom/value-types/defaults.js: Read -node_modules/framer-motion/dist/es/render/dom/value-types/dimensions.js: Read -node_modules/framer-motion/dist/es/render/dom/value-types/find.js: Read -node_modules/framer-motion/dist/es/render/dom/value-types/get-as-type.js: Read -node_modules/framer-motion/dist/es/render/dom/value-types/number.js: Read -node_modules/framer-motion/dist/es/render/dom/value-types/package.json: Read -node_modules/framer-motion/dist/es/render/dom/value-types/test.js: Read -node_modules/framer-motion/dist/es/render/dom/value-types/type-auto.js: Read -node_modules/framer-motion/dist/es/render/dom/value-types/type-int.js: Read -node_modules/framer-motion/dist/es/render/html/config-motion.js: Read -node_modules/framer-motion/dist/es/render/html/package.json: Read -node_modules/framer-motion/dist/es/render/html/use-props.js: Read -node_modules/framer-motion/dist/es/render/html/utils/build-projection-transform.js: Read -node_modules/framer-motion/dist/es/render/html/utils/build-styles.js: Read -node_modules/framer-motion/dist/es/render/html/utils/build-transform.js: Read -node_modules/framer-motion/dist/es/render/html/utils/create-render-state.js: Read -node_modules/framer-motion/dist/es/render/html/utils/package.json: Read -node_modules/framer-motion/dist/es/render/html/utils/render.js: Read -node_modules/framer-motion/dist/es/render/html/utils/scrape-motion-values.js: Read -node_modules/framer-motion/dist/es/render/html/utils/transform.js: Read -node_modules/framer-motion/dist/es/render/html/visual-element.js: Read -node_modules/framer-motion/dist/es/render/index.js: Read -node_modules/framer-motion/dist/es/render/package.json: Read -node_modules/framer-motion/dist/es/render/svg/config-motion.js: Read -node_modules/framer-motion/dist/es/render/svg/lowercase-elements.js: Read -node_modules/framer-motion/dist/es/render/svg/package.json: Read -node_modules/framer-motion/dist/es/render/svg/use-props.js: Read -node_modules/framer-motion/dist/es/render/svg/utils/build-attrs.js: Read -node_modules/framer-motion/dist/es/render/svg/utils/camel-case-attrs.js: Read -node_modules/framer-motion/dist/es/render/svg/utils/create-render-state.js: Read -node_modules/framer-motion/dist/es/render/svg/utils/package.json: Read -node_modules/framer-motion/dist/es/render/svg/utils/path.js: Read -node_modules/framer-motion/dist/es/render/svg/utils/render.js: Read -node_modules/framer-motion/dist/es/render/svg/utils/scrape-motion-values.js: Read -node_modules/framer-motion/dist/es/render/svg/utils/transform-origin.js: Read -node_modules/framer-motion/dist/es/render/svg/visual-element.js: Read -node_modules/framer-motion/dist/es/render/utils/animation-state.js: Read -node_modules/framer-motion/dist/es/render/utils/animation.js: Read -node_modules/framer-motion/dist/es/render/utils/compare-by-depth.js: Read -node_modules/framer-motion/dist/es/render/utils/flat-tree.js: Read -node_modules/framer-motion/dist/es/render/utils/is-draggable.js: Read -node_modules/framer-motion/dist/es/render/utils/lifecycles.js: Read -node_modules/framer-motion/dist/es/render/utils/motion-values.js: Read -node_modules/framer-motion/dist/es/render/utils/package.json: Read -node_modules/framer-motion/dist/es/render/utils/projection.js: Read -node_modules/framer-motion/dist/es/render/utils/setters.js: Read -node_modules/framer-motion/dist/es/render/utils/state.js: Read -node_modules/framer-motion/dist/es/render/utils/types.js: Read -node_modules/framer-motion/dist/es/render/utils/variants.js: Read -node_modules/framer-motion/dist/es/utils/array.js: Read -node_modules/framer-motion/dist/es/utils/each-axis.js: Read -node_modules/framer-motion/dist/es/utils/geometry/delta-apply.js: Read -node_modules/framer-motion/dist/es/utils/geometry/delta-calc.js: Read -node_modules/framer-motion/dist/es/utils/geometry/index.js: Read -node_modules/framer-motion/dist/es/utils/geometry/package.json: Read -node_modules/framer-motion/dist/es/utils/is-browser.js: Read -node_modules/framer-motion/dist/es/utils/is-numerical-string.js: Read -node_modules/framer-motion/dist/es/utils/is-ref-object.js: Read -node_modules/framer-motion/dist/es/utils/noop.js: Read -node_modules/framer-motion/dist/es/utils/package.json: Read -node_modules/framer-motion/dist/es/utils/resolve-value.js: Read -node_modules/framer-motion/dist/es/utils/shallow-compare.js: Read -node_modules/framer-motion/dist/es/utils/subscription-manager.js: Read -node_modules/framer-motion/dist/es/utils/time-conversion.js: Read -node_modules/framer-motion/dist/es/utils/transform.js: Read -node_modules/framer-motion/dist/es/utils/use-constant.js: Read -node_modules/framer-motion/dist/es/utils/use-cycle.js: Read -node_modules/framer-motion/dist/es/utils/use-force-update.js: Read -node_modules/framer-motion/dist/es/utils/use-isomorphic-effect.js: Read -node_modules/framer-motion/dist/es/utils/use-reduced-motion.js: Read -node_modules/framer-motion/dist/es/utils/use-unmount-effect.js: Read -node_modules/framer-motion/dist/es/value/index.js: Read -node_modules/framer-motion/dist/es/value/package.json: Read -node_modules/framer-motion/dist/es/value/scroll/package.json: Read -node_modules/framer-motion/dist/es/value/scroll/use-element-scroll.js: Read -node_modules/framer-motion/dist/es/value/scroll/use-viewport-scroll.js: Read -node_modules/framer-motion/dist/es/value/scroll/utils.js: Read -node_modules/framer-motion/dist/es/value/use-combine-values.js: Read -node_modules/framer-motion/dist/es/value/use-inverted-scale.js: Read -node_modules/framer-motion/dist/es/value/use-motion-template.js: Read -node_modules/framer-motion/dist/es/value/use-motion-value.js: Read -node_modules/framer-motion/dist/es/value/use-on-change.js: Read -node_modules/framer-motion/dist/es/value/use-spring.js: Read -node_modules/framer-motion/dist/es/value/use-transform.js: Read -node_modules/framer-motion/dist/es/value/use-velocity.js: Read -node_modules/framer-motion/dist/es/value/utils/is-motion-value.js: Read -node_modules/framer-motion/dist/es/value/utils/package.json: Read -node_modules/framer-motion/dist/es/value/utils/resolve-motion-value.js: Read -node_modules/framer-motion/dist/package.json: Read -node_modules/framer-motion/package.json: Read -node_modules/framesync/dist/es/create-render-step.js: Read -node_modules/framesync/dist/es/index.js: Read -node_modules/framesync/dist/es/on-next-frame.js: Read -node_modules/framesync/dist/es/package.json: Read -node_modules/framesync/dist/package.json: Read -node_modules/framesync/package.json: Read -node_modules/fs-extra/lib/copy/copy-sync.js: Read -node_modules/fs-extra/lib/copy/copy.js: Read -node_modules/fs-extra/lib/copy/index.js: Read -node_modules/fs-extra/lib/copy/package.json: Read -node_modules/fs-extra/lib/empty/index.js: Read -node_modules/fs-extra/lib/empty/package.json: Read -node_modules/fs-extra/lib/ensure/file.js: Read -node_modules/fs-extra/lib/ensure/index.js: Read -node_modules/fs-extra/lib/ensure/link.js: Read -node_modules/fs-extra/lib/ensure/package.json: Read -node_modules/fs-extra/lib/ensure/symlink-paths.js: Read -node_modules/fs-extra/lib/ensure/symlink-type.js: Read -node_modules/fs-extra/lib/ensure/symlink.js: Read -node_modules/fs-extra/lib/fs/index.js: Read -node_modules/fs-extra/lib/fs/package.json: Read -node_modules/fs-extra/lib/index.js: Read -node_modules/fs-extra/lib/json/index.js: Read -node_modules/fs-extra/lib/json/jsonfile.js: Read -node_modules/fs-extra/lib/json/output-json-sync.js: Read -node_modules/fs-extra/lib/json/output-json.js: Read -node_modules/fs-extra/lib/json/package.json: Read -node_modules/fs-extra/lib/mkdirs/index.js: Read -node_modules/fs-extra/lib/mkdirs/make-dir.js: Read -node_modules/fs-extra/lib/mkdirs/package.json: Read -node_modules/fs-extra/lib/mkdirs/utils.js: Read -node_modules/fs-extra/lib/move/index.js: Read -node_modules/fs-extra/lib/move/move-sync.js: Read -node_modules/fs-extra/lib/move/move.js: Read -node_modules/fs-extra/lib/move/package.json: Read -node_modules/fs-extra/lib/output-file/index.js: Read -node_modules/fs-extra/lib/output-file/package.json: Read -node_modules/fs-extra/lib/package.json: Read -node_modules/fs-extra/lib/path-exists/index.js: Read -node_modules/fs-extra/lib/path-exists/package.json: Read -node_modules/fs-extra/lib/remove/index.js: Read -node_modules/fs-extra/lib/remove/package.json: Read -node_modules/fs-extra/lib/util/package.json: Read -node_modules/fs-extra/lib/util/stat.js: Read -node_modules/fs-extra/lib/util/utimes.js: Read -node_modules/fs-extra/package.json: Read -node_modules/fs.realpath/index.js: Read -node_modules/fs.realpath/old.js: Read -node_modules/fs.realpath/package.json: Read -node_modules/fsevents/package.json: Read -node_modules/function-bind/implementation.js: Read -node_modules/function-bind/index.js: Read -node_modules/function-bind/package.json: Read -node_modules/functions-have-names/index.js: Read -node_modules/functions-have-names/package.json: Read -node_modules/fuse.js/dist/fuse.esm.js: Read -node_modules/fuse.js/dist/package.json: Read -node_modules/fuse.js/package.json: Read -node_modules/fuzzy-search/package.json: Read -node_modules/fuzzy-search/src/FuzzySearch.js: Read -node_modules/fuzzy-search/src/Helper.js: Read -node_modules/fuzzy-search/src/package.json: Read -node_modules/gensync/index.js: Read -node_modules/gensync/package.json: Read -node_modules/get-intrinsic/index.js: Read -node_modules/get-intrinsic/package.json: Read -node_modules/get-nonce/dist/es2015/index.js: Read -node_modules/get-nonce/dist/es2015/package.json: Read -node_modules/get-nonce/dist/package.json: Read -node_modules/get-nonce/package.json: Read -node_modules/get-own-enumerable-property-symbols/lib/index.js: Read -node_modules/get-own-enumerable-property-symbols/lib/package.json: Read -node_modules/get-own-enumerable-property-symbols/package.json: Read -node_modules/get-proto/Object.getPrototypeOf.js: Read -node_modules/get-proto/Reflect.getPrototypeOf.js: Read -node_modules/get-proto/index.js: Read -node_modules/get-proto/package.json: Read -node_modules/glob-parent/index.js: Read -node_modules/glob-parent/package.json: Read -node_modules/gopd/gOPD.js: Read -node_modules/gopd/index.js: Read -node_modules/gopd/package.json: Read -node_modules/graceful-fs/clone.js: Read -node_modules/graceful-fs/graceful-fs.js: Read -node_modules/graceful-fs/legacy-streams.js: Read -node_modules/graceful-fs/package.json: Read -node_modules/graceful-fs/polyfills.js: Read -node_modules/has-property-descriptors/index.js: Read -node_modules/has-property-descriptors/package.json: Read -node_modules/has-proto/index.js: Read -node_modules/has-proto/package.json: Read -node_modules/has-symbols/index.js: Read -node_modules/has-symbols/package.json: Read -node_modules/has-symbols/shams.js: Read -node_modules/has-tostringtag/package.json: Read -node_modules/has-tostringtag/shams.js: Read -node_modules/hasown/index.js: Read -node_modules/hasown/package.json: Read -node_modules/hast-util-parse-selector/index.js: Read -node_modules/hast-util-parse-selector/package.json: Read -node_modules/hastscript/factory.js: Read -node_modules/hastscript/html.js: Read -node_modules/hastscript/index.js: Read -node_modules/hastscript/package.json: Read -node_modules/hey-listen/dist/hey-listen.es.js: Read -node_modules/hey-listen/dist/package.json: Read -node_modules/hey-listen/package.json: Read -node_modules/history/esm/history.js: Read -node_modules/history/esm/package.json: Read -node_modules/history/node_modules/package.json: Read -node_modules/history/node_modules/resolve-pathname/package.json: Read -node_modules/history/node_modules/tiny-invariant/package.json: Read -node_modules/history/node_modules/tiny-warning/package.json: Read -node_modules/history/node_modules/value-equal/package.json: Read -node_modules/history/package.json: Read -node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js: Read -node_modules/hoist-non-react-statics/dist/package.json: Read -node_modules/hoist-non-react-statics/package.json: Read -node_modules/html-parse-stringify/dist/html-parse-stringify.module.js: Read -node_modules/html-parse-stringify/dist/package.json: Read -node_modules/html-parse-stringify/package.json: Read -node_modules/i18next-http-backend/esm/getFetch.cjs: Read -node_modules/i18next-http-backend/esm/index.js: Read -node_modules/i18next-http-backend/esm/package.json: Read -node_modules/i18next-http-backend/esm/request.js: Read -node_modules/i18next-http-backend/esm/utils.js: Read -node_modules/i18next-http-backend/node_modules/cross-fetch/dist/browser-ponyfill.js: Read -node_modules/i18next-http-backend/node_modules/cross-fetch/dist/package.json: Read -node_modules/i18next-http-backend/node_modules/cross-fetch/package.json: Read -node_modules/i18next-http-backend/node_modules/package.json: Read -node_modules/i18next-http-backend/package.json: Read -node_modules/i18next/dist/esm/i18next.js: Read -node_modules/i18next/dist/esm/package.json: Read -node_modules/i18next/dist/package.json: Read -node_modules/i18next/package.json: Read -node_modules/idb/build/index.js: Read -node_modules/idb/build/wrap-idb-value.js: Read -node_modules/idb/package.json: Read -node_modules/inflight/inflight.js: Read -node_modules/inflight/package.json: Read -node_modules/inherits/inherits.js: Read -node_modules/inherits/package.json: Read -node_modules/internal-slot/index.js: Read -node_modules/internal-slot/package.json: Read -node_modules/internmap/package.json: Read -node_modules/internmap/src/index.js: Read -node_modules/internmap/src/package.json: Read -node_modules/invariant/browser.js: Read -node_modules/invariant/package.json: Read -node_modules/is-alphabetical/index.js: Read -node_modules/is-alphabetical/package.json: Read -node_modules/is-alphanumerical/index.js: Read -node_modules/is-alphanumerical/package.json: Read -node_modules/is-binary-path/index.js: Read -node_modules/is-binary-path/package.json: Read -node_modules/is-builtin-module/index.js: Read -node_modules/is-builtin-module/node_modules/builtin-modules/index.js: Read -node_modules/is-builtin-module/node_modules/builtin-modules/package.json: Read -node_modules/is-builtin-module/package.json: Read -node_modules/is-callable/index.js: Read -node_modules/is-callable/package.json: Read -node_modules/is-core-module/core.json: Read -node_modules/is-core-module/index.js: Read -node_modules/is-core-module/package.json: Read -node_modules/is-date-object/index.js: Read -node_modules/is-date-object/package.json: Read -node_modules/is-decimal/index.js: Read -node_modules/is-decimal/package.json: Read -node_modules/is-extglob/index.js: Read -node_modules/is-extglob/package.json: Read -node_modules/is-glob/index.js: Read -node_modules/is-glob/package.json: Read -node_modules/is-hexadecimal/index.js: Read -node_modules/is-hexadecimal/package.json: Read -node_modules/is-module/index.js: Read -node_modules/is-module/package.json: Read -node_modules/is-number/index.js: Read -node_modules/is-number/package.json: Read -node_modules/is-obj/index.js: Read -node_modules/is-obj/package.json: Read -node_modules/is-printable-key-event/dist/index.js: Read -node_modules/is-printable-key-event/dist/package.json: Read -node_modules/is-printable-key-event/package.json: Read -node_modules/is-regex/index.js: Read -node_modules/is-regex/package.json: Read -node_modules/is-regexp/index.js: Read -node_modules/is-regexp/package.json: Read -node_modules/is-stream/index.js: Read -node_modules/is-stream/package.json: Read -node_modules/is-symbol/index.js: Read -node_modules/is-symbol/package.json: Read -node_modules/js-tokens/index.js: Read -node_modules/js-tokens/package.json: Read -node_modules/jsesc/jsesc.js: Read -node_modules/jsesc/package.json: Read -node_modules/json-schema-traverse/index.js: Read -node_modules/json-schema-traverse/package.json: Read -node_modules/json5/lib/index.js: Read -node_modules/json5/lib/package.json: Read -node_modules/json5/lib/parse.js: Read -node_modules/json5/lib/stringify.js: Read -node_modules/json5/lib/unicode.js: Read -node_modules/json5/lib/util.js: Read -node_modules/json5/package.json: Read -node_modules/jsonfile/index.js: Read -node_modules/jsonfile/package.json: Read -node_modules/jsonfile/utils.js: Read -node_modules/jsonpointer/jsonpointer.js: Read -node_modules/jsonpointer/package.json: Read -node_modules/katex/dist/fonts/KaTeX_AMS-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_AMS-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_AMS-Regular.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Caligraphic-Bold.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Caligraphic-Bold.woff: Read -node_modules/katex/dist/fonts/KaTeX_Caligraphic-Bold.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Caligraphic-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Caligraphic-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_Caligraphic-Regular.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Fraktur-Bold.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Fraktur-Bold.woff: Read -node_modules/katex/dist/fonts/KaTeX_Fraktur-Bold.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Fraktur-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Fraktur-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_Fraktur-Regular.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Main-Bold.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Main-Bold.woff: Read -node_modules/katex/dist/fonts/KaTeX_Main-Bold.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Main-BoldItalic.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Main-BoldItalic.woff: Read -node_modules/katex/dist/fonts/KaTeX_Main-BoldItalic.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Main-Italic.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Main-Italic.woff: Read -node_modules/katex/dist/fonts/KaTeX_Main-Italic.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Main-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Main-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_Main-Regular.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Math-BoldItalic.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Math-BoldItalic.woff: Read -node_modules/katex/dist/fonts/KaTeX_Math-BoldItalic.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Math-Italic.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Math-Italic.woff: Read -node_modules/katex/dist/fonts/KaTeX_Math-Italic.woff2: Read -node_modules/katex/dist/fonts/KaTeX_SansSerif-Bold.ttf: Read -node_modules/katex/dist/fonts/KaTeX_SansSerif-Bold.woff: Read -node_modules/katex/dist/fonts/KaTeX_SansSerif-Bold.woff2: Read -node_modules/katex/dist/fonts/KaTeX_SansSerif-Italic.ttf: Read -node_modules/katex/dist/fonts/KaTeX_SansSerif-Italic.woff: Read -node_modules/katex/dist/fonts/KaTeX_SansSerif-Italic.woff2: Read -node_modules/katex/dist/fonts/KaTeX_SansSerif-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_SansSerif-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_SansSerif-Regular.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Script-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Script-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_Script-Regular.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Size1-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Size1-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_Size1-Regular.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Size2-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Size2-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_Size2-Regular.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Size3-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Size3-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_Size3-Regular.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Size4-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Size4-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_Size4-Regular.woff2: Read -node_modules/katex/dist/fonts/KaTeX_Typewriter-Regular.ttf: Read -node_modules/katex/dist/fonts/KaTeX_Typewriter-Regular.woff: Read -node_modules/katex/dist/fonts/KaTeX_Typewriter-Regular.woff2: Read -node_modules/katex/dist/fonts/package.json: Read -node_modules/katex/dist/katex.min.css: Read -node_modules/katex/dist/katex.mjs: Read -node_modules/katex/dist/package.json: Read -node_modules/katex/package.json: Read -node_modules/kbar/lib/InternalEvents.js: Read -node_modules/kbar/lib/KBarAnimator.js: Read -node_modules/kbar/lib/KBarContextProvider.js: Read -node_modules/kbar/lib/KBarPortal.js: Read -node_modules/kbar/lib/KBarPositioner.js: Read -node_modules/kbar/lib/KBarResults.js: Read -node_modules/kbar/lib/KBarSearch.js: Read -node_modules/kbar/lib/action/ActionImpl.js: Read -node_modules/kbar/lib/action/ActionInterface.js: Read -node_modules/kbar/lib/action/Command.js: Read -node_modules/kbar/lib/action/HistoryImpl.js: Read -node_modules/kbar/lib/action/index.js: Read -node_modules/kbar/lib/action/package.json: Read -node_modules/kbar/lib/index.js: Read -node_modules/kbar/lib/package.json: Read -node_modules/kbar/lib/tinykeys.js: Read -node_modules/kbar/lib/types.js: Read -node_modules/kbar/lib/useKBar.js: Read -node_modules/kbar/lib/useMatches.js: Read -node_modules/kbar/lib/useRegisterActions.js: Read -node_modules/kbar/lib/useStore.js: Read -node_modules/kbar/lib/utils.js: Read -node_modules/kbar/package.json: Read -node_modules/khroma/dist/channels/index.js: Read -node_modules/khroma/dist/channels/package.json: Read -node_modules/khroma/dist/channels/reusable.js: Read -node_modules/khroma/dist/channels/type.js: Read -node_modules/khroma/dist/color/hex.js: Read -node_modules/khroma/dist/color/hsl.js: Read -node_modules/khroma/dist/color/index.js: Read -node_modules/khroma/dist/color/keyword.js: Read -node_modules/khroma/dist/color/package.json: Read -node_modules/khroma/dist/color/rgb.js: Read -node_modules/khroma/dist/constants.js: Read -node_modules/khroma/dist/index.js: Read -node_modules/khroma/dist/methods/adjust.js: Read -node_modules/khroma/dist/methods/adjust_channel.js: Read -node_modules/khroma/dist/methods/alpha.js: Read -node_modules/khroma/dist/methods/blue.js: Read -node_modules/khroma/dist/methods/change.js: Read -node_modules/khroma/dist/methods/channel.js: Read -node_modules/khroma/dist/methods/complement.js: Read -node_modules/khroma/dist/methods/contrast.js: Read -node_modules/khroma/dist/methods/darken.js: Read -node_modules/khroma/dist/methods/desaturate.js: Read -node_modules/khroma/dist/methods/grayscale.js: Read -node_modules/khroma/dist/methods/green.js: Read -node_modules/khroma/dist/methods/hsla.js: Read -node_modules/khroma/dist/methods/hue.js: Read -node_modules/khroma/dist/methods/index.js: Read -node_modules/khroma/dist/methods/invert.js: Read -node_modules/khroma/dist/methods/is_dark.js: Read -node_modules/khroma/dist/methods/is_light.js: Read -node_modules/khroma/dist/methods/is_transparent.js: Read -node_modules/khroma/dist/methods/is_valid.js: Read -node_modules/khroma/dist/methods/lighten.js: Read -node_modules/khroma/dist/methods/lightness.js: Read -node_modules/khroma/dist/methods/luminance.js: Read -node_modules/khroma/dist/methods/mix.js: Read -node_modules/khroma/dist/methods/opacify.js: Read -node_modules/khroma/dist/methods/package.json: Read -node_modules/khroma/dist/methods/red.js: Read -node_modules/khroma/dist/methods/rgba.js: Read -node_modules/khroma/dist/methods/saturate.js: Read -node_modules/khroma/dist/methods/saturation.js: Read -node_modules/khroma/dist/methods/scale.js: Read -node_modules/khroma/dist/methods/to_hex.js: Read -node_modules/khroma/dist/methods/to_hsla.js: Read -node_modules/khroma/dist/methods/to_keyword.js: Read -node_modules/khroma/dist/methods/to_rgba.js: Read -node_modules/khroma/dist/methods/transparentize.js: Read -node_modules/khroma/dist/package.json: Read -node_modules/khroma/dist/utils/channel.js: Read -node_modules/khroma/dist/utils/index.js: Read -node_modules/khroma/dist/utils/lang.js: Read -node_modules/khroma/dist/utils/package.json: Read -node_modules/khroma/dist/utils/unit.js: Read -node_modules/khroma/package.json: Read -node_modules/langium/lib/default-module.js: Read -node_modules/langium/lib/dependency-injection.js: Read -node_modules/langium/lib/documentation/comment-provider.js: Read -node_modules/langium/lib/documentation/documentation-provider.js: Read -node_modules/langium/lib/documentation/index.js: Read -node_modules/langium/lib/documentation/jsdoc.js: Read -node_modules/langium/lib/documentation/package.json: Read -node_modules/langium/lib/index.js: Read -node_modules/langium/lib/languages/generated/ast.js: Read -node_modules/langium/lib/languages/generated/package.json: Read -node_modules/langium/lib/languages/grammar-config.js: Read -node_modules/langium/lib/languages/index.js: Read -node_modules/langium/lib/languages/language-meta-data.js: Read -node_modules/langium/lib/languages/package.json: Read -node_modules/langium/lib/package.json: Read -node_modules/langium/lib/parser/async-parser.js: Read -node_modules/langium/lib/parser/completion-parser-builder.js: Read -node_modules/langium/lib/parser/cst-node-builder.js: Read -node_modules/langium/lib/parser/indentation-aware.js: Read -node_modules/langium/lib/parser/index.js: Read -node_modules/langium/lib/parser/langium-parser-builder.js: Read -node_modules/langium/lib/parser/langium-parser.js: Read -node_modules/langium/lib/parser/lexer.js: Read -node_modules/langium/lib/parser/package.json: Read -node_modules/langium/lib/parser/parser-builder-base.js: Read -node_modules/langium/lib/parser/parser-config.js: Read -node_modules/langium/lib/parser/token-builder.js: Read -node_modules/langium/lib/parser/value-converter.js: Read -node_modules/langium/lib/references/index.js: Read -node_modules/langium/lib/references/linker.js: Read -node_modules/langium/lib/references/name-provider.js: Read -node_modules/langium/lib/references/package.json: Read -node_modules/langium/lib/references/references.js: Read -node_modules/langium/lib/references/scope-computation.js: Read -node_modules/langium/lib/references/scope-provider.js: Read -node_modules/langium/lib/references/scope.js: Read -node_modules/langium/lib/serializer/hydrator.js: Read -node_modules/langium/lib/serializer/index.js: Read -node_modules/langium/lib/serializer/json-serializer.js: Read -node_modules/langium/lib/serializer/package.json: Read -node_modules/langium/lib/service-registry.js: Read -node_modules/langium/lib/services.js: Read -node_modules/langium/lib/syntax-tree.js: Read -node_modules/langium/lib/utils/ast-utils.js: Read -node_modules/langium/lib/utils/caching.js: Read -node_modules/langium/lib/utils/cancellation.js: Read -node_modules/langium/lib/utils/collections.js: Read -node_modules/langium/lib/utils/cst-utils.js: Read -node_modules/langium/lib/utils/disposable.js: Read -node_modules/langium/lib/utils/errors.js: Read -node_modules/langium/lib/utils/event.js: Read -node_modules/langium/lib/utils/grammar-loader.js: Read -node_modules/langium/lib/utils/grammar-utils.js: Read -node_modules/langium/lib/utils/index.js: Read -node_modules/langium/lib/utils/package.json: Read -node_modules/langium/lib/utils/promise-utils.js: Read -node_modules/langium/lib/utils/regexp-utils.js: Read -node_modules/langium/lib/utils/stream.js: Read -node_modules/langium/lib/utils/uri-utils.js: Read -node_modules/langium/lib/validation/document-validator.js: Read -node_modules/langium/lib/validation/index.js: Read -node_modules/langium/lib/validation/package.json: Read -node_modules/langium/lib/validation/validation-registry.js: Read -node_modules/langium/lib/workspace/ast-descriptions.js: Read -node_modules/langium/lib/workspace/ast-node-locator.js: Read -node_modules/langium/lib/workspace/configuration.js: Read -node_modules/langium/lib/workspace/document-builder.js: Read -node_modules/langium/lib/workspace/documents.js: Read -node_modules/langium/lib/workspace/file-system-provider.js: Read -node_modules/langium/lib/workspace/index-manager.js: Read -node_modules/langium/lib/workspace/index.js: Read -node_modules/langium/lib/workspace/package.json: Read -node_modules/langium/lib/workspace/workspace-lock.js: Read -node_modules/langium/lib/workspace/workspace-manager.js: Read -node_modules/langium/node_modules/chevrotain-allstar/package.json: Read -node_modules/langium/node_modules/chevrotain/package.json: Read -node_modules/langium/node_modules/package.json: Read -node_modules/langium/node_modules/vscode-languageserver-textdocument/package.json: Read -node_modules/langium/node_modules/vscode-languageserver-types/package.json: Read -node_modules/langium/node_modules/vscode-uri/package.json: Read -node_modules/langium/package.json: Read -node_modules/layout-base/layout-base.js: Read -node_modules/layout-base/package.json: Read -node_modules/leven/index.js: Read -node_modules/leven/package.json: Read -node_modules/lib0/array.js: Read -node_modules/lib0/binary.js: Read -node_modules/lib0/buffer.js: Read -node_modules/lib0/conditions.js: Read -node_modules/lib0/decoding.js: Read -node_modules/lib0/diff.js: Read -node_modules/lib0/dom.js: Read -node_modules/lib0/encoding.js: Read -node_modules/lib0/environment.js: Read -node_modules/lib0/error.js: Read -node_modules/lib0/eventloop.js: Read -node_modules/lib0/function.js: Read -node_modules/lib0/hash/package.json: Read -node_modules/lib0/hash/sha256.js: Read -node_modules/lib0/indexeddb.js: Read -node_modules/lib0/iterator.js: Read -node_modules/lib0/json.js: Read -node_modules/lib0/logging.common.js: Read -node_modules/lib0/logging.js: Read -node_modules/lib0/map.js: Read -node_modules/lib0/math.js: Read -node_modules/lib0/metric.js: Read -node_modules/lib0/mutex.js: Read -node_modules/lib0/number.js: Read -node_modules/lib0/object.js: Read -node_modules/lib0/observable.js: Read -node_modules/lib0/package.json: Read -node_modules/lib0/pair.js: Read -node_modules/lib0/promise.js: Read -node_modules/lib0/random.js: Read -node_modules/lib0/set.js: Read -node_modules/lib0/storage.js: Read -node_modules/lib0/string.js: Read -node_modules/lib0/symbol.js: Read -node_modules/lib0/time.js: Read -node_modules/lib0/traits.js: Read -node_modules/lib0/webcrypto.js: Read -node_modules/libphonenumber-js/core/index.js: Read -node_modules/libphonenumber-js/core/package.json: Read -node_modules/libphonenumber-js/es6/AsYouType.js: Read -node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternMatcher.js: Read -node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternParser.js: Read -node_modules/libphonenumber-js/es6/AsYouTypeFormatter.complete.js: Read -node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js: Read -node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js: Read -node_modules/libphonenumber-js/es6/AsYouTypeParser.js: Read -node_modules/libphonenumber-js/es6/AsYouTypeState.js: Read -node_modules/libphonenumber-js/es6/ParseError.js: Read -node_modules/libphonenumber-js/es6/PhoneNumber.js: Read -node_modules/libphonenumber-js/es6/PhoneNumberMatcher.js: Read -node_modules/libphonenumber-js/es6/constants.js: Read -node_modules/libphonenumber-js/es6/findNumbers/LRUCache.js: Read -node_modules/libphonenumber-js/es6/findNumbers/Leniency.js: Read -node_modules/libphonenumber-js/es6/findNumbers/RegExpCache.js: Read -node_modules/libphonenumber-js/es6/findNumbers/isValidCandidate.js: Read -node_modules/libphonenumber-js/es6/findNumbers/isValidPreCandidate.js: Read -node_modules/libphonenumber-js/es6/findNumbers/matchPhoneNumberStringAgainstPhoneNumber.js: Read -node_modules/libphonenumber-js/es6/findNumbers/package.json: Read -node_modules/libphonenumber-js/es6/findNumbers/parsePreCandidate.js: Read -node_modules/libphonenumber-js/es6/findNumbers/utf-8.js: Read -node_modules/libphonenumber-js/es6/findNumbers/util.js: Read -node_modules/libphonenumber-js/es6/findPhoneNumbersInText.js: Read -node_modules/libphonenumber-js/es6/format.js: Read -node_modules/libphonenumber-js/es6/formatIncompletePhoneNumber.js: Read -node_modules/libphonenumber-js/es6/getCountries.js: Read -node_modules/libphonenumber-js/es6/getCountryCallingCode.js: Read -node_modules/libphonenumber-js/es6/getExampleNumber.js: Read -node_modules/libphonenumber-js/es6/helpers/RFC3966.js: Read -node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js: Read -node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js: Read -node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js: Read -node_modules/libphonenumber-js/es6/helpers/extension/extractExtension.js: Read -node_modules/libphonenumber-js/es6/helpers/extension/package.json: Read -node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js: Read -node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js: Read -node_modules/libphonenumber-js/es6/helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js: Read -node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js: Read -node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js: Read -node_modules/libphonenumber-js/es6/helpers/extractPhoneContext.js: Read -node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js: Read -node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js: Read -node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js: Read -node_modules/libphonenumber-js/es6/helpers/getIddPrefix.js: Read -node_modules/libphonenumber-js/es6/helpers/getNumberType.js: Read -node_modules/libphonenumber-js/es6/helpers/getPossibleCountriesForNumber.js: Read -node_modules/libphonenumber-js/es6/helpers/isObject.js: Read -node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js: Read -node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js: Read -node_modules/libphonenumber-js/es6/helpers/mergeArrays.js: Read -node_modules/libphonenumber-js/es6/helpers/package.json: Read -node_modules/libphonenumber-js/es6/helpers/parseDigits.js: Read -node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js: Read -node_modules/libphonenumber-js/es6/isPossible.js: Read -node_modules/libphonenumber-js/es6/isPossiblePhoneNumber.js: Read -node_modules/libphonenumber-js/es6/isValid.js: Read -node_modules/libphonenumber-js/es6/isValidPhoneNumber.js: Read -node_modules/libphonenumber-js/es6/legacy/findNumbers.js: Read -node_modules/libphonenumber-js/es6/legacy/package.json: Read -node_modules/libphonenumber-js/es6/legacy/searchNumbers.js: Read -node_modules/libphonenumber-js/es6/metadata.js: Read -node_modules/libphonenumber-js/es6/normalizeArguments.js: Read -node_modules/libphonenumber-js/es6/package.json: Read -node_modules/libphonenumber-js/es6/parse.js: Read -node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js: Read -node_modules/libphonenumber-js/es6/parsePhoneNumber.js: Read -node_modules/libphonenumber-js/es6/parsePhoneNumberWithError.js: Read -node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js: Read -node_modules/libphonenumber-js/es6/parsePhoneNumber_.js: Read -node_modules/libphonenumber-js/es6/searchPhoneNumbersInText.js: Read -node_modules/libphonenumber-js/es6/tools/package.json: Read -node_modules/libphonenumber-js/es6/tools/semver-compare.js: Read -node_modules/libphonenumber-js/es6/validatePhoneNumberLength.js: Read -node_modules/libphonenumber-js/max/exports/AsYouType.js: Read -node_modules/libphonenumber-js/max/exports/Metadata.js: Read -node_modules/libphonenumber-js/max/exports/PhoneNumber.js: Read -node_modules/libphonenumber-js/max/exports/PhoneNumberMatcher.js: Read -node_modules/libphonenumber-js/max/exports/findNumbers.js: Read -node_modules/libphonenumber-js/max/exports/findPhoneNumbersInText.js: Read -node_modules/libphonenumber-js/max/exports/formatIncompletePhoneNumber.js: Read -node_modules/libphonenumber-js/max/exports/getCountries.js: Read -node_modules/libphonenumber-js/max/exports/getCountryCallingCode.js: Read -node_modules/libphonenumber-js/max/exports/getExampleNumber.js: Read -node_modules/libphonenumber-js/max/exports/getExtPrefix.js: Read -node_modules/libphonenumber-js/max/exports/isPossiblePhoneNumber.js: Read -node_modules/libphonenumber-js/max/exports/isSupportedCountry.js: Read -node_modules/libphonenumber-js/max/exports/isValidPhoneNumber.js: Read -node_modules/libphonenumber-js/max/exports/package.json: Read -node_modules/libphonenumber-js/max/exports/parsePhoneNumber.js: Read -node_modules/libphonenumber-js/max/exports/parsePhoneNumberWithError.js: Read -node_modules/libphonenumber-js/max/exports/searchNumbers.js: Read -node_modules/libphonenumber-js/max/exports/searchPhoneNumbersInText.js: Read -node_modules/libphonenumber-js/max/exports/validatePhoneNumberLength.js: Read -node_modules/libphonenumber-js/max/exports/withMetadataArgument.js: Read -node_modules/libphonenumber-js/max/index.js: Read -node_modules/libphonenumber-js/max/package.json: Read -node_modules/libphonenumber-js/metadata.max.json.js: Read -node_modules/libphonenumber-js/package.json: Read -node_modules/lightningcss-linux-arm64-gnu/package.json: Read -node_modules/lightningcss/node/browserslistToTargets.js: Read -node_modules/lightningcss/node/composeVisitors.js: Read -node_modules/lightningcss/node/flags.js: Read -node_modules/lightningcss/node/index.js: Read -node_modules/lightningcss/node/index.mjs: Read -node_modules/lightningcss/node/package.json: Read -node_modules/lightningcss/package.json: Read -node_modules/linkify-it/index.mjs: Read -node_modules/linkify-it/lib/package.json: Read -node_modules/linkify-it/lib/re.mjs: Read -node_modules/linkify-it/package.json: Read -node_modules/localforage/dist/localforage.js: Read -node_modules/localforage/dist/package.json: Read -node_modules/localforage/package.json: Read -node_modules/lodash-es/_DataView.js: Read -node_modules/lodash-es/_Hash.js: Read -node_modules/lodash-es/_LazyWrapper.js: Read -node_modules/lodash-es/_ListCache.js: Read -node_modules/lodash-es/_LodashWrapper.js: Read -node_modules/lodash-es/_Map.js: Read -node_modules/lodash-es/_MapCache.js: Read -node_modules/lodash-es/_Promise.js: Read -node_modules/lodash-es/_Set.js: Read -node_modules/lodash-es/_SetCache.js: Read -node_modules/lodash-es/_Stack.js: Read -node_modules/lodash-es/_Symbol.js: Read -node_modules/lodash-es/_Uint8Array.js: Read -node_modules/lodash-es/_WeakMap.js: Read -node_modules/lodash-es/_apply.js: Read -node_modules/lodash-es/_arrayAggregator.js: Read -node_modules/lodash-es/_arrayEach.js: Read -node_modules/lodash-es/_arrayEachRight.js: Read -node_modules/lodash-es/_arrayEvery.js: Read -node_modules/lodash-es/_arrayFilter.js: Read -node_modules/lodash-es/_arrayIncludes.js: Read -node_modules/lodash-es/_arrayIncludesWith.js: Read -node_modules/lodash-es/_arrayLikeKeys.js: Read -node_modules/lodash-es/_arrayMap.js: Read -node_modules/lodash-es/_arrayPush.js: Read -node_modules/lodash-es/_arrayReduce.js: Read -node_modules/lodash-es/_arrayReduceRight.js: Read -node_modules/lodash-es/_arraySample.js: Read -node_modules/lodash-es/_arraySampleSize.js: Read -node_modules/lodash-es/_arrayShuffle.js: Read -node_modules/lodash-es/_arraySome.js: Read -node_modules/lodash-es/_asciiSize.js: Read -node_modules/lodash-es/_asciiToArray.js: Read -node_modules/lodash-es/_asciiWords.js: Read -node_modules/lodash-es/_assignMergeValue.js: Read -node_modules/lodash-es/_assignValue.js: Read -node_modules/lodash-es/_assocIndexOf.js: Read -node_modules/lodash-es/_baseAggregator.js: Read -node_modules/lodash-es/_baseAssign.js: Read -node_modules/lodash-es/_baseAssignIn.js: Read -node_modules/lodash-es/_baseAssignValue.js: Read -node_modules/lodash-es/_baseAt.js: Read -node_modules/lodash-es/_baseClamp.js: Read -node_modules/lodash-es/_baseClone.js: Read -node_modules/lodash-es/_baseConforms.js: Read -node_modules/lodash-es/_baseConformsTo.js: Read -node_modules/lodash-es/_baseCreate.js: Read -node_modules/lodash-es/_baseDelay.js: Read -node_modules/lodash-es/_baseDifference.js: Read -node_modules/lodash-es/_baseEach.js: Read -node_modules/lodash-es/_baseEachRight.js: Read -node_modules/lodash-es/_baseEvery.js: Read -node_modules/lodash-es/_baseExtremum.js: Read -node_modules/lodash-es/_baseFill.js: Read -node_modules/lodash-es/_baseFilter.js: Read -node_modules/lodash-es/_baseFindIndex.js: Read -node_modules/lodash-es/_baseFindKey.js: Read -node_modules/lodash-es/_baseFlatten.js: Read -node_modules/lodash-es/_baseFor.js: Read -node_modules/lodash-es/_baseForOwn.js: Read -node_modules/lodash-es/_baseForOwnRight.js: Read -node_modules/lodash-es/_baseForRight.js: Read -node_modules/lodash-es/_baseFunctions.js: Read -node_modules/lodash-es/_baseGet.js: Read -node_modules/lodash-es/_baseGetAllKeys.js: Read -node_modules/lodash-es/_baseGetTag.js: Read -node_modules/lodash-es/_baseGt.js: Read -node_modules/lodash-es/_baseHas.js: Read -node_modules/lodash-es/_baseHasIn.js: Read -node_modules/lodash-es/_baseInRange.js: Read -node_modules/lodash-es/_baseIndexOf.js: Read -node_modules/lodash-es/_baseIndexOfWith.js: Read -node_modules/lodash-es/_baseIntersection.js: Read -node_modules/lodash-es/_baseInverter.js: Read -node_modules/lodash-es/_baseInvoke.js: Read -node_modules/lodash-es/_baseIsArguments.js: Read -node_modules/lodash-es/_baseIsArrayBuffer.js: Read -node_modules/lodash-es/_baseIsDate.js: Read -node_modules/lodash-es/_baseIsEqual.js: Read -node_modules/lodash-es/_baseIsEqualDeep.js: Read -node_modules/lodash-es/_baseIsMap.js: Read -node_modules/lodash-es/_baseIsMatch.js: Read -node_modules/lodash-es/_baseIsNaN.js: Read -node_modules/lodash-es/_baseIsNative.js: Read -node_modules/lodash-es/_baseIsRegExp.js: Read -node_modules/lodash-es/_baseIsSet.js: Read -node_modules/lodash-es/_baseIsTypedArray.js: Read -node_modules/lodash-es/_baseIteratee.js: Read -node_modules/lodash-es/_baseKeys.js: Read -node_modules/lodash-es/_baseKeysIn.js: Read -node_modules/lodash-es/_baseLodash.js: Read -node_modules/lodash-es/_baseLt.js: Read -node_modules/lodash-es/_baseMap.js: Read -node_modules/lodash-es/_baseMatches.js: Read -node_modules/lodash-es/_baseMatchesProperty.js: Read -node_modules/lodash-es/_baseMean.js: Read -node_modules/lodash-es/_baseMerge.js: Read -node_modules/lodash-es/_baseMergeDeep.js: Read -node_modules/lodash-es/_baseNth.js: Read -node_modules/lodash-es/_baseOrderBy.js: Read -node_modules/lodash-es/_basePick.js: Read -node_modules/lodash-es/_basePickBy.js: Read -node_modules/lodash-es/_baseProperty.js: Read -node_modules/lodash-es/_basePropertyDeep.js: Read -node_modules/lodash-es/_basePropertyOf.js: Read -node_modules/lodash-es/_basePullAll.js: Read -node_modules/lodash-es/_basePullAt.js: Read -node_modules/lodash-es/_baseRandom.js: Read -node_modules/lodash-es/_baseRange.js: Read -node_modules/lodash-es/_baseReduce.js: Read -node_modules/lodash-es/_baseRepeat.js: Read -node_modules/lodash-es/_baseRest.js: Read -node_modules/lodash-es/_baseSample.js: Read -node_modules/lodash-es/_baseSampleSize.js: Read -node_modules/lodash-es/_baseSet.js: Read -node_modules/lodash-es/_baseSetData.js: Read -node_modules/lodash-es/_baseSetToString.js: Read -node_modules/lodash-es/_baseShuffle.js: Read -node_modules/lodash-es/_baseSlice.js: Read -node_modules/lodash-es/_baseSome.js: Read -node_modules/lodash-es/_baseSortBy.js: Read -node_modules/lodash-es/_baseSortedIndex.js: Read -node_modules/lodash-es/_baseSortedIndexBy.js: Read -node_modules/lodash-es/_baseSortedUniq.js: Read -node_modules/lodash-es/_baseSum.js: Read -node_modules/lodash-es/_baseTimes.js: Read -node_modules/lodash-es/_baseToNumber.js: Read -node_modules/lodash-es/_baseToPairs.js: Read -node_modules/lodash-es/_baseToString.js: Read -node_modules/lodash-es/_baseTrim.js: Read -node_modules/lodash-es/_baseUnary.js: Read -node_modules/lodash-es/_baseUniq.js: Read -node_modules/lodash-es/_baseUnset.js: Read -node_modules/lodash-es/_baseUpdate.js: Read -node_modules/lodash-es/_baseValues.js: Read -node_modules/lodash-es/_baseWhile.js: Read -node_modules/lodash-es/_baseWrapperValue.js: Read -node_modules/lodash-es/_baseXor.js: Read -node_modules/lodash-es/_baseZipObject.js: Read -node_modules/lodash-es/_cacheHas.js: Read -node_modules/lodash-es/_castArrayLikeObject.js: Read -node_modules/lodash-es/_castFunction.js: Read -node_modules/lodash-es/_castPath.js: Read -node_modules/lodash-es/_castRest.js: Read -node_modules/lodash-es/_castSlice.js: Read -node_modules/lodash-es/_charsEndIndex.js: Read -node_modules/lodash-es/_charsStartIndex.js: Read -node_modules/lodash-es/_cloneArrayBuffer.js: Read -node_modules/lodash-es/_cloneBuffer.js: Read -node_modules/lodash-es/_cloneDataView.js: Read -node_modules/lodash-es/_cloneRegExp.js: Read -node_modules/lodash-es/_cloneSymbol.js: Read -node_modules/lodash-es/_cloneTypedArray.js: Read -node_modules/lodash-es/_compareAscending.js: Read -node_modules/lodash-es/_compareMultiple.js: Read -node_modules/lodash-es/_composeArgs.js: Read -node_modules/lodash-es/_composeArgsRight.js: Read -node_modules/lodash-es/_copyArray.js: Read -node_modules/lodash-es/_copyObject.js: Read -node_modules/lodash-es/_copySymbols.js: Read -node_modules/lodash-es/_copySymbolsIn.js: Read -node_modules/lodash-es/_coreJsData.js: Read -node_modules/lodash-es/_countHolders.js: Read -node_modules/lodash-es/_createAggregator.js: Read -node_modules/lodash-es/_createAssigner.js: Read -node_modules/lodash-es/_createBaseEach.js: Read -node_modules/lodash-es/_createBaseFor.js: Read -node_modules/lodash-es/_createBind.js: Read -node_modules/lodash-es/_createCaseFirst.js: Read -node_modules/lodash-es/_createCompounder.js: Read -node_modules/lodash-es/_createCtor.js: Read -node_modules/lodash-es/_createCurry.js: Read -node_modules/lodash-es/_createFind.js: Read -node_modules/lodash-es/_createFlow.js: Read -node_modules/lodash-es/_createHybrid.js: Read -node_modules/lodash-es/_createInverter.js: Read -node_modules/lodash-es/_createMathOperation.js: Read -node_modules/lodash-es/_createOver.js: Read -node_modules/lodash-es/_createPadding.js: Read -node_modules/lodash-es/_createPartial.js: Read -node_modules/lodash-es/_createRange.js: Read -node_modules/lodash-es/_createRecurry.js: Read -node_modules/lodash-es/_createRelationalOperation.js: Read -node_modules/lodash-es/_createRound.js: Read -node_modules/lodash-es/_createSet.js: Read -node_modules/lodash-es/_createToPairs.js: Read -node_modules/lodash-es/_createWrap.js: Read -node_modules/lodash-es/_customDefaultsAssignIn.js: Read -node_modules/lodash-es/_customDefaultsMerge.js: Read -node_modules/lodash-es/_customOmitClone.js: Read -node_modules/lodash-es/_deburrLetter.js: Read -node_modules/lodash-es/_defineProperty.js: Read -node_modules/lodash-es/_equalArrays.js: Read -node_modules/lodash-es/_equalByTag.js: Read -node_modules/lodash-es/_equalObjects.js: Read -node_modules/lodash-es/_escapeHtmlChar.js: Read -node_modules/lodash-es/_escapeStringChar.js: Read -node_modules/lodash-es/_flatRest.js: Read -node_modules/lodash-es/_freeGlobal.js: Read -node_modules/lodash-es/_getAllKeys.js: Read -node_modules/lodash-es/_getAllKeysIn.js: Read -node_modules/lodash-es/_getData.js: Read -node_modules/lodash-es/_getFuncName.js: Read -node_modules/lodash-es/_getHolder.js: Read -node_modules/lodash-es/_getMapData.js: Read -node_modules/lodash-es/_getMatchData.js: Read -node_modules/lodash-es/_getNative.js: Read -node_modules/lodash-es/_getPrototype.js: Read -node_modules/lodash-es/_getRawTag.js: Read -node_modules/lodash-es/_getSymbols.js: Read -node_modules/lodash-es/_getSymbolsIn.js: Read -node_modules/lodash-es/_getTag.js: Read -node_modules/lodash-es/_getValue.js: Read -node_modules/lodash-es/_getView.js: Read -node_modules/lodash-es/_getWrapDetails.js: Read -node_modules/lodash-es/_hasPath.js: Read -node_modules/lodash-es/_hasUnicode.js: Read -node_modules/lodash-es/_hasUnicodeWord.js: Read -node_modules/lodash-es/_hashClear.js: Read -node_modules/lodash-es/_hashDelete.js: Read -node_modules/lodash-es/_hashGet.js: Read -node_modules/lodash-es/_hashHas.js: Read -node_modules/lodash-es/_hashSet.js: Read -node_modules/lodash-es/_initCloneArray.js: Read -node_modules/lodash-es/_initCloneByTag.js: Read -node_modules/lodash-es/_initCloneObject.js: Read -node_modules/lodash-es/_insertWrapDetails.js: Read -node_modules/lodash-es/_isFlattenable.js: Read -node_modules/lodash-es/_isIndex.js: Read -node_modules/lodash-es/_isIterateeCall.js: Read -node_modules/lodash-es/_isKey.js: Read -node_modules/lodash-es/_isKeyable.js: Read -node_modules/lodash-es/_isLaziable.js: Read -node_modules/lodash-es/_isMaskable.js: Read -node_modules/lodash-es/_isMasked.js: Read -node_modules/lodash-es/_isPrototype.js: Read -node_modules/lodash-es/_isStrictComparable.js: Read -node_modules/lodash-es/_iteratorToArray.js: Read -node_modules/lodash-es/_lazyClone.js: Read -node_modules/lodash-es/_lazyReverse.js: Read -node_modules/lodash-es/_lazyValue.js: Read -node_modules/lodash-es/_listCacheClear.js: Read -node_modules/lodash-es/_listCacheDelete.js: Read -node_modules/lodash-es/_listCacheGet.js: Read -node_modules/lodash-es/_listCacheHas.js: Read -node_modules/lodash-es/_listCacheSet.js: Read -node_modules/lodash-es/_mapCacheClear.js: Read -node_modules/lodash-es/_mapCacheDelete.js: Read -node_modules/lodash-es/_mapCacheGet.js: Read -node_modules/lodash-es/_mapCacheHas.js: Read -node_modules/lodash-es/_mapCacheSet.js: Read -node_modules/lodash-es/_mapToArray.js: Read -node_modules/lodash-es/_matchesStrictComparable.js: Read -node_modules/lodash-es/_memoizeCapped.js: Read -node_modules/lodash-es/_mergeData.js: Read -node_modules/lodash-es/_metaMap.js: Read -node_modules/lodash-es/_nativeCreate.js: Read -node_modules/lodash-es/_nativeKeys.js: Read -node_modules/lodash-es/_nativeKeysIn.js: Read -node_modules/lodash-es/_nodeUtil.js: Read -node_modules/lodash-es/_objectToString.js: Read -node_modules/lodash-es/_overArg.js: Read -node_modules/lodash-es/_overRest.js: Read -node_modules/lodash-es/_parent.js: Read -node_modules/lodash-es/_reEscape.js: Read -node_modules/lodash-es/_reEvaluate.js: Read -node_modules/lodash-es/_reInterpolate.js: Read -node_modules/lodash-es/_realNames.js: Read -node_modules/lodash-es/_reorder.js: Read -node_modules/lodash-es/_replaceHolders.js: Read -node_modules/lodash-es/_root.js: Read -node_modules/lodash-es/_safeGet.js: Read -node_modules/lodash-es/_setCacheAdd.js: Read -node_modules/lodash-es/_setCacheHas.js: Read -node_modules/lodash-es/_setData.js: Read -node_modules/lodash-es/_setToArray.js: Read -node_modules/lodash-es/_setToPairs.js: Read -node_modules/lodash-es/_setToString.js: Read -node_modules/lodash-es/_setWrapToString.js: Read -node_modules/lodash-es/_shortOut.js: Read -node_modules/lodash-es/_shuffleSelf.js: Read -node_modules/lodash-es/_stackClear.js: Read -node_modules/lodash-es/_stackDelete.js: Read -node_modules/lodash-es/_stackGet.js: Read -node_modules/lodash-es/_stackHas.js: Read -node_modules/lodash-es/_stackSet.js: Read -node_modules/lodash-es/_strictIndexOf.js: Read -node_modules/lodash-es/_strictLastIndexOf.js: Read -node_modules/lodash-es/_stringSize.js: Read -node_modules/lodash-es/_stringToArray.js: Read -node_modules/lodash-es/_stringToPath.js: Read -node_modules/lodash-es/_toKey.js: Read -node_modules/lodash-es/_toSource.js: Read -node_modules/lodash-es/_trimmedEndIndex.js: Read -node_modules/lodash-es/_unescapeHtmlChar.js: Read -node_modules/lodash-es/_unicodeSize.js: Read -node_modules/lodash-es/_unicodeToArray.js: Read -node_modules/lodash-es/_unicodeWords.js: Read -node_modules/lodash-es/_updateWrapDetails.js: Read -node_modules/lodash-es/_wrapperClone.js: Read -node_modules/lodash-es/add.js: Read -node_modules/lodash-es/after.js: Read -node_modules/lodash-es/array.default.js: Read -node_modules/lodash-es/array.js: Read -node_modules/lodash-es/ary.js: Read -node_modules/lodash-es/assign.js: Read -node_modules/lodash-es/assignIn.js: Read -node_modules/lodash-es/assignInWith.js: Read -node_modules/lodash-es/assignWith.js: Read -node_modules/lodash-es/at.js: Read -node_modules/lodash-es/attempt.js: Read -node_modules/lodash-es/before.js: Read -node_modules/lodash-es/bind.js: Read -node_modules/lodash-es/bindAll.js: Read -node_modules/lodash-es/bindKey.js: Read -node_modules/lodash-es/camelCase.js: Read -node_modules/lodash-es/capitalize.js: Read -node_modules/lodash-es/castArray.js: Read -node_modules/lodash-es/ceil.js: Read -node_modules/lodash-es/chain.js: Read -node_modules/lodash-es/chunk.js: Read -node_modules/lodash-es/clamp.js: Read -node_modules/lodash-es/clone.js: Read -node_modules/lodash-es/cloneDeep.js: Read -node_modules/lodash-es/cloneDeepWith.js: Read -node_modules/lodash-es/cloneWith.js: Read -node_modules/lodash-es/collection.default.js: Read -node_modules/lodash-es/collection.js: Read -node_modules/lodash-es/commit.js: Read -node_modules/lodash-es/compact.js: Read -node_modules/lodash-es/concat.js: Read -node_modules/lodash-es/cond.js: Read -node_modules/lodash-es/conforms.js: Read -node_modules/lodash-es/conformsTo.js: Read -node_modules/lodash-es/constant.js: Read -node_modules/lodash-es/countBy.js: Read -node_modules/lodash-es/create.js: Read -node_modules/lodash-es/curry.js: Read -node_modules/lodash-es/curryRight.js: Read -node_modules/lodash-es/date.default.js: Read -node_modules/lodash-es/date.js: Read -node_modules/lodash-es/debounce.js: Read -node_modules/lodash-es/deburr.js: Read -node_modules/lodash-es/defaultTo.js: Read -node_modules/lodash-es/defaults.js: Read -node_modules/lodash-es/defaultsDeep.js: Read -node_modules/lodash-es/defer.js: Read -node_modules/lodash-es/delay.js: Read -node_modules/lodash-es/difference.js: Read -node_modules/lodash-es/differenceBy.js: Read -node_modules/lodash-es/differenceWith.js: Read -node_modules/lodash-es/divide.js: Read -node_modules/lodash-es/drop.js: Read -node_modules/lodash-es/dropRight.js: Read -node_modules/lodash-es/dropRightWhile.js: Read -node_modules/lodash-es/dropWhile.js: Read -node_modules/lodash-es/each.js: Read -node_modules/lodash-es/eachRight.js: Read -node_modules/lodash-es/endsWith.js: Read -node_modules/lodash-es/entries.js: Read -node_modules/lodash-es/entriesIn.js: Read -node_modules/lodash-es/eq.js: Read -node_modules/lodash-es/escape.js: Read -node_modules/lodash-es/escapeRegExp.js: Read -node_modules/lodash-es/every.js: Read -node_modules/lodash-es/extend.js: Read -node_modules/lodash-es/extendWith.js: Read -node_modules/lodash-es/fill.js: Read -node_modules/lodash-es/filter.js: Read -node_modules/lodash-es/find.js: Read -node_modules/lodash-es/findIndex.js: Read -node_modules/lodash-es/findKey.js: Read -node_modules/lodash-es/findLast.js: Read -node_modules/lodash-es/findLastIndex.js: Read -node_modules/lodash-es/findLastKey.js: Read -node_modules/lodash-es/first.js: Read -node_modules/lodash-es/flatMap.js: Read -node_modules/lodash-es/flatMapDeep.js: Read -node_modules/lodash-es/flatMapDepth.js: Read -node_modules/lodash-es/flatten.js: Read -node_modules/lodash-es/flattenDeep.js: Read -node_modules/lodash-es/flattenDepth.js: Read -node_modules/lodash-es/flip.js: Read -node_modules/lodash-es/floor.js: Read -node_modules/lodash-es/flow.js: Read -node_modules/lodash-es/flowRight.js: Read -node_modules/lodash-es/forEach.js: Read -node_modules/lodash-es/forEachRight.js: Read -node_modules/lodash-es/forIn.js: Read -node_modules/lodash-es/forInRight.js: Read -node_modules/lodash-es/forOwn.js: Read -node_modules/lodash-es/forOwnRight.js: Read -node_modules/lodash-es/fromPairs.js: Read -node_modules/lodash-es/function.default.js: Read -node_modules/lodash-es/function.js: Read -node_modules/lodash-es/functions.js: Read -node_modules/lodash-es/functionsIn.js: Read -node_modules/lodash-es/get.js: Read -node_modules/lodash-es/groupBy.js: Read -node_modules/lodash-es/gt.js: Read -node_modules/lodash-es/gte.js: Read -node_modules/lodash-es/has.js: Read -node_modules/lodash-es/hasIn.js: Read -node_modules/lodash-es/head.js: Read -node_modules/lodash-es/identity.js: Read -node_modules/lodash-es/inRange.js: Read -node_modules/lodash-es/includes.js: Read -node_modules/lodash-es/indexOf.js: Read -node_modules/lodash-es/initial.js: Read -node_modules/lodash-es/intersection.js: Read -node_modules/lodash-es/intersectionBy.js: Read -node_modules/lodash-es/intersectionWith.js: Read -node_modules/lodash-es/invert.js: Read -node_modules/lodash-es/invertBy.js: Read -node_modules/lodash-es/invoke.js: Read -node_modules/lodash-es/invokeMap.js: Read -node_modules/lodash-es/isArguments.js: Read -node_modules/lodash-es/isArray.js: Read -node_modules/lodash-es/isArrayBuffer.js: Read -node_modules/lodash-es/isArrayLike.js: Read -node_modules/lodash-es/isArrayLikeObject.js: Read -node_modules/lodash-es/isBoolean.js: Read -node_modules/lodash-es/isBuffer.js: Read -node_modules/lodash-es/isDate.js: Read -node_modules/lodash-es/isElement.js: Read -node_modules/lodash-es/isEmpty.js: Read -node_modules/lodash-es/isEqual.js: Read -node_modules/lodash-es/isEqualWith.js: Read -node_modules/lodash-es/isError.js: Read -node_modules/lodash-es/isFinite.js: Read -node_modules/lodash-es/isFunction.js: Read -node_modules/lodash-es/isInteger.js: Read -node_modules/lodash-es/isLength.js: Read -node_modules/lodash-es/isMap.js: Read -node_modules/lodash-es/isMatch.js: Read -node_modules/lodash-es/isMatchWith.js: Read -node_modules/lodash-es/isNaN.js: Read -node_modules/lodash-es/isNative.js: Read -node_modules/lodash-es/isNil.js: Read -node_modules/lodash-es/isNull.js: Read -node_modules/lodash-es/isNumber.js: Read -node_modules/lodash-es/isObject.js: Read -node_modules/lodash-es/isObjectLike.js: Read -node_modules/lodash-es/isPlainObject.js: Read -node_modules/lodash-es/isRegExp.js: Read -node_modules/lodash-es/isSafeInteger.js: Read -node_modules/lodash-es/isSet.js: Read -node_modules/lodash-es/isString.js: Read -node_modules/lodash-es/isSymbol.js: Read -node_modules/lodash-es/isTypedArray.js: Read -node_modules/lodash-es/isUndefined.js: Read -node_modules/lodash-es/isWeakMap.js: Read -node_modules/lodash-es/isWeakSet.js: Read -node_modules/lodash-es/iteratee.js: Read -node_modules/lodash-es/join.js: Read -node_modules/lodash-es/kebabCase.js: Read -node_modules/lodash-es/keyBy.js: Read -node_modules/lodash-es/keys.js: Read -node_modules/lodash-es/keysIn.js: Read -node_modules/lodash-es/lang.default.js: Read -node_modules/lodash-es/lang.js: Read -node_modules/lodash-es/last.js: Read -node_modules/lodash-es/lastIndexOf.js: Read -node_modules/lodash-es/lodash.default.js: Read -node_modules/lodash-es/lodash.js: Read -node_modules/lodash-es/lowerCase.js: Read -node_modules/lodash-es/lowerFirst.js: Read -node_modules/lodash-es/lt.js: Read -node_modules/lodash-es/lte.js: Read -node_modules/lodash-es/map.js: Read -node_modules/lodash-es/mapKeys.js: Read -node_modules/lodash-es/mapValues.js: Read -node_modules/lodash-es/matches.js: Read -node_modules/lodash-es/matchesProperty.js: Read -node_modules/lodash-es/math.default.js: Read -node_modules/lodash-es/math.js: Read -node_modules/lodash-es/max.js: Read -node_modules/lodash-es/maxBy.js: Read -node_modules/lodash-es/mean.js: Read -node_modules/lodash-es/meanBy.js: Read -node_modules/lodash-es/memoize.js: Read -node_modules/lodash-es/merge.js: Read -node_modules/lodash-es/mergeWith.js: Read -node_modules/lodash-es/method.js: Read -node_modules/lodash-es/methodOf.js: Read -node_modules/lodash-es/min.js: Read -node_modules/lodash-es/minBy.js: Read -node_modules/lodash-es/mixin.js: Read -node_modules/lodash-es/multiply.js: Read -node_modules/lodash-es/negate.js: Read -node_modules/lodash-es/next.js: Read -node_modules/lodash-es/noop.js: Read -node_modules/lodash-es/now.js: Read -node_modules/lodash-es/nth.js: Read -node_modules/lodash-es/nthArg.js: Read -node_modules/lodash-es/number.default.js: Read -node_modules/lodash-es/number.js: Read -node_modules/lodash-es/object.default.js: Read -node_modules/lodash-es/object.js: Read -node_modules/lodash-es/omit.js: Read -node_modules/lodash-es/omitBy.js: Read -node_modules/lodash-es/once.js: Read -node_modules/lodash-es/orderBy.js: Read -node_modules/lodash-es/over.js: Read -node_modules/lodash-es/overArgs.js: Read -node_modules/lodash-es/overEvery.js: Read -node_modules/lodash-es/overSome.js: Read -node_modules/lodash-es/package.json: Read -node_modules/lodash-es/pad.js: Read -node_modules/lodash-es/padEnd.js: Read -node_modules/lodash-es/padStart.js: Read -node_modules/lodash-es/parseInt.js: Read -node_modules/lodash-es/partial.js: Read -node_modules/lodash-es/partialRight.js: Read -node_modules/lodash-es/partition.js: Read -node_modules/lodash-es/pick.js: Read -node_modules/lodash-es/pickBy.js: Read -node_modules/lodash-es/plant.js: Read -node_modules/lodash-es/property.js: Read -node_modules/lodash-es/propertyOf.js: Read -node_modules/lodash-es/pull.js: Read -node_modules/lodash-es/pullAll.js: Read -node_modules/lodash-es/pullAllBy.js: Read -node_modules/lodash-es/pullAllWith.js: Read -node_modules/lodash-es/pullAt.js: Read -node_modules/lodash-es/random.js: Read -node_modules/lodash-es/range.js: Read -node_modules/lodash-es/rangeRight.js: Read -node_modules/lodash-es/rearg.js: Read -node_modules/lodash-es/reduce.js: Read -node_modules/lodash-es/reduceRight.js: Read -node_modules/lodash-es/reject.js: Read -node_modules/lodash-es/remove.js: Read -node_modules/lodash-es/repeat.js: Read -node_modules/lodash-es/replace.js: Read -node_modules/lodash-es/rest.js: Read -node_modules/lodash-es/result.js: Read -node_modules/lodash-es/reverse.js: Read -node_modules/lodash-es/round.js: Read -node_modules/lodash-es/sample.js: Read -node_modules/lodash-es/sampleSize.js: Read -node_modules/lodash-es/seq.default.js: Read -node_modules/lodash-es/seq.js: Read -node_modules/lodash-es/set.js: Read -node_modules/lodash-es/setWith.js: Read -node_modules/lodash-es/shuffle.js: Read -node_modules/lodash-es/size.js: Read -node_modules/lodash-es/slice.js: Read -node_modules/lodash-es/snakeCase.js: Read -node_modules/lodash-es/some.js: Read -node_modules/lodash-es/sortBy.js: Read -node_modules/lodash-es/sortedIndex.js: Read -node_modules/lodash-es/sortedIndexBy.js: Read -node_modules/lodash-es/sortedIndexOf.js: Read -node_modules/lodash-es/sortedLastIndex.js: Read -node_modules/lodash-es/sortedLastIndexBy.js: Read -node_modules/lodash-es/sortedLastIndexOf.js: Read -node_modules/lodash-es/sortedUniq.js: Read -node_modules/lodash-es/sortedUniqBy.js: Read -node_modules/lodash-es/split.js: Read -node_modules/lodash-es/spread.js: Read -node_modules/lodash-es/startCase.js: Read -node_modules/lodash-es/startsWith.js: Read -node_modules/lodash-es/string.default.js: Read -node_modules/lodash-es/string.js: Read -node_modules/lodash-es/stubArray.js: Read -node_modules/lodash-es/stubFalse.js: Read -node_modules/lodash-es/stubObject.js: Read -node_modules/lodash-es/stubString.js: Read -node_modules/lodash-es/stubTrue.js: Read -node_modules/lodash-es/subtract.js: Read -node_modules/lodash-es/sum.js: Read -node_modules/lodash-es/sumBy.js: Read -node_modules/lodash-es/tail.js: Read -node_modules/lodash-es/take.js: Read -node_modules/lodash-es/takeRight.js: Read -node_modules/lodash-es/takeRightWhile.js: Read -node_modules/lodash-es/takeWhile.js: Read -node_modules/lodash-es/tap.js: Read -node_modules/lodash-es/template.js: Read -node_modules/lodash-es/templateSettings.js: Read -node_modules/lodash-es/throttle.js: Read -node_modules/lodash-es/thru.js: Read -node_modules/lodash-es/times.js: Read -node_modules/lodash-es/toArray.js: Read -node_modules/lodash-es/toFinite.js: Read -node_modules/lodash-es/toInteger.js: Read -node_modules/lodash-es/toIterator.js: Read -node_modules/lodash-es/toJSON.js: Read -node_modules/lodash-es/toLength.js: Read -node_modules/lodash-es/toLower.js: Read -node_modules/lodash-es/toNumber.js: Read -node_modules/lodash-es/toPairs.js: Read -node_modules/lodash-es/toPairsIn.js: Read -node_modules/lodash-es/toPath.js: Read -node_modules/lodash-es/toPlainObject.js: Read -node_modules/lodash-es/toSafeInteger.js: Read -node_modules/lodash-es/toString.js: Read -node_modules/lodash-es/toUpper.js: Read -node_modules/lodash-es/transform.js: Read -node_modules/lodash-es/trim.js: Read -node_modules/lodash-es/trimEnd.js: Read -node_modules/lodash-es/trimStart.js: Read -node_modules/lodash-es/truncate.js: Read -node_modules/lodash-es/unary.js: Read -node_modules/lodash-es/unescape.js: Read -node_modules/lodash-es/union.js: Read -node_modules/lodash-es/unionBy.js: Read -node_modules/lodash-es/unionWith.js: Read -node_modules/lodash-es/uniq.js: Read -node_modules/lodash-es/uniqBy.js: Read -node_modules/lodash-es/uniqWith.js: Read -node_modules/lodash-es/uniqueId.js: Read -node_modules/lodash-es/unset.js: Read -node_modules/lodash-es/unzip.js: Read -node_modules/lodash-es/unzipWith.js: Read -node_modules/lodash-es/update.js: Read -node_modules/lodash-es/updateWith.js: Read -node_modules/lodash-es/upperCase.js: Read -node_modules/lodash-es/upperFirst.js: Read -node_modules/lodash-es/util.default.js: Read -node_modules/lodash-es/util.js: Read -node_modules/lodash-es/value.js: Read -node_modules/lodash-es/valueOf.js: Read -node_modules/lodash-es/values.js: Read -node_modules/lodash-es/valuesIn.js: Read -node_modules/lodash-es/without.js: Read -node_modules/lodash-es/words.js: Read -node_modules/lodash-es/wrap.js: Read -node_modules/lodash-es/wrapperAt.js: Read -node_modules/lodash-es/wrapperChain.js: Read -node_modules/lodash-es/wrapperLodash.js: Read -node_modules/lodash-es/wrapperReverse.js: Read -node_modules/lodash-es/wrapperValue.js: Read -node_modules/lodash-es/xor.js: Read -node_modules/lodash-es/xorBy.js: Read -node_modules/lodash-es/xorWith.js: Read -node_modules/lodash-es/zip.js: Read -node_modules/lodash-es/zipObject.js: Read -node_modules/lodash-es/zipObjectDeep.js: Read -node_modules/lodash-es/zipWith.js: Read -node_modules/lodash.debounce/index.js: Read -node_modules/lodash.debounce/package.json: Read -node_modules/lodash/_DataView.js: Read -node_modules/lodash/_Hash.js: Read -node_modules/lodash/_ListCache.js: Read -node_modules/lodash/_Map.js: Read -node_modules/lodash/_MapCache.js: Read -node_modules/lodash/_Promise.js: Read -node_modules/lodash/_Set.js: Read -node_modules/lodash/_SetCache.js: Read -node_modules/lodash/_Stack.js: Read -node_modules/lodash/_Symbol.js: Read -node_modules/lodash/_Uint8Array.js: Read -node_modules/lodash/_WeakMap.js: Read -node_modules/lodash/_apply.js: Read -node_modules/lodash/_arrayAggregator.js: Read -node_modules/lodash/_arrayEach.js: Read -node_modules/lodash/_arrayFilter.js: Read -node_modules/lodash/_arrayIncludes.js: Read -node_modules/lodash/_arrayIncludesWith.js: Read -node_modules/lodash/_arrayLikeKeys.js: Read -node_modules/lodash/_arrayMap.js: Read -node_modules/lodash/_arrayPush.js: Read -node_modules/lodash/_arrayReduce.js: Read -node_modules/lodash/_arraySome.js: Read -node_modules/lodash/_asciiSize.js: Read -node_modules/lodash/_asciiToArray.js: Read -node_modules/lodash/_assignMergeValue.js: Read -node_modules/lodash/_assignValue.js: Read -node_modules/lodash/_assocIndexOf.js: Read -node_modules/lodash/_baseAggregator.js: Read -node_modules/lodash/_baseAssign.js: Read -node_modules/lodash/_baseAssignIn.js: Read -node_modules/lodash/_baseAssignValue.js: Read -node_modules/lodash/_baseClamp.js: Read -node_modules/lodash/_baseClone.js: Read -node_modules/lodash/_baseCreate.js: Read -node_modules/lodash/_baseDifference.js: Read -node_modules/lodash/_baseEach.js: Read -node_modules/lodash/_baseFill.js: Read -node_modules/lodash/_baseFilter.js: Read -node_modules/lodash/_baseFindIndex.js: Read -node_modules/lodash/_baseFlatten.js: Read -node_modules/lodash/_baseFor.js: Read -node_modules/lodash/_baseForOwn.js: Read -node_modules/lodash/_baseGet.js: Read -node_modules/lodash/_baseGetAllKeys.js: Read -node_modules/lodash/_baseGetTag.js: Read -node_modules/lodash/_baseHasIn.js: Read -node_modules/lodash/_baseIndexOf.js: Read -node_modules/lodash/_baseIntersection.js: Read -node_modules/lodash/_baseIsArguments.js: Read -node_modules/lodash/_baseIsEqual.js: Read -node_modules/lodash/_baseIsEqualDeep.js: Read -node_modules/lodash/_baseIsMap.js: Read -node_modules/lodash/_baseIsMatch.js: Read -node_modules/lodash/_baseIsNaN.js: Read -node_modules/lodash/_baseIsNative.js: Read -node_modules/lodash/_baseIsSet.js: Read -node_modules/lodash/_baseIsTypedArray.js: Read -node_modules/lodash/_baseIteratee.js: Read -node_modules/lodash/_baseKeys.js: Read -node_modules/lodash/_baseKeysIn.js: Read -node_modules/lodash/_baseMap.js: Read -node_modules/lodash/_baseMatches.js: Read -node_modules/lodash/_baseMatchesProperty.js: Read -node_modules/lodash/_baseMerge.js: Read -node_modules/lodash/_baseMergeDeep.js: Read -node_modules/lodash/_baseOrderBy.js: Read -node_modules/lodash/_basePick.js: Read -node_modules/lodash/_basePickBy.js: Read -node_modules/lodash/_baseProperty.js: Read -node_modules/lodash/_basePropertyDeep.js: Read -node_modules/lodash/_basePropertyOf.js: Read -node_modules/lodash/_baseReduce.js: Read -node_modules/lodash/_baseRepeat.js: Read -node_modules/lodash/_baseRest.js: Read -node_modules/lodash/_baseSet.js: Read -node_modules/lodash/_baseSetToString.js: Read -node_modules/lodash/_baseSlice.js: Read -node_modules/lodash/_baseSome.js: Read -node_modules/lodash/_baseSortBy.js: Read -node_modules/lodash/_baseTimes.js: Read -node_modules/lodash/_baseToString.js: Read -node_modules/lodash/_baseTrim.js: Read -node_modules/lodash/_baseUnary.js: Read -node_modules/lodash/_baseUniq.js: Read -node_modules/lodash/_baseValues.js: Read -node_modules/lodash/_cacheHas.js: Read -node_modules/lodash/_castArrayLikeObject.js: Read -node_modules/lodash/_castFunction.js: Read -node_modules/lodash/_castPath.js: Read -node_modules/lodash/_castSlice.js: Read -node_modules/lodash/_charsEndIndex.js: Read -node_modules/lodash/_charsStartIndex.js: Read -node_modules/lodash/_cloneArrayBuffer.js: Read -node_modules/lodash/_cloneBuffer.js: Read -node_modules/lodash/_cloneDataView.js: Read -node_modules/lodash/_cloneRegExp.js: Read -node_modules/lodash/_cloneSymbol.js: Read -node_modules/lodash/_cloneTypedArray.js: Read -node_modules/lodash/_compareAscending.js: Read -node_modules/lodash/_compareMultiple.js: Read -node_modules/lodash/_copyArray.js: Read -node_modules/lodash/_copyObject.js: Read -node_modules/lodash/_copySymbols.js: Read -node_modules/lodash/_copySymbolsIn.js: Read -node_modules/lodash/_coreJsData.js: Read -node_modules/lodash/_createAggregator.js: Read -node_modules/lodash/_createAssigner.js: Read -node_modules/lodash/_createBaseEach.js: Read -node_modules/lodash/_createBaseFor.js: Read -node_modules/lodash/_createCaseFirst.js: Read -node_modules/lodash/_createFind.js: Read -node_modules/lodash/_createPadding.js: Read -node_modules/lodash/_createRound.js: Read -node_modules/lodash/_createSet.js: Read -node_modules/lodash/_customDefaultsAssignIn.js: Read -node_modules/lodash/_deburrLetter.js: Read -node_modules/lodash/_defineProperty.js: Read -node_modules/lodash/_equalArrays.js: Read -node_modules/lodash/_equalByTag.js: Read -node_modules/lodash/_equalObjects.js: Read -node_modules/lodash/_escapeHtmlChar.js: Read -node_modules/lodash/_escapeStringChar.js: Read -node_modules/lodash/_flatRest.js: Read -node_modules/lodash/_freeGlobal.js: Read -node_modules/lodash/_getAllKeys.js: Read -node_modules/lodash/_getAllKeysIn.js: Read -node_modules/lodash/_getMapData.js: Read -node_modules/lodash/_getMatchData.js: Read -node_modules/lodash/_getNative.js: Read -node_modules/lodash/_getPrototype.js: Read -node_modules/lodash/_getRawTag.js: Read -node_modules/lodash/_getSymbols.js: Read -node_modules/lodash/_getSymbolsIn.js: Read -node_modules/lodash/_getTag.js: Read -node_modules/lodash/_getValue.js: Read -node_modules/lodash/_hasPath.js: Read -node_modules/lodash/_hasUnicode.js: Read -node_modules/lodash/_hashClear.js: Read -node_modules/lodash/_hashDelete.js: Read -node_modules/lodash/_hashGet.js: Read -node_modules/lodash/_hashHas.js: Read -node_modules/lodash/_hashSet.js: Read -node_modules/lodash/_initCloneArray.js: Read -node_modules/lodash/_initCloneByTag.js: Read -node_modules/lodash/_initCloneObject.js: Read -node_modules/lodash/_isFlattenable.js: Read -node_modules/lodash/_isIndex.js: Read -node_modules/lodash/_isIterateeCall.js: Read -node_modules/lodash/_isKey.js: Read -node_modules/lodash/_isKeyable.js: Read -node_modules/lodash/_isMasked.js: Read -node_modules/lodash/_isPrototype.js: Read -node_modules/lodash/_isStrictComparable.js: Read -node_modules/lodash/_listCacheClear.js: Read -node_modules/lodash/_listCacheDelete.js: Read -node_modules/lodash/_listCacheGet.js: Read -node_modules/lodash/_listCacheHas.js: Read -node_modules/lodash/_listCacheSet.js: Read -node_modules/lodash/_mapCacheClear.js: Read -node_modules/lodash/_mapCacheDelete.js: Read -node_modules/lodash/_mapCacheGet.js: Read -node_modules/lodash/_mapCacheHas.js: Read -node_modules/lodash/_mapCacheSet.js: Read -node_modules/lodash/_mapToArray.js: Read -node_modules/lodash/_matchesStrictComparable.js: Read -node_modules/lodash/_memoizeCapped.js: Read -node_modules/lodash/_nativeCreate.js: Read -node_modules/lodash/_nativeKeys.js: Read -node_modules/lodash/_nativeKeysIn.js: Read -node_modules/lodash/_nodeUtil.js: Read -node_modules/lodash/_objectToString.js: Read -node_modules/lodash/_overArg.js: Read -node_modules/lodash/_overRest.js: Read -node_modules/lodash/_reEscape.js: Read -node_modules/lodash/_reEvaluate.js: Read -node_modules/lodash/_reInterpolate.js: Read -node_modules/lodash/_root.js: Read -node_modules/lodash/_safeGet.js: Read -node_modules/lodash/_setCacheAdd.js: Read -node_modules/lodash/_setCacheHas.js: Read -node_modules/lodash/_setToArray.js: Read -node_modules/lodash/_setToString.js: Read -node_modules/lodash/_shortOut.js: Read -node_modules/lodash/_stackClear.js: Read -node_modules/lodash/_stackDelete.js: Read -node_modules/lodash/_stackGet.js: Read -node_modules/lodash/_stackHas.js: Read -node_modules/lodash/_stackSet.js: Read -node_modules/lodash/_strictIndexOf.js: Read -node_modules/lodash/_stringSize.js: Read -node_modules/lodash/_stringToArray.js: Read -node_modules/lodash/_stringToPath.js: Read -node_modules/lodash/_toKey.js: Read -node_modules/lodash/_toSource.js: Read -node_modules/lodash/_trimmedEndIndex.js: Read -node_modules/lodash/_unicodeSize.js: Read -node_modules/lodash/_unicodeToArray.js: Read -node_modules/lodash/assignInWith.js: Read -node_modules/lodash/attempt.js: Read -node_modules/lodash/capitalize.js: Read -node_modules/lodash/chunk.js: Read -node_modules/lodash/cloneDeep.js: Read -node_modules/lodash/compact.js: Read -node_modules/lodash/concat.js: Read -node_modules/lodash/constant.js: Read -node_modules/lodash/debounce.js: Read -node_modules/lodash/deburr.js: Read -node_modules/lodash/difference.js: Read -node_modules/lodash/differenceBy.js: Read -node_modules/lodash/differenceWith.js: Read -node_modules/lodash/each.js: Read -node_modules/lodash/eq.js: Read -node_modules/lodash/escape.js: Read -node_modules/lodash/escapeRegExp.js: Read -node_modules/lodash/fill.js: Read -node_modules/lodash/filter.js: Read -node_modules/lodash/find.js: Read -node_modules/lodash/findIndex.js: Read -node_modules/lodash/flatten.js: Read -node_modules/lodash/flattenDeep.js: Read -node_modules/lodash/floor.js: Read -node_modules/lodash/forEach.js: Read -node_modules/lodash/forOwn.js: Read -node_modules/lodash/get.js: Read -node_modules/lodash/groupBy.js: Read -node_modules/lodash/hasIn.js: Read -node_modules/lodash/identity.js: Read -node_modules/lodash/includes.js: Read -node_modules/lodash/intersection.js: Read -node_modules/lodash/isArguments.js: Read -node_modules/lodash/isArray.js: Read -node_modules/lodash/isArrayLike.js: Read -node_modules/lodash/isArrayLikeObject.js: Read -node_modules/lodash/isBuffer.js: Read -node_modules/lodash/isEmpty.js: Read -node_modules/lodash/isEqual.js: Read -node_modules/lodash/isError.js: Read -node_modules/lodash/isFunction.js: Read -node_modules/lodash/isLength.js: Read -node_modules/lodash/isMap.js: Read -node_modules/lodash/isMatch.js: Read -node_modules/lodash/isNil.js: Read -node_modules/lodash/isNull.js: Read -node_modules/lodash/isObject.js: Read -node_modules/lodash/isObjectLike.js: Read -node_modules/lodash/isPlainObject.js: Read -node_modules/lodash/isSet.js: Read -node_modules/lodash/isString.js: Read -node_modules/lodash/isSymbol.js: Read -node_modules/lodash/isTypedArray.js: Read -node_modules/lodash/isUndefined.js: Read -node_modules/lodash/keyBy.js: Read -node_modules/lodash/keys.js: Read -node_modules/lodash/keysIn.js: Read -node_modules/lodash/last.js: Read -node_modules/lodash/lowerFirst.js: Read -node_modules/lodash/map.js: Read -node_modules/lodash/memoize.js: Read -node_modules/lodash/merge.js: Read -node_modules/lodash/negate.js: Read -node_modules/lodash/noop.js: Read -node_modules/lodash/now.js: Read -node_modules/lodash/omitBy.js: Read -node_modules/lodash/orderBy.js: Read -node_modules/lodash/package.json: Read -node_modules/lodash/padStart.js: Read -node_modules/lodash/pick.js: Read -node_modules/lodash/pickBy.js: Read -node_modules/lodash/property.js: Read -node_modules/lodash/reduce.js: Read -node_modules/lodash/some.js: Read -node_modules/lodash/sortBy.js: Read -node_modules/lodash/stubArray.js: Read -node_modules/lodash/stubFalse.js: Read -node_modules/lodash/template.js: Read -node_modules/lodash/templateSettings.js: Read -node_modules/lodash/throttle.js: Read -node_modules/lodash/times.js: Read -node_modules/lodash/toFinite.js: Read -node_modules/lodash/toInteger.js: Read -node_modules/lodash/toLength.js: Read -node_modules/lodash/toNumber.js: Read -node_modules/lodash/toPlainObject.js: Read -node_modules/lodash/toString.js: Read -node_modules/lodash/trim.js: Read -node_modules/lodash/uniq.js: Read -node_modules/lodash/uniqBy.js: Read -node_modules/lodash/upperFirst.js: Read -node_modules/lodash/values.js: Read -node_modules/magic-string/dist/magic-string.cjs.js: Read -node_modules/magic-string/dist/package.json: Read -node_modules/magic-string/package.json: Read -node_modules/markdown-it-container/index.js: Read -node_modules/markdown-it-container/package.json: Read -node_modules/markdown-it-emoji/index.mjs: Read -node_modules/markdown-it-emoji/lib/bare.mjs: Read -node_modules/markdown-it-emoji/lib/data/full.mjs: Read -node_modules/markdown-it-emoji/lib/data/light.mjs: Read -node_modules/markdown-it-emoji/lib/data/package.json: Read -node_modules/markdown-it-emoji/lib/data/shortcuts.mjs: Read -node_modules/markdown-it-emoji/lib/full.mjs: Read -node_modules/markdown-it-emoji/lib/light.mjs: Read -node_modules/markdown-it-emoji/lib/normalize_opts.mjs: Read -node_modules/markdown-it-emoji/lib/package.json: Read -node_modules/markdown-it-emoji/lib/render.mjs: Read -node_modules/markdown-it-emoji/lib/replace.mjs: Read -node_modules/markdown-it-emoji/package.json: Read -node_modules/markdown-it/index.mjs: Read -node_modules/markdown-it/lib/common/html_blocks.mjs: Read -node_modules/markdown-it/lib/common/html_re.mjs: Read -node_modules/markdown-it/lib/common/package.json: Read -node_modules/markdown-it/lib/common/utils.mjs: Read -node_modules/markdown-it/lib/helpers/index.mjs: Read -node_modules/markdown-it/lib/helpers/package.json: Read -node_modules/markdown-it/lib/helpers/parse_link_destination.mjs: Read -node_modules/markdown-it/lib/helpers/parse_link_label.mjs: Read -node_modules/markdown-it/lib/helpers/parse_link_title.mjs: Read -node_modules/markdown-it/lib/index.mjs: Read -node_modules/markdown-it/lib/package.json: Read -node_modules/markdown-it/lib/parser_block.mjs: Read -node_modules/markdown-it/lib/parser_core.mjs: Read -node_modules/markdown-it/lib/parser_inline.mjs: Read -node_modules/markdown-it/lib/presets/commonmark.mjs: Read -node_modules/markdown-it/lib/presets/default.mjs: Read -node_modules/markdown-it/lib/presets/package.json: Read -node_modules/markdown-it/lib/presets/zero.mjs: Read -node_modules/markdown-it/lib/renderer.mjs: Read -node_modules/markdown-it/lib/ruler.mjs: Read -node_modules/markdown-it/lib/rules_block/blockquote.mjs: Read -node_modules/markdown-it/lib/rules_block/code.mjs: Read -node_modules/markdown-it/lib/rules_block/fence.mjs: Read -node_modules/markdown-it/lib/rules_block/heading.mjs: Read -node_modules/markdown-it/lib/rules_block/hr.mjs: Read -node_modules/markdown-it/lib/rules_block/html_block.mjs: Read -node_modules/markdown-it/lib/rules_block/lheading.mjs: Read -node_modules/markdown-it/lib/rules_block/list.mjs: Read -node_modules/markdown-it/lib/rules_block/package.json: Read -node_modules/markdown-it/lib/rules_block/paragraph.mjs: Read -node_modules/markdown-it/lib/rules_block/reference.mjs: Read -node_modules/markdown-it/lib/rules_block/state_block.mjs: Read -node_modules/markdown-it/lib/rules_block/table.mjs: Read -node_modules/markdown-it/lib/rules_core/block.mjs: Read -node_modules/markdown-it/lib/rules_core/inline.mjs: Read -node_modules/markdown-it/lib/rules_core/linkify.mjs: Read -node_modules/markdown-it/lib/rules_core/normalize.mjs: Read -node_modules/markdown-it/lib/rules_core/package.json: Read -node_modules/markdown-it/lib/rules_core/replacements.mjs: Read -node_modules/markdown-it/lib/rules_core/smartquotes.mjs: Read -node_modules/markdown-it/lib/rules_core/state_core.mjs: Read -node_modules/markdown-it/lib/rules_core/text_join.mjs: Read -node_modules/markdown-it/lib/rules_inline/autolink.mjs: Read -node_modules/markdown-it/lib/rules_inline/backticks.mjs: Read -node_modules/markdown-it/lib/rules_inline/balance_pairs.mjs: Read -node_modules/markdown-it/lib/rules_inline/emphasis.mjs: Read -node_modules/markdown-it/lib/rules_inline/entity.mjs: Read -node_modules/markdown-it/lib/rules_inline/escape.mjs: Read -node_modules/markdown-it/lib/rules_inline/fragments_join.mjs: Read -node_modules/markdown-it/lib/rules_inline/html_inline.mjs: Read -node_modules/markdown-it/lib/rules_inline/image.mjs: Read -node_modules/markdown-it/lib/rules_inline/link.mjs: Read -node_modules/markdown-it/lib/rules_inline/linkify.mjs: Read -node_modules/markdown-it/lib/rules_inline/newline.mjs: Read -node_modules/markdown-it/lib/rules_inline/package.json: Read -node_modules/markdown-it/lib/rules_inline/state_inline.mjs: Read -node_modules/markdown-it/lib/rules_inline/strikethrough.mjs: Read -node_modules/markdown-it/lib/rules_inline/text.mjs: Read -node_modules/markdown-it/lib/token.mjs: Read -node_modules/markdown-it/node_modules/entities/package.json: Read -node_modules/markdown-it/node_modules/linkify-it/package.json: Read -node_modules/markdown-it/node_modules/mdurl/package.json: Read -node_modules/markdown-it/node_modules/package.json: Read -node_modules/markdown-it/node_modules/punycode.js/package.json: Read -node_modules/markdown-it/node_modules/uc.micro/package.json: Read -node_modules/markdown-it/package.json: Read -node_modules/marked/lib/marked.esm.js: Read -node_modules/marked/lib/package.json: Read -node_modules/marked/package.json: Read -node_modules/math-intrinsics/abs.js: Read -node_modules/math-intrinsics/constants/maxSafeInteger.js: Read -node_modules/math-intrinsics/constants/package.json: Read -node_modules/math-intrinsics/floor.js: Read -node_modules/math-intrinsics/isFinite.js: Read -node_modules/math-intrinsics/isInteger.js: Read -node_modules/math-intrinsics/isNaN.js: Read -node_modules/math-intrinsics/max.js: Read -node_modules/math-intrinsics/min.js: Read -node_modules/math-intrinsics/package.json: Read -node_modules/math-intrinsics/pow.js: Read -node_modules/math-intrinsics/round.js: Read -node_modules/math-intrinsics/sign.js: Read -node_modules/mdurl/index.mjs: Read -node_modules/mdurl/lib/decode.mjs: Read -node_modules/mdurl/lib/encode.mjs: Read -node_modules/mdurl/lib/format.mjs: Read -node_modules/mdurl/lib/package.json: Read -node_modules/mdurl/lib/parse.mjs: Read -node_modules/mdurl/package.json: Read -node_modules/memoize-one/dist/memoize-one.esm.js: Read -node_modules/memoize-one/dist/package.json: Read -node_modules/memoize-one/package.json: Read -node_modules/merge2/index.js: Read -node_modules/merge2/package.json: Read -node_modules/mermaid/dist/chunks/mermaid.core/architectureDiagram-SUXI7LT5.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/blockDiagram-6J76NXCF.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/c4Diagram-6F6E4RAY.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-353BL4L5.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-3XYRH5AP.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-55PJQP7W.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-67H74DCK.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-AACKK3MU.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-AC5SNWB5.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-BFAMUDN2.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-E2GYISFI.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-IWUHOULB.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-JW4RIYDF.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-L5ZGVLVO.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-M6DAPIYF.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-MXNHSMXR.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-OW32GOEJ.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-P3VETL53.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-QESNASVV.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-RKKUNAVE.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-SKB7J2MH.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-SZ463SBG.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/chunk-UWXLY5YG.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/classDiagram-M3E45YP4.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/classDiagram-v2-YAWTLIQI.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/dagre-JOIXM2OF.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/diagram-5UYTHUR4.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/diagram-VMROVX33.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/diagram-ZTM2IBQH.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/erDiagram-3M52JZNH.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/flowDiagram-KYDEHFYC.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/ganttDiagram-EK5VF46D.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/gitGraphDiagram-GW3U2K7C.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/infoDiagram-LHK5PUON.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/journeyDiagram-EWQZEKCU.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/kanban-definition-ZSS6B67P.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/mindmap-definition-6CBA2TL7.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/package.json: Read -node_modules/mermaid/dist/chunks/mermaid.core/pieDiagram-NIOCPIFQ.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/quadrantDiagram-2OG54O6I.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/requirementDiagram-QOLK2EJ7.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/sankeyDiagram-4UZDY2LN.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/sequenceDiagram-SKLFT4DO.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/stateDiagram-MI5ZYTHO.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/stateDiagram-v2-5AN5P6BG.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/timeline-definition-MYPXXCX6.mjs: Read -node_modules/mermaid/dist/chunks/mermaid.core/xychartDiagram-H2YORKM3.mjs: Read -node_modules/mermaid/dist/chunks/package.json: Read -node_modules/mermaid/dist/mermaid.core.mjs: Read -node_modules/mermaid/dist/package.json: Read -node_modules/mermaid/node_modules/cytoscape-cose-bilkent/package.json: Read -node_modules/mermaid/node_modules/cytoscape-fcose/package.json: Read -node_modules/mermaid/node_modules/cytoscape/package.json: Read -node_modules/mermaid/node_modules/d3-sankey/package.json: Read -node_modules/mermaid/node_modules/d3/package.json: Read -node_modules/mermaid/node_modules/dayjs/package.json: Read -node_modules/mermaid/node_modules/dompurify/package.json: Read -node_modules/mermaid/node_modules/katex/package.json: Read -node_modules/mermaid/node_modules/khroma/package.json: Read -node_modules/mermaid/node_modules/marked/package.json: Read -node_modules/mermaid/node_modules/package.json: Read -node_modules/mermaid/node_modules/roughjs/package.json: Read -node_modules/mermaid/node_modules/stylis/package.json: Read -node_modules/mermaid/node_modules/ts-dedent/package.json: Read -node_modules/mermaid/package.json: Read -node_modules/micromatch/index.js: Read -node_modules/micromatch/package.json: Read -node_modules/mime-types/index.js: Read -node_modules/mime-types/node_modules/mime-db/db.json: Read -node_modules/mime-types/node_modules/mime-db/index.js: Read -node_modules/mime-types/node_modules/mime-db/package.json: Read -node_modules/mime-types/node_modules/package.json: Read -node_modules/mime-types/node_modules/path/package.json: Read -node_modules/mime-types/package.json: Read -node_modules/minimatch/minimatch.js: Read -node_modules/minimatch/node_modules/balanced-match/package.json: Read -node_modules/minimatch/node_modules/brace-expansion/index.js: Read -node_modules/minimatch/node_modules/brace-expansion/package.json: Read -node_modules/minimatch/node_modules/concat-map/package.json: Read -node_modules/minimatch/package.json: Read -node_modules/mobx-react-lite/es/ObserverComponent.js: Read -node_modules/mobx-react-lite/es/assertEnvironment.js: Read -node_modules/mobx-react-lite/es/index.js: Read -node_modules/mobx-react-lite/es/observer.js: Read -node_modules/mobx-react-lite/es/observerBatching.js: Read -node_modules/mobx-react-lite/es/package.json: Read -node_modules/mobx-react-lite/es/printDebugValue.js: Read -node_modules/mobx-react-lite/es/reactionCleanupTracking.js: Read -node_modules/mobx-react-lite/es/staticRendering.js: Read -node_modules/mobx-react-lite/es/useAsObservableSource.js: Read -node_modules/mobx-react-lite/es/useLocalStore.js: Read -node_modules/mobx-react-lite/es/useObserver.js: Read -node_modules/mobx-react-lite/es/useQueuedForceUpdate.js: Read -node_modules/mobx-react-lite/es/utils.js: Read -node_modules/mobx-react-lite/es/utils/package.json: Read -node_modules/mobx-react-lite/es/utils/reactBatchedUpdates.js: Read -node_modules/mobx-react-lite/package.json: Read -node_modules/mobx-react/dist/mobxreact.esm.js: Read -node_modules/mobx-react/dist/package.json: Read -node_modules/mobx-react/package.json: Read -node_modules/mobx-utils/mobx-utils.module.js: Read -node_modules/mobx-utils/package.json: Read -node_modules/mobx/lib/mobx.module.js: Read -node_modules/mobx/lib/package.json: Read -node_modules/mobx/package.json: Read -node_modules/ms/index.js: Read -node_modules/ms/package.json: Read -node_modules/nanoid/non-secure/index.cjs: Read -node_modules/nanoid/package.json: Read -node_modules/natural-sort/dist/natural-sort.js: Read -node_modules/natural-sort/dist/package.json: Read -node_modules/natural-sort/package.json: Read -node_modules/node-releases/data/processed/envs.json: Read -node_modules/node-releases/data/release-schedule/release-schedule.json: Read -node_modules/node-releases/package.json: Read -node_modules/normalize-path/index.js: Read -node_modules/normalize-path/package.json: Read -node_modules/object-assign/index.js: Read -node_modules/object-assign/package.json: Read -node_modules/object-inspect/index.js: Read -node_modules/object-inspect/package.json: Read -node_modules/object-inspect/util.inspect.js: Read -node_modules/object-keys/index.js: Read -node_modules/object-keys/isArguments.js: Read -node_modules/object-keys/package.json: Read -node_modules/once/once.js: Read -node_modules/once/package.json: Read -node_modules/orderedmap/dist/index.js: Read -node_modules/orderedmap/dist/package.json: Read -node_modules/orderedmap/package.json: Read -node_modules/outline-icons/lib/components/AcademicCapIcon.js: Read -node_modules/outline-icons/lib/components/AlignCenterIcon.js: Read -node_modules/outline-icons/lib/components/AlignFullWidthIcon.js: Read -node_modules/outline-icons/lib/components/AlignImageCenterIcon.js: Read -node_modules/outline-icons/lib/components/AlignImageLeftIcon.js: Read -node_modules/outline-icons/lib/components/AlignImageRightIcon.js: Read -node_modules/outline-icons/lib/components/AlignLeftIcon.js: Read -node_modules/outline-icons/lib/components/AlignRightIcon.js: Read -node_modules/outline-icons/lib/components/AlphabeticalReverseSortIcon.js: Read -node_modules/outline-icons/lib/components/AlphabeticalSortIcon.js: Read -node_modules/outline-icons/lib/components/ArchiveIcon.js: Read -node_modules/outline-icons/lib/components/ArrowIcon.js: Read -node_modules/outline-icons/lib/components/AttachmentIcon.js: Read -node_modules/outline-icons/lib/components/BackIcon.js: Read -node_modules/outline-icons/lib/components/BeakerIcon.js: Read -node_modules/outline-icons/lib/components/BicycleIcon.js: Read -node_modules/outline-icons/lib/components/BillingIcon.js: Read -node_modules/outline-icons/lib/components/BlockQuoteIcon.js: Read -node_modules/outline-icons/lib/components/BoldIcon.js: Read -node_modules/outline-icons/lib/components/BookmarkIcon.js: Read -node_modules/outline-icons/lib/components/BookmarkedIcon.js: Read -node_modules/outline-icons/lib/components/BrowserIcon.js: Read -node_modules/outline-icons/lib/components/BugIcon.js: Read -node_modules/outline-icons/lib/components/BuildingBlocksIcon.js: Read -node_modules/outline-icons/lib/components/BulletedListIcon.js: Read -node_modules/outline-icons/lib/components/CalendarIcon.js: Read -node_modules/outline-icons/lib/components/CameraIcon.js: Read -node_modules/outline-icons/lib/components/CaretDownIcon.js: Read -node_modules/outline-icons/lib/components/CaretUpIcon.js: Read -node_modules/outline-icons/lib/components/CarrotIcon.js: Read -node_modules/outline-icons/lib/components/CaseSensitiveIcon.js: Read -node_modules/outline-icons/lib/components/CheckboxIcon.js: Read -node_modules/outline-icons/lib/components/CheckmarkIcon.js: Read -node_modules/outline-icons/lib/components/ClockIcon.js: Read -node_modules/outline-icons/lib/components/CloseIcon.js: Read -node_modules/outline-icons/lib/components/CloudIcon.js: Read -node_modules/outline-icons/lib/components/CodeIcon.js: Read -node_modules/outline-icons/lib/components/CoinsIcon.js: Read -node_modules/outline-icons/lib/components/CollapseIcon.js: Read -node_modules/outline-icons/lib/components/CollapsedIcon.js: Read -node_modules/outline-icons/lib/components/CollectionIcon.js: Read -node_modules/outline-icons/lib/components/CommentIcon.js: Read -node_modules/outline-icons/lib/components/CopyIcon.js: Read -node_modules/outline-icons/lib/components/CrossIcon.js: Read -node_modules/outline-icons/lib/components/DatabaseIcon.js: Read -node_modules/outline-icons/lib/components/DigitalIcon.js: Read -node_modules/outline-icons/lib/components/DisclosureIcon.js: Read -node_modules/outline-icons/lib/components/DisconnectedIcon.js: Read -node_modules/outline-icons/lib/components/DocumentIcon.js: Read -node_modules/outline-icons/lib/components/DoneIcon.js: Read -node_modules/outline-icons/lib/components/DownloadIcon.js: Read -node_modules/outline-icons/lib/components/DraftsIcon.js: Read -node_modules/outline-icons/lib/components/DuplicateIcon.js: Read -node_modules/outline-icons/lib/components/EditIcon.js: Read -node_modules/outline-icons/lib/components/EmailIcon.js: Read -node_modules/outline-icons/lib/components/EmbedIcon.js: Read -node_modules/outline-icons/lib/components/ExpandedIcon.js: Read -node_modules/outline-icons/lib/components/ExportIcon.js: Read -node_modules/outline-icons/lib/components/EyeIcon.js: Read -node_modules/outline-icons/lib/components/FeedbackIcon.js: Read -node_modules/outline-icons/lib/components/FlameIcon.js: Read -node_modules/outline-icons/lib/components/GlobeIcon.js: Read -node_modules/outline-icons/lib/components/GoToIcon.js: Read -node_modules/outline-icons/lib/components/GraphIcon.js: Read -node_modules/outline-icons/lib/components/GroupIcon.js: Read -node_modules/outline-icons/lib/components/HashtagIcon.js: Read -node_modules/outline-icons/lib/components/Heading1Icon.js: Read -node_modules/outline-icons/lib/components/Heading2Icon.js: Read -node_modules/outline-icons/lib/components/Heading3Icon.js: Read -node_modules/outline-icons/lib/components/Heading4Icon.js: Read -node_modules/outline-icons/lib/components/HighlightIcon.js: Read -node_modules/outline-icons/lib/components/HistoryIcon.js: Read -node_modules/outline-icons/lib/components/HomeIcon.js: Read -node_modules/outline-icons/lib/components/HorizontalRuleIcon.js: Read -node_modules/outline-icons/lib/components/IceCreamIcon.js: Read -node_modules/outline-icons/lib/components/Icon.js: Read -node_modules/outline-icons/lib/components/ImageIcon.js: Read -node_modules/outline-icons/lib/components/ImportIcon.js: Read -node_modules/outline-icons/lib/components/IndentIcon.js: Read -node_modules/outline-icons/lib/components/InfoIcon.js: Read -node_modules/outline-icons/lib/components/InputIcon.js: Read -node_modules/outline-icons/lib/components/InsertAboveIcon.js: Read -node_modules/outline-icons/lib/components/InsertBelowIcon.js: Read -node_modules/outline-icons/lib/components/InsertLeftIcon.js: Read -node_modules/outline-icons/lib/components/InsertRightIcon.js: Read -node_modules/outline-icons/lib/components/InternetIcon.js: Read -node_modules/outline-icons/lib/components/ItalicIcon.js: Read -node_modules/outline-icons/lib/components/JournalIcon.js: Read -node_modules/outline-icons/lib/components/KeyboardIcon.js: Read -node_modules/outline-icons/lib/components/LeafIcon.js: Read -node_modules/outline-icons/lib/components/LibraryIcon.js: Read -node_modules/outline-icons/lib/components/LightBulbIcon.js: Read -node_modules/outline-icons/lib/components/LightningIcon.js: Read -node_modules/outline-icons/lib/components/LinkIcon.js: Read -node_modules/outline-icons/lib/components/LogoutIcon.js: Read -node_modules/outline-icons/lib/components/ManualSortIcon.js: Read -node_modules/outline-icons/lib/components/MarkAsReadIcon.js: Read -node_modules/outline-icons/lib/components/MathIcon.js: Read -node_modules/outline-icons/lib/components/MenuIcon.js: Read -node_modules/outline-icons/lib/components/MoonIcon.js: Read -node_modules/outline-icons/lib/components/MoreIcon.js: Read -node_modules/outline-icons/lib/components/MoveIcon.js: Read -node_modules/outline-icons/lib/components/NewDocumentIcon.js: Read -node_modules/outline-icons/lib/components/NextIcon.js: Read -node_modules/outline-icons/lib/components/NotepadIcon.js: Read -node_modules/outline-icons/lib/components/OpenIcon.js: Read -node_modules/outline-icons/lib/components/OrderedListIcon.js: Read -node_modules/outline-icons/lib/components/OutdentIcon.js: Read -node_modules/outline-icons/lib/components/PadlockIcon.js: Read -node_modules/outline-icons/lib/components/PageBreakIcon.js: Read -node_modules/outline-icons/lib/components/PaletteIcon.js: Read -node_modules/outline-icons/lib/components/PinIcon.js: Read -node_modules/outline-icons/lib/components/PlaneIcon.js: Read -node_modules/outline-icons/lib/components/PlusIcon.js: Read -node_modules/outline-icons/lib/components/PrintIcon.js: Read -node_modules/outline-icons/lib/components/PrivateCollectionIcon.js: Read -node_modules/outline-icons/lib/components/ProfileIcon.js: Read -node_modules/outline-icons/lib/components/PromoteIcon.js: Read -node_modules/outline-icons/lib/components/PublishIcon.js: Read -node_modules/outline-icons/lib/components/QuestionMarkIcon.js: Read -node_modules/outline-icons/lib/components/RamenIcon.js: Read -node_modules/outline-icons/lib/components/ReactionIcon.js: Read -node_modules/outline-icons/lib/components/RegexIcon.js: Read -node_modules/outline-icons/lib/components/ReplaceIcon.js: Read -node_modules/outline-icons/lib/components/RestoreIcon.js: Read -node_modules/outline-icons/lib/components/SearchIcon.js: Read -node_modules/outline-icons/lib/components/ServerRackIcon.js: Read -node_modules/outline-icons/lib/components/SettingsIcon.js: Read -node_modules/outline-icons/lib/components/ShapesIcon.js: Read -node_modules/outline-icons/lib/components/ShuffleIcon.js: Read -node_modules/outline-icons/lib/components/SidebarIcon.js: Read -node_modules/outline-icons/lib/components/SmileyIcon.js: Read -node_modules/outline-icons/lib/components/SparklesIcon.js: Read -node_modules/outline-icons/lib/components/SportIcon.js: Read -node_modules/outline-icons/lib/components/StarredIcon.js: Read -node_modules/outline-icons/lib/components/StrikethroughIcon.js: Read -node_modules/outline-icons/lib/components/SubscribeIcon.js: Read -node_modules/outline-icons/lib/components/SunIcon.js: Read -node_modules/outline-icons/lib/components/TableHeaderColumnIcon.js: Read -node_modules/outline-icons/lib/components/TableHeaderRowIcon.js: Read -node_modules/outline-icons/lib/components/TableIcon.js: Read -node_modules/outline-icons/lib/components/TableMergeCellsIcon.js: Read -node_modules/outline-icons/lib/components/TableOfContentsIcon.js: Read -node_modules/outline-icons/lib/components/TableSplitCellsIcon.js: Read -node_modules/outline-icons/lib/components/TargetIcon.js: Read -node_modules/outline-icons/lib/components/TeamIcon.js: Read -node_modules/outline-icons/lib/components/TerminalIcon.js: Read -node_modules/outline-icons/lib/components/ThumbsDownIcon.js: Read -node_modules/outline-icons/lib/components/ThumbsUpIcon.js: Read -node_modules/outline-icons/lib/components/TodoListIcon.js: Read -node_modules/outline-icons/lib/components/ToolsIcon.js: Read -node_modules/outline-icons/lib/components/TrashIcon.js: Read -node_modules/outline-icons/lib/components/TruckIcon.js: Read -node_modules/outline-icons/lib/components/UnpublishIcon.js: Read -node_modules/outline-icons/lib/components/UnstarredIcon.js: Read -node_modules/outline-icons/lib/components/UnsubscribeIcon.js: Read -node_modules/outline-icons/lib/components/UserIcon.js: Read -node_modules/outline-icons/lib/components/VehicleIcon.js: Read -node_modules/outline-icons/lib/components/WarningIcon.js: Read -node_modules/outline-icons/lib/components/WebhooksIcon.js: Read -node_modules/outline-icons/lib/components/package.json: Read -node_modules/outline-icons/lib/index.js: Read -node_modules/outline-icons/lib/package.json: Read -node_modules/outline-icons/package.json: Read -node_modules/package.json: Read -node_modules/parse-entities/decode-entity.browser.js: Read -node_modules/parse-entities/index.js: Read -node_modules/parse-entities/package.json: Read -node_modules/path-is-absolute/index.js: Read -node_modules/path-is-absolute/package.json: Read -node_modules/path-to-regexp/index.js: Read -node_modules/path-to-regexp/node_modules/isarray/index.js: Read -node_modules/path-to-regexp/node_modules/isarray/package.json: Read -node_modules/path-to-regexp/node_modules/package.json: Read -node_modules/path-to-regexp/package.json: Read -node_modules/path/package.json: Read -node_modules/picocolors/package.json: Read -node_modules/picocolors/picocolors.js: Read -node_modules/picomatch/index.js: Read -node_modules/picomatch/lib/constants.js: Read -node_modules/picomatch/lib/package.json: Read -node_modules/picomatch/lib/parse.js: Read -node_modules/picomatch/lib/picomatch.js: Read -node_modules/picomatch/lib/scan.js: Read -node_modules/picomatch/lib/utils.js: Read -node_modules/picomatch/package.json: Read -node_modules/pluralize/package.json: Read -node_modules/pluralize/pluralize.js: Read -node_modules/png-chunks-extract/index.js: Read -node_modules/png-chunks-extract/package.json: Read -node_modules/polished/dist/package.json: Read -node_modules/polished/dist/polished.esm.js: Read -node_modules/polished/package.json: Read -node_modules/popmotion/dist/es/animations/generators/decay.js: Read -node_modules/popmotion/dist/es/animations/generators/keyframes.js: Read -node_modules/popmotion/dist/es/animations/generators/package.json: Read -node_modules/popmotion/dist/es/animations/generators/spring.js: Read -node_modules/popmotion/dist/es/animations/index.js: Read -node_modules/popmotion/dist/es/animations/inertia.js: Read -node_modules/popmotion/dist/es/animations/package.json: Read -node_modules/popmotion/dist/es/animations/utils/detect-animation-from-options.js: Read -node_modules/popmotion/dist/es/animations/utils/elapsed.js: Read -node_modules/popmotion/dist/es/animations/utils/find-spring.js: Read -node_modules/popmotion/dist/es/animations/utils/package.json: Read -node_modules/popmotion/dist/es/easing/cubic-bezier.js: Read -node_modules/popmotion/dist/es/easing/index.js: Read -node_modules/popmotion/dist/es/easing/package.json: Read -node_modules/popmotion/dist/es/easing/steps.js: Read -node_modules/popmotion/dist/es/easing/utils.js: Read -node_modules/popmotion/dist/es/index.js: Read -node_modules/popmotion/dist/es/package.json: Read -node_modules/popmotion/dist/es/utils/angle.js: Read -node_modules/popmotion/dist/es/utils/apply-offset.js: Read -node_modules/popmotion/dist/es/utils/attract.js: Read -node_modules/popmotion/dist/es/utils/clamp.js: Read -node_modules/popmotion/dist/es/utils/degrees-to-radians.js: Read -node_modules/popmotion/dist/es/utils/distance.js: Read -node_modules/popmotion/dist/es/utils/inc.js: Read -node_modules/popmotion/dist/es/utils/interpolate.js: Read -node_modules/popmotion/dist/es/utils/is-point-3d.js: Read -node_modules/popmotion/dist/es/utils/is-point.js: Read -node_modules/popmotion/dist/es/utils/mix-color.js: Read -node_modules/popmotion/dist/es/utils/mix-complex.js: Read -node_modules/popmotion/dist/es/utils/mix.js: Read -node_modules/popmotion/dist/es/utils/package.json: Read -node_modules/popmotion/dist/es/utils/pipe.js: Read -node_modules/popmotion/dist/es/utils/point-from-vector.js: Read -node_modules/popmotion/dist/es/utils/progress.js: Read -node_modules/popmotion/dist/es/utils/radians-to-degrees.js: Read -node_modules/popmotion/dist/es/utils/smooth-frame.js: Read -node_modules/popmotion/dist/es/utils/smooth.js: Read -node_modules/popmotion/dist/es/utils/snap.js: Read -node_modules/popmotion/dist/es/utils/to-decimal.js: Read -node_modules/popmotion/dist/es/utils/velocity-per-frame.js: Read -node_modules/popmotion/dist/es/utils/velocity-per-second.js: Read -node_modules/popmotion/dist/es/utils/wrap.js: Read -node_modules/popmotion/dist/package.json: Read -node_modules/popmotion/package.json: Read -node_modules/postcss/lib/at-rule.js: Read -node_modules/postcss/lib/comment.js: Read -node_modules/postcss/lib/container.js: Read -node_modules/postcss/lib/css-syntax-error.js: Read -node_modules/postcss/lib/declaration.js: Read -node_modules/postcss/lib/document.js: Read -node_modules/postcss/lib/fromJSON.js: Read -node_modules/postcss/lib/input.js: Read -node_modules/postcss/lib/lazy-result.js: Read -node_modules/postcss/lib/list.js: Read -node_modules/postcss/lib/map-generator.js: Read -node_modules/postcss/lib/no-work-result.js: Read -node_modules/postcss/lib/node.js: Read -node_modules/postcss/lib/package.json: Read -node_modules/postcss/lib/parse.js: Read -node_modules/postcss/lib/parser.js: Read -node_modules/postcss/lib/postcss.js: Read -node_modules/postcss/lib/postcss.mjs: Read -node_modules/postcss/lib/previous-map.js: Read -node_modules/postcss/lib/processor.js: Read -node_modules/postcss/lib/result.js: Read -node_modules/postcss/lib/root.js: Read -node_modules/postcss/lib/rule.js: Read -node_modules/postcss/lib/stringifier.js: Read -node_modules/postcss/lib/stringify.js: Read -node_modules/postcss/lib/symbols.js: Read -node_modules/postcss/lib/terminal-highlight.js: Read -node_modules/postcss/lib/tokenize.js: Read -node_modules/postcss/lib/warn-once.js: Read -node_modules/postcss/lib/warning.js: Read -node_modules/postcss/node_modules/nanoid/package.json: Read -node_modules/postcss/node_modules/picocolors/package.json: Read -node_modules/postcss/node_modules/source-map-js/package.json: Read -node_modules/postcss/package.json: Read -node_modules/prismjs/components/package.json: Read -node_modules/prismjs/components/prism-core.js: Read -node_modules/prismjs/package.json: Read -node_modules/prop-types/factoryWithThrowingShims.js: Read -node_modules/prop-types/index.js: Read -node_modules/prop-types/lib/ReactPropTypesSecret.js: Read -node_modules/prop-types/lib/package.json: Read -node_modules/prop-types/package.json: Read -node_modules/property-information/find.js: Read -node_modules/property-information/html.js: Read -node_modules/property-information/lib/aria.js: Read -node_modules/property-information/lib/html.js: Read -node_modules/property-information/lib/package.json: Read -node_modules/property-information/lib/util/case-insensitive-transform.js: Read -node_modules/property-information/lib/util/case-sensitive-transform.js: Read -node_modules/property-information/lib/util/create.js: Read -node_modules/property-information/lib/util/defined-info.js: Read -node_modules/property-information/lib/util/info.js: Read -node_modules/property-information/lib/util/merge.js: Read -node_modules/property-information/lib/util/package.json: Read -node_modules/property-information/lib/util/schema.js: Read -node_modules/property-information/lib/util/types.js: Read -node_modules/property-information/lib/xlink.js: Read -node_modules/property-information/lib/xml.js: Read -node_modules/property-information/lib/xmlns.js: Read -node_modules/property-information/normalize.js: Read -node_modules/property-information/package.json: Read -node_modules/prosemirror-codemark/dist/esm/actions.js: Read -node_modules/prosemirror-codemark/dist/esm/index.js: Read -node_modules/prosemirror-codemark/dist/esm/inputRules.js: Read -node_modules/prosemirror-codemark/dist/esm/package.json: Read -node_modules/prosemirror-codemark/dist/esm/plugin.js: Read -node_modules/prosemirror-codemark/dist/esm/types.js: Read -node_modules/prosemirror-codemark/dist/esm/utils.js: Read -node_modules/prosemirror-codemark/dist/package.json: Read -node_modules/prosemirror-codemark/package.json: Read -node_modules/prosemirror-commands/dist/index.js: Read -node_modules/prosemirror-commands/dist/package.json: Read -node_modules/prosemirror-commands/package.json: Read -node_modules/prosemirror-dropcursor/dist/index.js: Read -node_modules/prosemirror-dropcursor/dist/package.json: Read -node_modules/prosemirror-dropcursor/package.json: Read -node_modules/prosemirror-gapcursor/dist/index.js: Read -node_modules/prosemirror-gapcursor/dist/package.json: Read -node_modules/prosemirror-gapcursor/package.json: Read -node_modules/prosemirror-history/dist/index.js: Read -node_modules/prosemirror-history/dist/package.json: Read -node_modules/prosemirror-history/package.json: Read -node_modules/prosemirror-inputrules/dist/index.js: Read -node_modules/prosemirror-inputrules/dist/package.json: Read -node_modules/prosemirror-inputrules/package.json: Read -node_modules/prosemirror-keymap/dist/index.js: Read -node_modules/prosemirror-keymap/dist/package.json: Read -node_modules/prosemirror-keymap/package.json: Read -node_modules/prosemirror-markdown/dist/index.js: Read -node_modules/prosemirror-markdown/dist/package.json: Read -node_modules/prosemirror-markdown/node_modules/markdown-it/package.json: Read -node_modules/prosemirror-markdown/node_modules/package.json: Read -node_modules/prosemirror-markdown/node_modules/prosemirror-model/package.json: Read -node_modules/prosemirror-markdown/package.json: Read -node_modules/prosemirror-model/dist/index.js: Read -node_modules/prosemirror-model/dist/package.json: Read -node_modules/prosemirror-model/package.json: Read -node_modules/prosemirror-schema-list/dist/index.js: Read -node_modules/prosemirror-schema-list/dist/package.json: Read -node_modules/prosemirror-schema-list/package.json: Read -node_modules/prosemirror-state/dist/index.js: Read -node_modules/prosemirror-state/dist/package.json: Read -node_modules/prosemirror-state/package.json: Read -node_modules/prosemirror-tables/dist/index.js: Read -node_modules/prosemirror-tables/dist/package.json: Read -node_modules/prosemirror-tables/package.json: Read -node_modules/prosemirror-transform/dist/index.js: Read -node_modules/prosemirror-transform/dist/package.json: Read -node_modules/prosemirror-transform/package.json: Read -node_modules/prosemirror-view/dist/index.js: Read -node_modules/prosemirror-view/dist/package.json: Read -node_modules/prosemirror-view/package.json: Read -node_modules/punycode.js/package.json: Read -node_modules/punycode.js/punycode.es6.js: Read -node_modules/query-string/index.js: Read -node_modules/query-string/package.json: Read -node_modules/queue-microtask/index.js: Read -node_modules/queue-microtask/package.json: Read -node_modules/randombytes/index.js: Read -node_modules/randombytes/package.json: Read -node_modules/react-avatar-editor/dist/index.js: Read -node_modules/react-avatar-editor/dist/package.json: Read -node_modules/react-avatar-editor/package.json: Read -node_modules/react-color/lib/components/chrome/Chrome.js: Read -node_modules/react-color/lib/components/chrome/ChromeFields.js: Read -node_modules/react-color/lib/components/chrome/ChromePointer.js: Read -node_modules/react-color/lib/components/chrome/ChromePointerCircle.js: Read -node_modules/react-color/lib/components/chrome/package.json: Read -node_modules/react-color/lib/components/common/Alpha.js: Read -node_modules/react-color/lib/components/common/Checkboard.js: Read -node_modules/react-color/lib/components/common/ColorWrap.js: Read -node_modules/react-color/lib/components/common/EditableInput.js: Read -node_modules/react-color/lib/components/common/Hue.js: Read -node_modules/react-color/lib/components/common/Raised.js: Read -node_modules/react-color/lib/components/common/Saturation.js: Read -node_modules/react-color/lib/components/common/Swatch.js: Read -node_modules/react-color/lib/components/common/index.js: Read -node_modules/react-color/lib/components/common/package.json: Read -node_modules/react-color/lib/components/package.json: Read -node_modules/react-color/lib/helpers/alpha.js: Read -node_modules/react-color/lib/helpers/checkboard.js: Read -node_modules/react-color/lib/helpers/color.js: Read -node_modules/react-color/lib/helpers/hue.js: Read -node_modules/react-color/lib/helpers/interaction.js: Read -node_modules/react-color/lib/helpers/package.json: Read -node_modules/react-color/lib/helpers/saturation.js: Read -node_modules/react-color/lib/package.json: Read -node_modules/react-color/package.json: Read -node_modules/react-day-picker/dist/index.esm.js: Read -node_modules/react-day-picker/dist/package.json: Read -node_modules/react-day-picker/dist/style.css: Read -node_modules/react-day-picker/package.json: Read -node_modules/react-dnd-html5-backend/dist/BrowserDetector.js: Read -node_modules/react-dnd-html5-backend/dist/EnterLeaveCounter.js: Read -node_modules/react-dnd-html5-backend/dist/HTML5BackendImpl.js: Read -node_modules/react-dnd-html5-backend/dist/MonotonicInterpolant.js: Read -node_modules/react-dnd-html5-backend/dist/NativeDragSources/NativeDragSource.js: Read -node_modules/react-dnd-html5-backend/dist/NativeDragSources/getDataFromDataTransfer.js: Read -node_modules/react-dnd-html5-backend/dist/NativeDragSources/index.js: Read -node_modules/react-dnd-html5-backend/dist/NativeDragSources/nativeTypesConfig.js: Read -node_modules/react-dnd-html5-backend/dist/NativeDragSources/package.json: Read -node_modules/react-dnd-html5-backend/dist/NativeTypes.js: Read -node_modules/react-dnd-html5-backend/dist/OffsetUtils.js: Read -node_modules/react-dnd-html5-backend/dist/OptionsReader.js: Read -node_modules/react-dnd-html5-backend/dist/getEmptyImage.js: Read -node_modules/react-dnd-html5-backend/dist/index.js: Read -node_modules/react-dnd-html5-backend/dist/package.json: Read -node_modules/react-dnd-html5-backend/dist/utils/js_utils.js: Read -node_modules/react-dnd-html5-backend/dist/utils/package.json: Read -node_modules/react-dnd-html5-backend/package.json: Read -node_modules/react-dnd/dist/core/DndContext.js: Read -node_modules/react-dnd/dist/core/DndProvider.js: Read -node_modules/react-dnd/dist/core/DragPreviewImage.js: Read -node_modules/react-dnd/dist/core/index.js: Read -node_modules/react-dnd/dist/core/package.json: Read -node_modules/react-dnd/dist/hooks/index.js: Read -node_modules/react-dnd/dist/hooks/package.json: Read -node_modules/react-dnd/dist/hooks/types.js: Read -node_modules/react-dnd/dist/hooks/useCollectedProps.js: Read -node_modules/react-dnd/dist/hooks/useCollector.js: Read -node_modules/react-dnd/dist/hooks/useDrag/DragSourceImpl.js: Read -node_modules/react-dnd/dist/hooks/useDrag/connectors.js: Read -node_modules/react-dnd/dist/hooks/useDrag/index.js: Read -node_modules/react-dnd/dist/hooks/useDrag/package.json: Read -node_modules/react-dnd/dist/hooks/useDrag/useDrag.js: Read -node_modules/react-dnd/dist/hooks/useDrag/useDragSource.js: Read -node_modules/react-dnd/dist/hooks/useDrag/useDragSourceConnector.js: Read -node_modules/react-dnd/dist/hooks/useDrag/useDragSourceMonitor.js: Read -node_modules/react-dnd/dist/hooks/useDrag/useDragType.js: Read -node_modules/react-dnd/dist/hooks/useDrag/useRegisteredDragSource.js: Read -node_modules/react-dnd/dist/hooks/useDragDropManager.js: Read -node_modules/react-dnd/dist/hooks/useDragLayer.js: Read -node_modules/react-dnd/dist/hooks/useDrop/DropTargetImpl.js: Read -node_modules/react-dnd/dist/hooks/useDrop/connectors.js: Read -node_modules/react-dnd/dist/hooks/useDrop/index.js: Read -node_modules/react-dnd/dist/hooks/useDrop/package.json: Read -node_modules/react-dnd/dist/hooks/useDrop/useAccept.js: Read -node_modules/react-dnd/dist/hooks/useDrop/useDrop.js: Read -node_modules/react-dnd/dist/hooks/useDrop/useDropTarget.js: Read -node_modules/react-dnd/dist/hooks/useDrop/useDropTargetConnector.js: Read -node_modules/react-dnd/dist/hooks/useDrop/useDropTargetMonitor.js: Read -node_modules/react-dnd/dist/hooks/useDrop/useRegisteredDropTarget.js: Read -node_modules/react-dnd/dist/hooks/useIsomorphicLayoutEffect.js: Read -node_modules/react-dnd/dist/hooks/useMonitorOutput.js: Read -node_modules/react-dnd/dist/hooks/useOptionalFactory.js: Read -node_modules/react-dnd/dist/index.js: Read -node_modules/react-dnd/dist/internals/DragSourceMonitorImpl.js: Read -node_modules/react-dnd/dist/internals/DropTargetMonitorImpl.js: Read -node_modules/react-dnd/dist/internals/SourceConnector.js: Read -node_modules/react-dnd/dist/internals/TargetConnector.js: Read -node_modules/react-dnd/dist/internals/index.js: Read -node_modules/react-dnd/dist/internals/isRef.js: Read -node_modules/react-dnd/dist/internals/package.json: Read -node_modules/react-dnd/dist/internals/registration.js: Read -node_modules/react-dnd/dist/internals/wrapConnectorHooks.js: Read -node_modules/react-dnd/dist/package.json: Read -node_modules/react-dnd/dist/types/connectors.js: Read -node_modules/react-dnd/dist/types/index.js: Read -node_modules/react-dnd/dist/types/monitors.js: Read -node_modules/react-dnd/dist/types/options.js: Read -node_modules/react-dnd/dist/types/package.json: Read -node_modules/react-dnd/package.json: Read -node_modules/react-dom/cjs/package.json: Read -node_modules/react-dom/cjs/react-dom.production.min.js: Read -node_modules/react-dom/index.js: Read -node_modules/react-dom/node_modules/object-assign/package.json: Read -node_modules/react-dom/node_modules/package.json: Read -node_modules/react-dom/node_modules/react/package.json: Read -node_modules/react-dom/node_modules/scheduler/package.json: Read -node_modules/react-dom/package.json: Read -node_modules/react-dropzone/dist/es/index.js: Read -node_modules/react-dropzone/dist/es/package.json: Read -node_modules/react-dropzone/dist/es/utils/index.js: Read -node_modules/react-dropzone/dist/es/utils/package.json: Read -node_modules/react-dropzone/dist/package.json: Read -node_modules/react-dropzone/package.json: Read -node_modules/react-fast-compare/index.js: Read -node_modules/react-fast-compare/package.json: Read -node_modules/react-helmet-async/lib/index.esm.js: Read -node_modules/react-helmet-async/lib/package.json: Read -node_modules/react-helmet-async/package.json: Read -node_modules/react-hook-form/dist/index.esm.mjs: Read -node_modules/react-hook-form/dist/package.json: Read -node_modules/react-hook-form/package.json: Read -node_modules/react-i18next/dist/es/I18nextProvider.js: Read -node_modules/react-i18next/dist/es/Trans.js: Read -node_modules/react-i18next/dist/es/TransWithoutContext.js: Read -node_modules/react-i18next/dist/es/Translation.js: Read -node_modules/react-i18next/dist/es/context.js: Read -node_modules/react-i18next/dist/es/defaults.js: Read -node_modules/react-i18next/dist/es/i18nInstance.js: Read -node_modules/react-i18next/dist/es/index.js: Read -node_modules/react-i18next/dist/es/initReactI18next.js: Read -node_modules/react-i18next/dist/es/package.json: Read -node_modules/react-i18next/dist/es/unescape.js: Read -node_modules/react-i18next/dist/es/useSSR.js: Read -node_modules/react-i18next/dist/es/useTranslation.js: Read -node_modules/react-i18next/dist/es/utils.js: Read -node_modules/react-i18next/dist/es/withSSR.js: Read -node_modules/react-i18next/dist/es/withTranslation.js: Read -node_modules/react-i18next/dist/package.json: Read -node_modules/react-i18next/package.json: Read -node_modules/react-is/cjs/package.json: Read -node_modules/react-is/cjs/react-is.production.min.js: Read -node_modules/react-is/index.js: Read -node_modules/react-is/package.json: Read -node_modules/react-medium-image-zoom/dist/index.js: Read -node_modules/react-medium-image-zoom/dist/package.json: Read -node_modules/react-medium-image-zoom/package.json: Read -node_modules/react-merge-refs/dist/index.mjs: Read -node_modules/react-merge-refs/dist/package.json: Read -node_modules/react-merge-refs/package.json: Read -node_modules/react-portal/es/LegacyPortal.js: Read -node_modules/react-portal/es/Portal.js: Read -node_modules/react-portal/es/PortalCompat.js: Read -node_modules/react-portal/es/PortalWithState.js: Read -node_modules/react-portal/es/index.js: Read -node_modules/react-portal/es/package.json: Read -node_modules/react-portal/es/utils.js: Read -node_modules/react-portal/package.json: Read -node_modules/react-remove-scroll-bar/constants/package.json: Read -node_modules/react-remove-scroll-bar/dist/es2015/component.js: Read -node_modules/react-remove-scroll-bar/dist/es2015/constants.js: Read -node_modules/react-remove-scroll-bar/dist/es2015/index.js: Read -node_modules/react-remove-scroll-bar/dist/es2015/package.json: Read -node_modules/react-remove-scroll-bar/dist/es2015/utils.js: Read -node_modules/react-remove-scroll-bar/dist/package.json: Read -node_modules/react-remove-scroll-bar/package.json: Read -node_modules/react-remove-scroll/dist/es2015/Combination.js: Read -node_modules/react-remove-scroll/dist/es2015/SideEffect.js: Read -node_modules/react-remove-scroll/dist/es2015/UI.js: Read -node_modules/react-remove-scroll/dist/es2015/aggresiveCapture.js: Read -node_modules/react-remove-scroll/dist/es2015/handleScroll.js: Read -node_modules/react-remove-scroll/dist/es2015/index.js: Read -node_modules/react-remove-scroll/dist/es2015/medium.js: Read -node_modules/react-remove-scroll/dist/es2015/package.json: Read -node_modules/react-remove-scroll/dist/es2015/sidecar.js: Read -node_modules/react-remove-scroll/dist/package.json: Read -node_modules/react-remove-scroll/package.json: Read -node_modules/react-router-dom/esm/package.json: Read -node_modules/react-router-dom/esm/react-router-dom.js: Read -node_modules/react-router-dom/node_modules/history/package.json: Read -node_modules/react-router-dom/node_modules/package.json: Read -node_modules/react-router-dom/node_modules/prop-types/package.json: Read -node_modules/react-router-dom/node_modules/react-router/package.json: Read -node_modules/react-router-dom/node_modules/react/package.json: Read -node_modules/react-router-dom/node_modules/tiny-invariant/package.json: Read -node_modules/react-router-dom/node_modules/tiny-warning/package.json: Read -node_modules/react-router-dom/package.json: Read -node_modules/react-router/esm/package.json: Read -node_modules/react-router/esm/react-router.js: Read -node_modules/react-router/node_modules/history/package.json: Read -node_modules/react-router/node_modules/hoist-non-react-statics/package.json: Read -node_modules/react-router/node_modules/package.json: Read -node_modules/react-router/node_modules/path-to-regexp/package.json: Read -node_modules/react-router/node_modules/prop-types/package.json: Read -node_modules/react-router/node_modules/react-is/package.json: Read -node_modules/react-router/node_modules/react/package.json: Read -node_modules/react-router/node_modules/tiny-invariant/package.json: Read -node_modules/react-router/node_modules/tiny-warning/package.json: Read -node_modules/react-router/package.json: Read -node_modules/react-style-singleton/dist/es2015/component.js: Read -node_modules/react-style-singleton/dist/es2015/hook.js: Read -node_modules/react-style-singleton/dist/es2015/index.js: Read -node_modules/react-style-singleton/dist/es2015/package.json: Read -node_modules/react-style-singleton/dist/es2015/singleton.js: Read -node_modules/react-style-singleton/dist/package.json: Read -node_modules/react-style-singleton/package.json: Read -node_modules/react-use-measure/dist/index.js: Read -node_modules/react-use-measure/dist/package.json: Read -node_modules/react-use-measure/package.json: Read -node_modules/react-virtual/dist/package.json: Read -node_modules/react-virtual/dist/react-virtual.mjs: Read -node_modules/react-virtual/package.json: Read -node_modules/react-virtualized-auto-sizer/dist/package.json: Read -node_modules/react-virtualized-auto-sizer/dist/react-virtualized-auto-sizer.esm.js: Read -node_modules/react-virtualized-auto-sizer/package.json: Read -node_modules/react-waypoint/es/index.js: Read -node_modules/react-waypoint/es/package.json: Read -node_modules/react-waypoint/node_modules/consolidated-events/package.json: Read -node_modules/react-waypoint/node_modules/package.json: Read -node_modules/react-waypoint/node_modules/prop-types/package.json: Read -node_modules/react-waypoint/node_modules/react-is/cjs/package.json: Read -node_modules/react-waypoint/node_modules/react-is/cjs/react-is.production.min.js: Read -node_modules/react-waypoint/node_modules/react-is/index.js: Read -node_modules/react-waypoint/node_modules/react-is/package.json: Read -node_modules/react-waypoint/node_modules/react/package.json: Read -node_modules/react-waypoint/package.json: Read -node_modules/react-window/dist/index.esm.js: Read -node_modules/react-window/dist/package.json: Read -node_modules/react-window/package.json: Read -node_modules/react/cjs/package.json: Read -node_modules/react/cjs/react-jsx-runtime.production.min.js: Read -node_modules/react/cjs/react.production.min.js: Read -node_modules/react/index.js: Read -node_modules/react/jsx-runtime.js: Read -node_modules/react/node_modules/object-assign/package.json: Read -node_modules/react/node_modules/package.json: Read -node_modules/react/node_modules/react/package.json: Read -node_modules/react/package.json: Read -node_modules/reactcss/lib/autoprefix.js: Read -node_modules/reactcss/lib/components/active.js: Read -node_modules/reactcss/lib/components/hover.js: Read -node_modules/reactcss/lib/components/package.json: Read -node_modules/reactcss/lib/flattenNames.js: Read -node_modules/reactcss/lib/index.js: Read -node_modules/reactcss/lib/loop.js: Read -node_modules/reactcss/lib/mergeClasses.js: Read -node_modules/reactcss/lib/package.json: Read -node_modules/reactcss/package.json: Read -node_modules/readdirp/index.js: Read -node_modules/readdirp/package.json: Read -node_modules/reakit-system/SystemProvider/package.json: Read -node_modules/reakit-system/createComponent/package.json: Read -node_modules/reakit-system/createHook/package.json: Read -node_modules/reakit-system/es/SystemContext.js: Read -node_modules/reakit-system/es/SystemProvider.js: Read -node_modules/reakit-system/es/_rollupPluginBabelHelpers-0c84a174.js: Read -node_modules/reakit-system/es/createComponent.js: Read -node_modules/reakit-system/es/createHook.js: Read -node_modules/reakit-system/es/package.json: Read -node_modules/reakit-system/es/useCreateElement.js: Read -node_modules/reakit-system/es/useOptions.js: Read -node_modules/reakit-system/es/useProps.js: Read -node_modules/reakit-system/es/useToken.js: Read -node_modules/reakit-system/package.json: Read -node_modules/reakit-system/useCreateElement/package.json: Read -node_modules/reakit-utils/applyState/package.json: Read -node_modules/reakit-utils/canUseDOM/package.json: Read -node_modules/reakit-utils/closest/package.json: Read -node_modules/reakit-utils/contains/package.json: Read -node_modules/reakit-utils/createEvent/package.json: Read -node_modules/reakit-utils/createOnKeyDown/package.json: Read -node_modules/reakit-utils/dom/package.json: Read -node_modules/reakit-utils/ensureFocus/package.json: Read -node_modules/reakit-utils/es/_rollupPluginBabelHelpers-1f0bf8c2.js: Read -node_modules/reakit-utils/es/applyState.js: Read -node_modules/reakit-utils/es/canUseDOM.js: Read -node_modules/reakit-utils/es/closest.js: Read -node_modules/reakit-utils/es/contains.js: Read -node_modules/reakit-utils/es/createEvent.js: Read -node_modules/reakit-utils/es/createOnKeyDown.js: Read -node_modules/reakit-utils/es/dom.js: Read -node_modules/reakit-utils/es/ensureFocus.js: Read -node_modules/reakit-utils/es/fireBlurEvent.js: Read -node_modules/reakit-utils/es/fireEvent.js: Read -node_modules/reakit-utils/es/fireKeyboardEvent.js: Read -node_modules/reakit-utils/es/flatten.js: Read -node_modules/reakit-utils/es/getActiveElement.js: Read -node_modules/reakit-utils/es/getDocument.js: Read -node_modules/reakit-utils/es/getNextActiveElementOnBlur.js: Read -node_modules/reakit-utils/es/getWindow.js: Read -node_modules/reakit-utils/es/hasFocus.js: Read -node_modules/reakit-utils/es/hasFocusWithin.js: Read -node_modules/reakit-utils/es/isButton.js: Read -node_modules/reakit-utils/es/isEmpty.js: Read -node_modules/reakit-utils/es/isInteger.js: Read -node_modules/reakit-utils/es/isObject.js: Read -node_modules/reakit-utils/es/isPlainObject.js: Read -node_modules/reakit-utils/es/isPortalEvent.js: Read -node_modules/reakit-utils/es/isPromise.js: Read -node_modules/reakit-utils/es/isSelfTarget.js: Read -node_modules/reakit-utils/es/isTextField.js: Read -node_modules/reakit-utils/es/matches.js: Read -node_modules/reakit-utils/es/normalizePropsAreEqual.js: Read -node_modules/reakit-utils/es/package.json: Read -node_modules/reakit-utils/es/removeIndexFromArray.js: Read -node_modules/reakit-utils/es/removeItemFromArray.js: Read -node_modules/reakit-utils/es/shallowEqual.js: Read -node_modules/reakit-utils/es/splitProps.js: Read -node_modules/reakit-utils/es/tabbable.js: Read -node_modules/reakit-utils/es/toArray.js: Read -node_modules/reakit-utils/es/useForkRef.js: Read -node_modules/reakit-utils/es/useIsomorphicEffect.js: Read -node_modules/reakit-utils/es/useLiveRef.js: Read -node_modules/reakit-utils/es/useSealedState.js: Read -node_modules/reakit-utils/es/useUpdateEffect.js: Read -node_modules/reakit-utils/fireBlurEvent/package.json: Read -node_modules/reakit-utils/fireEvent/package.json: Read -node_modules/reakit-utils/fireKeyboardEvent/package.json: Read -node_modules/reakit-utils/flatten/package.json: Read -node_modules/reakit-utils/getActiveElement/package.json: Read -node_modules/reakit-utils/getDocument/package.json: Read -node_modules/reakit-utils/getNextActiveElementOnBlur/package.json: Read -node_modules/reakit-utils/hasFocusWithin/package.json: Read -node_modules/reakit-utils/isButton/package.json: Read -node_modules/reakit-utils/isEmpty/package.json: Read -node_modules/reakit-utils/isInteger/package.json: Read -node_modules/reakit-utils/isObject/package.json: Read -node_modules/reakit-utils/isPlainObject/package.json: Read -node_modules/reakit-utils/isPortalEvent/package.json: Read -node_modules/reakit-utils/isPromise/package.json: Read -node_modules/reakit-utils/isSelfTarget/package.json: Read -node_modules/reakit-utils/isTextField/package.json: Read -node_modules/reakit-utils/normalizePropsAreEqual/package.json: Read -node_modules/reakit-utils/package.json: Read -node_modules/reakit-utils/removeIndexFromArray/package.json: Read -node_modules/reakit-utils/removeItemFromArray/package.json: Read -node_modules/reakit-utils/shallowEqual/package.json: Read -node_modules/reakit-utils/splitProps/package.json: Read -node_modules/reakit-utils/tabbable/package.json: Read -node_modules/reakit-utils/toArray/package.json: Read -node_modules/reakit-utils/useForkRef/package.json: Read -node_modules/reakit-utils/useIsomorphicEffect/package.json: Read -node_modules/reakit-utils/useLiveRef/package.json: Read -node_modules/reakit-utils/useSealedState/package.json: Read -node_modules/reakit-utils/useUpdateEffect/package.json: Read -node_modules/reakit-warning/es/index.js: Read -node_modules/reakit-warning/es/package.json: Read -node_modules/reakit-warning/es/useWarning.js: Read -node_modules/reakit-warning/es/warning.js: Read -node_modules/reakit-warning/package.json: Read -node_modules/reakit-warning/warning/package.json: Read -node_modules/reakit/Menu/package.json: Read -node_modules/reakit/es/Box/Box.js: Read -node_modules/reakit/es/Box/package.json: Read -node_modules/reakit/es/Button/Button.js: Read -node_modules/reakit/es/Button/package.json: Read -node_modules/reakit/es/Checkbox/Checkbox.js: Read -node_modules/reakit/es/Checkbox/CheckboxState.js: Read -node_modules/reakit/es/Checkbox/package.json: Read -node_modules/reakit/es/Clickable/Clickable.js: Read -node_modules/reakit/es/Clickable/package.json: Read -node_modules/reakit/es/Combobox/Combobox.js: Read -node_modules/reakit/es/Combobox/ComboboxGridCell.js: Read -node_modules/reakit/es/Combobox/ComboboxGridRow.js: Read -node_modules/reakit/es/Combobox/ComboboxGridState.js: Read -node_modules/reakit/es/Combobox/ComboboxItem.js: Read -node_modules/reakit/es/Combobox/ComboboxList.js: Read -node_modules/reakit/es/Combobox/ComboboxListGridState.js: Read -node_modules/reakit/es/Combobox/ComboboxListState.js: Read -node_modules/reakit/es/Combobox/ComboboxOption.js: Read -node_modules/reakit/es/Combobox/ComboboxPopover.js: Read -node_modules/reakit/es/Combobox/ComboboxState.js: Read -node_modules/reakit/es/Combobox/package.json: Read -node_modules/reakit/es/ComboboxBaseState-73fabcba.js: Read -node_modules/reakit/es/ComboboxPopoverState-fdc371b4.js: Read -node_modules/reakit/es/Composite/Composite.js: Read -node_modules/reakit/es/Composite/CompositeGroup.js: Read -node_modules/reakit/es/Composite/CompositeItem.js: Read -node_modules/reakit/es/Composite/CompositeItemWidget.js: Read -node_modules/reakit/es/Composite/CompositeState.js: Read -node_modules/reakit/es/Composite/package.json: Read -node_modules/reakit/es/Dialog/Dialog.js: Read -node_modules/reakit/es/Dialog/DialogBackdrop.js: Read -node_modules/reakit/es/Dialog/DialogDisclosure.js: Read -node_modules/reakit/es/Dialog/DialogState.js: Read -node_modules/reakit/es/Dialog/package.json: Read -node_modules/reakit/es/DialogBackdropContext-8775f78b.js: Read -node_modules/reakit/es/Disclosure/Disclosure.js: Read -node_modules/reakit/es/Disclosure/DisclosureContent.js: Read -node_modules/reakit/es/Disclosure/DisclosureState.js: Read -node_modules/reakit/es/Disclosure/package.json: Read -node_modules/reakit/es/Form/Form.js: Read -node_modules/reakit/es/Form/FormCheckbox.js: Read -node_modules/reakit/es/Form/FormGroup.js: Read -node_modules/reakit/es/Form/FormInput.js: Read -node_modules/reakit/es/Form/FormLabel.js: Read -node_modules/reakit/es/Form/FormMessage.js: Read -node_modules/reakit/es/Form/FormPushButton.js: Read -node_modules/reakit/es/Form/FormRadio.js: Read -node_modules/reakit/es/Form/FormRadioGroup.js: Read -node_modules/reakit/es/Form/FormRemoveButton.js: Read -node_modules/reakit/es/Form/FormState.js: Read -node_modules/reakit/es/Form/FormSubmitButton.js: Read -node_modules/reakit/es/Form/package.json: Read -node_modules/reakit/es/Form/utils/getIn.js: Read -node_modules/reakit/es/Form/utils/package.json: Read -node_modules/reakit/es/Form/utils/setAllIn.js: Read -node_modules/reakit/es/Form/utils/setIn.js: Read -node_modules/reakit/es/Grid/Grid.js: Read -node_modules/reakit/es/Grid/GridCell.js: Read -node_modules/reakit/es/Grid/GridRow.js: Read -node_modules/reakit/es/Grid/GridState.js: Read -node_modules/reakit/es/Grid/package.json: Read -node_modules/reakit/es/Group/Group.js: Read -node_modules/reakit/es/Group/package.json: Read -node_modules/reakit/es/Id/Id.js: Read -node_modules/reakit/es/Id/IdProvider.js: Read -node_modules/reakit/es/Id/IdState.js: Read -node_modules/reakit/es/Id/package.json: Read -node_modules/reakit/es/Input/Input.js: Read -node_modules/reakit/es/Input/package.json: Read -node_modules/reakit/es/Menu/Menu.js: Read -node_modules/reakit/es/Menu/MenuArrow.js: Read -node_modules/reakit/es/Menu/MenuBar.js: Read -node_modules/reakit/es/Menu/MenuBarState.js: Read -node_modules/reakit/es/Menu/MenuButton.js: Read -node_modules/reakit/es/Menu/MenuDisclosure.js: Read -node_modules/reakit/es/Menu/MenuGroup.js: Read -node_modules/reakit/es/Menu/MenuItem.js: Read -node_modules/reakit/es/Menu/MenuItemCheckbox.js: Read -node_modules/reakit/es/Menu/MenuItemRadio.js: Read -node_modules/reakit/es/Menu/MenuSeparator.js: Read -node_modules/reakit/es/Menu/MenuState.js: Read -node_modules/reakit/es/Menu/index.js: Read -node_modules/reakit/es/Menu/package.json: Read -node_modules/reakit/es/MenuContext-6af6cf92.js: Read -node_modules/reakit/es/Popover/Popover.js: Read -node_modules/reakit/es/Popover/PopoverArrow.js: Read -node_modules/reakit/es/Popover/PopoverBackdrop.js: Read -node_modules/reakit/es/Popover/PopoverDisclosure.js: Read -node_modules/reakit/es/Popover/PopoverState.js: Read -node_modules/reakit/es/Popover/package.json: Read -node_modules/reakit/es/Portal/Portal.js: Read -node_modules/reakit/es/Portal/package.json: Read -node_modules/reakit/es/Provider.js: Read -node_modules/reakit/es/Radio/Radio.js: Read -node_modules/reakit/es/Radio/RadioGroup.js: Read -node_modules/reakit/es/Radio/RadioState.js: Read -node_modules/reakit/es/Radio/package.json: Read -node_modules/reakit/es/Role/Role.js: Read -node_modules/reakit/es/Role/package.json: Read -node_modules/reakit/es/Rover/Rover.js: Read -node_modules/reakit/es/Rover/RoverState.js: Read -node_modules/reakit/es/Rover/package.json: Read -node_modules/reakit/es/Separator/Separator.js: Read -node_modules/reakit/es/Separator/package.json: Read -node_modules/reakit/es/Tab/Tab.js: Read -node_modules/reakit/es/Tab/TabList.js: Read -node_modules/reakit/es/Tab/TabPanel.js: Read -node_modules/reakit/es/Tab/TabState.js: Read -node_modules/reakit/es/Tab/package.json: Read -node_modules/reakit/es/Tabbable/Tabbable.js: Read -node_modules/reakit/es/Tabbable/package.json: Read -node_modules/reakit/es/Toolbar/Toolbar.js: Read -node_modules/reakit/es/Toolbar/ToolbarItem.js: Read -node_modules/reakit/es/Toolbar/ToolbarSeparator.js: Read -node_modules/reakit/es/Toolbar/ToolbarState.js: Read -node_modules/reakit/es/Toolbar/package.json: Read -node_modules/reakit/es/Tooltip/Tooltip.js: Read -node_modules/reakit/es/Tooltip/TooltipArrow.js: Read -node_modules/reakit/es/Tooltip/TooltipReference.js: Read -node_modules/reakit/es/Tooltip/TooltipState.js: Read -node_modules/reakit/es/Tooltip/package.json: Read -node_modules/reakit/es/VisuallyHidden/VisuallyHidden.js: Read -node_modules/reakit/es/VisuallyHidden/package.json: Read -node_modules/reakit/es/__globalState-300469f0.js: Read -node_modules/reakit/es/__keys-08a69d36.js: Read -node_modules/reakit/es/__keys-0f89298f.js: Read -node_modules/reakit/es/__keys-26bb1730.js: Read -node_modules/reakit/es/__keys-3c0b2243.js: Read -node_modules/reakit/es/__keys-54ad6269.js: Read -node_modules/reakit/es/__keys-6742f591.js: Read -node_modules/reakit/es/__keys-ae468c11.js: Read -node_modules/reakit/es/__keys-d101cb3b.js: Read -node_modules/reakit/es/__keys-d251e56b.js: Read -node_modules/reakit/es/__keys-e6a5cfbe.js: Read -node_modules/reakit/es/__keys-ed7b48af.js: Read -node_modules/reakit/es/__keys-f74df4e0.js: Read -node_modules/reakit/es/_rollupPluginBabelHelpers-1f0bf8c2.js: Read -node_modules/reakit/es/findEnabledItemById-8ddca752.js: Read -node_modules/reakit/es/findVisibleSubmenu-1553e354.js: Read -node_modules/reakit/es/getCurrentId-5aa9849e.js: Read -node_modules/reakit/es/getInputId-aa144169.js: Read -node_modules/reakit/es/getLabelId-3db05e97.js: Read -node_modules/reakit/es/getMenuId-34730bd3.js: Read -node_modules/reakit/es/getPushButtonId-9f434755.js: Read -node_modules/reakit/es/index.js: Read -node_modules/reakit/es/package.json: Read -node_modules/reakit/es/reverse-30eaa122.js: Read -node_modules/reakit/es/setTextFieldValue-0a221f4e.js: Read -node_modules/reakit/es/shouldShowError-e8a86b53.js: Read -node_modules/reakit/es/userFocus-e16425e3.js: Read -node_modules/reakit/package.json: Read -node_modules/redux/es/package.json: Read -node_modules/redux/es/redux.js: Read -node_modules/redux/package.json: Read -node_modules/refractor/core.js: Read -node_modules/refractor/lang/bash.js: Read -node_modules/refractor/lang/basic.js: Read -node_modules/refractor/lang/c.js: Read -node_modules/refractor/lang/clike.js: Read -node_modules/refractor/lang/cpp.js: Read -node_modules/refractor/lang/csharp.js: Read -node_modules/refractor/lang/css.js: Read -node_modules/refractor/lang/csv.js: Read -node_modules/refractor/lang/dart.js: Read -node_modules/refractor/lang/docker.js: Read -node_modules/refractor/lang/elixir.js: Read -node_modules/refractor/lang/erb.js: Read -node_modules/refractor/lang/erlang.js: Read -node_modules/refractor/lang/go.js: Read -node_modules/refractor/lang/graphql.js: Read -node_modules/refractor/lang/groovy.js: Read -node_modules/refractor/lang/haskell.js: Read -node_modules/refractor/lang/hcl.js: Read -node_modules/refractor/lang/ini.js: Read -node_modules/refractor/lang/java.js: Read -node_modules/refractor/lang/javascript.js: Read -node_modules/refractor/lang/json.js: Read -node_modules/refractor/lang/jsx.js: Read -node_modules/refractor/lang/kotlin.js: Read -node_modules/refractor/lang/kusto.js: Read -node_modules/refractor/lang/lisp.js: Read -node_modules/refractor/lang/lua.js: Read -node_modules/refractor/lang/makefile.js: Read -node_modules/refractor/lang/markdown.js: Read -node_modules/refractor/lang/markup-templating.js: Read -node_modules/refractor/lang/markup.js: Read -node_modules/refractor/lang/mermaid.js: Read -node_modules/refractor/lang/nginx.js: Read -node_modules/refractor/lang/nix.js: Read -node_modules/refractor/lang/objectivec.js: Read -node_modules/refractor/lang/ocaml.js: Read -node_modules/refractor/lang/package.json: Read -node_modules/refractor/lang/perl.js: Read -node_modules/refractor/lang/php.js: Read -node_modules/refractor/lang/powershell.js: Read -node_modules/refractor/lang/promql.js: Read -node_modules/refractor/lang/protobuf.js: Read -node_modules/refractor/lang/python.js: Read -node_modules/refractor/lang/r.js: Read -node_modules/refractor/lang/regex.js: Read -node_modules/refractor/lang/ruby.js: Read -node_modules/refractor/lang/rust.js: Read -node_modules/refractor/lang/sass.js: Read -node_modules/refractor/lang/scala.js: Read -node_modules/refractor/lang/scss.js: Read -node_modules/refractor/lang/solidity.js: Read -node_modules/refractor/lang/splunk-spl.js: Read -node_modules/refractor/lang/sql.js: Read -node_modules/refractor/lang/swift.js: Read -node_modules/refractor/lang/toml.js: Read -node_modules/refractor/lang/tsx.js: Read -node_modules/refractor/lang/typescript.js: Read -node_modules/refractor/lang/vbnet.js: Read -node_modules/refractor/lang/verilog.js: Read -node_modules/refractor/lang/vhdl.js: Read -node_modules/refractor/lang/yaml.js: Read -node_modules/refractor/lang/zig.js: Read -node_modules/refractor/package.json: Read -node_modules/regenerate/package.json: Read -node_modules/regenerate/regenerate.js: Read -node_modules/regexp.prototype.flags/implementation.js: Read -node_modules/regexp.prototype.flags/index.js: Read -node_modules/regexp.prototype.flags/package.json: Read -node_modules/regexp.prototype.flags/polyfill.js: Read -node_modules/regexp.prototype.flags/shim.js: Read -node_modules/regexpu-core/data/all-characters.js: Read -node_modules/regexpu-core/data/character-class-escape-sets.js: Read -node_modules/regexpu-core/data/i-bmp-mappings.js: Read -node_modules/regexpu-core/data/iu-foldings.js: Read -node_modules/regexpu-core/data/iu-mappings.js: Read -node_modules/regexpu-core/data/package.json: Read -node_modules/regexpu-core/node_modules/regenerate/package.json: Read -node_modules/regexpu-core/node_modules/regjsgen/package.json: Read -node_modules/regexpu-core/node_modules/regjsparser/package.json: Read -node_modules/regexpu-core/node_modules/unicode-match-property-ecmascript/package.json: Read -node_modules/regexpu-core/node_modules/unicode-match-property-value-ecmascript/package.json: Read -node_modules/regexpu-core/package.json: Read -node_modules/regexpu-core/rewrite-pattern.js: Read -node_modules/regjsgen/package.json: Read -node_modules/regjsgen/regjsgen.js: Read -node_modules/regjsparser/package.json: Read -node_modules/regjsparser/parser.js: Read -node_modules/resolve-pathname/esm/package.json: Read -node_modules/resolve-pathname/esm/resolve-pathname.js: Read -node_modules/resolve-pathname/package.json: Read -node_modules/resolve/index.js: Read -node_modules/resolve/lib/async.js: Read -node_modules/resolve/lib/caller.js: Read -node_modules/resolve/lib/core.js: Read -node_modules/resolve/lib/core.json: Read -node_modules/resolve/lib/homedir.js: Read -node_modules/resolve/lib/is-core.js: Read -node_modules/resolve/lib/node-modules-paths.js: Read -node_modules/resolve/lib/normalize-options.js: Read -node_modules/resolve/lib/package.json: Read -node_modules/resolve/lib/sync.js: Read -node_modules/resolve/package.json: Read -node_modules/reusify/package.json: Read -node_modules/reusify/reusify.js: Read -node_modules/rfc6902/diff.js: Read -node_modules/rfc6902/index.js: Read -node_modules/rfc6902/package.json: Read -node_modules/rfc6902/patch.js: Read -node_modules/rfc6902/pointer.js: Read -node_modules/rfc6902/util.js: Read -node_modules/robust-predicates/esm/incircle.js: Read -node_modules/robust-predicates/esm/insphere.js: Read -node_modules/robust-predicates/esm/orient2d.js: Read -node_modules/robust-predicates/esm/orient3d.js: Read -node_modules/robust-predicates/esm/package.json: Read -node_modules/robust-predicates/esm/util.js: Read -node_modules/robust-predicates/index.js: Read -node_modules/robust-predicates/package.json: Read -node_modules/rolldown/dist/experimental-index.mjs: Read -node_modules/rolldown/dist/filter-index.mjs: Read -node_modules/rolldown/dist/index.mjs: Read -node_modules/rolldown/dist/package.json: Read -node_modules/rolldown/dist/parse-ast-index.mjs: Read -node_modules/rolldown/dist/shared/misc-CQeo-AFx.mjs: Read -node_modules/rolldown/dist/shared/package.json: Read -node_modules/rolldown/dist/shared/parse-ast-index-H6yPBJEa.mjs: Read -node_modules/rolldown/dist/shared/src-B2JXubG5.mjs: Read -node_modules/rolldown/node_modules/@oxc-project/package.json: Read -node_modules/rolldown/node_modules/@oxc-project/runtime/package.json: Read -node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/esm/decorate.js: Read -node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/esm/decorateMetadata.js: Read -node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/esm/package.json: Read -node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/package.json: Read -node_modules/rolldown/node_modules/@oxc-project/runtime/src/package.json: Read -node_modules/rolldown/node_modules/@rolldown/binding-linux-arm64-gnu/package.json: Read -node_modules/rolldown/node_modules/@rolldown/pluginutils/dist/index.js: Read -node_modules/rolldown/node_modules/@rolldown/pluginutils/dist/package.json: Read -node_modules/rolldown/node_modules/@rolldown/pluginutils/package.json: Read -node_modules/rolldown/package.json: Read -node_modules/rollup-plugin-stats/dist/extract.cjs: Read -node_modules/rollup-plugin-stats/package.json: Read -node_modules/rollup-plugin-webpack-stats/dist/chunks/transform.cjs: Read -node_modules/rollup-plugin-webpack-stats/dist/index.cjs: Read -node_modules/rollup-plugin-webpack-stats/dist/package.json: Read -node_modules/rollup-plugin-webpack-stats/node_modules/rollup-plugin-stats/package.json: Read -node_modules/rollup-plugin-webpack-stats/package.json: Read -node_modules/rollup/dist/package.json: Read -node_modules/rollup/dist/rollup.js: Read -node_modules/rollup/dist/shared/package.json: Read -node_modules/rollup/dist/shared/rollup.js: Read -node_modules/rollup/package.json: Read -node_modules/rope-sequence/dist/index.es.js: Read -node_modules/rope-sequence/dist/package.json: Read -node_modules/rope-sequence/package.json: Read -node_modules/roughjs/bundled/package.json: Read -node_modules/roughjs/bundled/rough.esm.js: Read -node_modules/roughjs/package.json: Read -node_modules/run-parallel/index.js: Read -node_modules/run-parallel/package.json: Read -node_modules/safe-regex-test/index.js: Read -node_modules/safe-regex-test/package.json: Read -node_modules/scheduler/cjs/package.json: Read -node_modules/scheduler/cjs/scheduler.production.min.js: Read -node_modules/scheduler/index.js: Read -node_modules/scheduler/package.json: Read -node_modules/scroll-into-view-if-needed/dist/index.js: Read -node_modules/scroll-into-view-if-needed/dist/package.json: Read -node_modules/scroll-into-view-if-needed/package.json: Read -node_modules/serialize-javascript/index.js: Read -node_modules/serialize-javascript/package.json: Read -node_modules/set-function-length/index.js: Read -node_modules/set-function-length/package.json: Read -node_modules/set-function-name/index.js: Read -node_modules/set-function-name/package.json: Read -node_modules/shallowequal/index.js: Read -node_modules/shallowequal/package.json: Read -node_modules/side-channel-list/index.js: Read -node_modules/side-channel-list/package.json: Read -node_modules/side-channel-map/index.js: Read -node_modules/side-channel-map/package.json: Read -node_modules/side-channel-weakmap/index.js: Read -node_modules/side-channel-weakmap/package.json: Read -node_modules/side-channel/index.js: Read -node_modules/side-channel/package.json: Read -node_modules/slug/package.json: Read -node_modules/slug/slug.js: Read -node_modules/slugify/package.json: Read -node_modules/slugify/slugify.js: Read -node_modules/smob/dist/index.cjs: Read -node_modules/smob/package.json: Read -node_modules/socket.io-client/build/esm/contrib/backo2.js: Read -node_modules/socket.io-client/build/esm/contrib/package.json: Read -node_modules/socket.io-client/build/esm/index.js: Read -node_modules/socket.io-client/build/esm/manager.js: Read -node_modules/socket.io-client/build/esm/on.js: Read -node_modules/socket.io-client/build/esm/package.json: Read -node_modules/socket.io-client/build/esm/socket.js: Read -node_modules/socket.io-client/build/esm/url.js: Read -node_modules/socket.io-client/build/package.json: Read -node_modules/socket.io-client/package.json: Read -node_modules/socket.io-parser/build/esm/binary.js: Read -node_modules/socket.io-parser/build/esm/index.js: Read -node_modules/socket.io-parser/build/esm/is-binary.js: Read -node_modules/socket.io-parser/build/esm/package.json: Read -node_modules/socket.io-parser/build/package.json: Read -node_modules/socket.io-parser/package.json: Read -node_modules/sonner/dist/index.mjs: Read -node_modules/sonner/dist/package.json: Read -node_modules/sonner/package.json: Read -node_modules/source-map-js/lib/array-set.js: Read -node_modules/source-map-js/lib/base64-vlq.js: Read -node_modules/source-map-js/lib/base64.js: Read -node_modules/source-map-js/lib/binary-search.js: Read -node_modules/source-map-js/lib/mapping-list.js: Read -node_modules/source-map-js/lib/package.json: Read -node_modules/source-map-js/lib/quick-sort.js: Read -node_modules/source-map-js/lib/source-map-consumer.js: Read -node_modules/source-map-js/lib/source-map-generator.js: Read -node_modules/source-map-js/lib/source-node.js: Read -node_modules/source-map-js/lib/util.js: Read -node_modules/source-map-js/package.json: Read -node_modules/source-map-js/source-map.js: Read -node_modules/sourcemap-codec/dist/package.json: Read -node_modules/sourcemap-codec/dist/sourcemap-codec.umd.js: Read -node_modules/sourcemap-codec/package.json: Read -node_modules/space-separated-tokens/index.js: Read -node_modules/space-separated-tokens/package.json: Read -node_modules/split-on-first/index.js: Read -node_modules/split-on-first/package.json: Read -node_modules/strict-uri-encode/index.js: Read -node_modules/strict-uri-encode/package.json: Read -node_modules/string-replace-to-array/package.json: Read -node_modules/string-replace-to-array/string-replace-to-array.js: Read -node_modules/string.prototype.matchall/implementation.js: Read -node_modules/string.prototype.matchall/index.js: Read -node_modules/string.prototype.matchall/package.json: Read -node_modules/string.prototype.matchall/polyfill-regexp-matchall.js: Read -node_modules/string.prototype.matchall/polyfill.js: Read -node_modules/string.prototype.matchall/regexp-matchall.js: Read -node_modules/string.prototype.matchall/shim.js: Read -node_modules/string.prototype.trim/implementation.js: Read -node_modules/string.prototype.trim/index.js: Read -node_modules/string.prototype.trim/package.json: Read -node_modules/string.prototype.trim/polyfill.js: Read -node_modules/string.prototype.trim/shim.js: Read -node_modules/stringify-object/index.js: Read -node_modules/stringify-object/package.json: Read -node_modules/strip-comments/index.js: Read -node_modules/strip-comments/lib/Node.js: Read -node_modules/strip-comments/lib/compile.js: Read -node_modules/strip-comments/lib/languages.js: Read -node_modules/strip-comments/lib/package.json: Read -node_modules/strip-comments/lib/parse.js: Read -node_modules/strip-comments/package.json: Read -node_modules/style-value-types/dist/es/color/hex.js: Read -node_modules/style-value-types/dist/es/color/hsla.js: Read -node_modules/style-value-types/dist/es/color/index.js: Read -node_modules/style-value-types/dist/es/color/package.json: Read -node_modules/style-value-types/dist/es/color/rgba.js: Read -node_modules/style-value-types/dist/es/color/utils.js: Read -node_modules/style-value-types/dist/es/complex/filter.js: Read -node_modules/style-value-types/dist/es/complex/index.js: Read -node_modules/style-value-types/dist/es/complex/package.json: Read -node_modules/style-value-types/dist/es/index.js: Read -node_modules/style-value-types/dist/es/numbers/index.js: Read -node_modules/style-value-types/dist/es/numbers/package.json: Read -node_modules/style-value-types/dist/es/numbers/units.js: Read -node_modules/style-value-types/dist/es/package.json: Read -node_modules/style-value-types/dist/es/utils.js: Read -node_modules/style-value-types/dist/package.json: Read -node_modules/style-value-types/package.json: Read -node_modules/styled-components-breakpoint/dist/esm/core.js: Read -node_modules/styled-components-breakpoint/dist/esm/index.js: Read -node_modules/styled-components-breakpoint/dist/esm/package.json: Read -node_modules/styled-components-breakpoint/dist/package.json: Read -node_modules/styled-components-breakpoint/package.json: Read -node_modules/styled-components/dist/package.json: Read -node_modules/styled-components/dist/styled-components.browser.esm.js: Read -node_modules/styled-components/node_modules/@emotion/is-prop-valid/dist/emotion-is-prop-valid.esm.js: Read -node_modules/styled-components/node_modules/@emotion/is-prop-valid/dist/package.json: Read -node_modules/styled-components/node_modules/@emotion/is-prop-valid/package.json: Read -node_modules/styled-components/node_modules/@emotion/memoize/dist/emotion-memoize.esm.js: Read -node_modules/styled-components/node_modules/@emotion/memoize/dist/package.json: Read -node_modules/styled-components/node_modules/@emotion/memoize/package.json: Read -node_modules/styled-components/node_modules/@emotion/package.json: Read -node_modules/styled-components/node_modules/@emotion/stylis/package.json: Read -node_modules/styled-components/node_modules/@emotion/unitless/package.json: Read -node_modules/styled-components/node_modules/hoist-non-react-statics/package.json: Read -node_modules/styled-components/node_modules/package.json: Read -node_modules/styled-components/node_modules/react-is/package.json: Read -node_modules/styled-components/node_modules/react/package.json: Read -node_modules/styled-components/node_modules/shallowequal/package.json: Read -node_modules/styled-components/package.json: Read -node_modules/styled-normalize/dist/index.js: Read -node_modules/styled-normalize/dist/package.json: Read -node_modules/styled-normalize/package.json: Read -node_modules/stylis/index.js: Read -node_modules/stylis/package.json: Read -node_modules/stylis/src/Enum.js: Read -node_modules/stylis/src/Middleware.js: Read -node_modules/stylis/src/Parser.js: Read -node_modules/stylis/src/Prefixer.js: Read -node_modules/stylis/src/Serializer.js: Read -node_modules/stylis/src/Tokenizer.js: Read -node_modules/stylis/src/Utility.js: Read -node_modules/stylis/src/package.json: Read -node_modules/supports-color/index.js: Read -node_modules/supports-color/node_modules/has-flag/index.js: Read -node_modules/supports-color/node_modules/has-flag/package.json: Read -node_modules/supports-color/package.json: Read -node_modules/temp-dir/index.js: Read -node_modules/temp-dir/package.json: Read -node_modules/tempy/index.js: Read -node_modules/tempy/node_modules/is-stream/package.json: Read -node_modules/tempy/node_modules/temp-dir/package.json: Read -node_modules/tempy/node_modules/unique-string/package.json: Read -node_modules/tempy/package.json: Read -node_modules/terser/dist/bundle.min.js: Read -node_modules/terser/dist/package.json: Read -node_modules/terser/node_modules/@jridgewell/source-map/package.json: Read -node_modules/terser/package.json: Read -node_modules/tiny-cookie/dist/package.json: Read -node_modules/tiny-cookie/dist/tiny-cookie.mjs: Read -node_modules/tiny-cookie/package.json: Read -node_modules/tiny-invariant/dist/package.json: Read -node_modules/tiny-invariant/dist/tiny-invariant.esm.js: Read -node_modules/tiny-invariant/package.json: Read -node_modules/tiny-warning/dist/package.json: Read -node_modules/tiny-warning/dist/tiny-warning.esm.js: Read -node_modules/tiny-warning/package.json: Read -node_modules/tinycolor2/package.json: Read -node_modules/tinycolor2/tinycolor.js: Read -node_modules/tinyglobby/dist/index.js: Read -node_modules/tinyglobby/dist/index.mjs: Read -node_modules/tinyglobby/dist/package.json: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/async.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/counter.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/functions/get-array.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/functions/group-files.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/functions/invoke-callback.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/functions/join-path.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/functions/package.json: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/functions/push-directory.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/functions/push-file.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/functions/resolve-symlink.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/functions/walk-directory.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/package.json: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/queue.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/sync.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/api/walker.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/builder/api-builder.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/builder/index.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/builder/package.json: Read -node_modules/tinyglobby/node_modules/fdir/dist/index.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/package.json: Read -node_modules/tinyglobby/node_modules/fdir/dist/types.js: Read -node_modules/tinyglobby/node_modules/fdir/dist/utils.js: Read -node_modules/tinyglobby/node_modules/fdir/package.json: Read -node_modules/tinyglobby/node_modules/picomatch/index.js: Read -node_modules/tinyglobby/node_modules/picomatch/lib/constants.js: Read -node_modules/tinyglobby/node_modules/picomatch/lib/package.json: Read -node_modules/tinyglobby/node_modules/picomatch/lib/parse.js: Read -node_modules/tinyglobby/node_modules/picomatch/lib/picomatch.js: Read -node_modules/tinyglobby/node_modules/picomatch/lib/scan.js: Read -node_modules/tinyglobby/node_modules/picomatch/lib/utils.js: Read -node_modules/tinyglobby/node_modules/picomatch/package.json: Read -node_modules/tinyglobby/package.json: Read -node_modules/to-regex-range/index.js: Read -node_modules/to-regex-range/package.json: Read -node_modules/toggle-selection/index.js: Read -node_modules/toggle-selection/package.json: Read -node_modules/ts-dedent/esm/index.js: Read -node_modules/ts-dedent/esm/package.json: Read -node_modules/ts-dedent/package.json: Read -node_modules/tslib/package.json: Read -node_modules/tslib/tslib.es6.mjs: Read -node_modules/uc.micro/categories/Cc/package.json: Read -node_modules/uc.micro/categories/Cc/regex.mjs: Read -node_modules/uc.micro/categories/Cf/package.json: Read -node_modules/uc.micro/categories/Cf/regex.mjs: Read -node_modules/uc.micro/categories/P/package.json: Read -node_modules/uc.micro/categories/P/regex.mjs: Read -node_modules/uc.micro/categories/S/package.json: Read -node_modules/uc.micro/categories/S/regex.mjs: Read -node_modules/uc.micro/categories/Z/package.json: Read -node_modules/uc.micro/categories/Z/regex.mjs: Read -node_modules/uc.micro/categories/package.json: Read -node_modules/uc.micro/index.mjs: Read -node_modules/uc.micro/package.json: Read -node_modules/uc.micro/properties/Any/package.json: Read -node_modules/uc.micro/properties/Any/regex.mjs: Read -node_modules/uc.micro/properties/package.json: Read -node_modules/unicode-canonical-property-names-ecmascript/index.js: Read -node_modules/unicode-canonical-property-names-ecmascript/package.json: Read -node_modules/unicode-match-property-ecmascript/index.js: Read -node_modules/unicode-match-property-ecmascript/package.json: Read -node_modules/unicode-match-property-value-ecmascript/data/mappings.js: Read -node_modules/unicode-match-property-value-ecmascript/data/package.json: Read -node_modules/unicode-match-property-value-ecmascript/index.js: Read -node_modules/unicode-match-property-value-ecmascript/package.json: Read -node_modules/unicode-property-aliases-ecmascript/index.js: Read -node_modules/unicode-property-aliases-ecmascript/package.json: Read -node_modules/unique-string/index.js: Read -node_modules/unique-string/package.json: Read -node_modules/universalify/index.js: Read -node_modules/universalify/package.json: Read -node_modules/upath/build/code/package.json: Read -node_modules/upath/build/code/upath.js: Read -node_modules/upath/build/package.json: Read -node_modules/upath/package.json: Read -node_modules/uri-js/dist/es5/package.json: Read -node_modules/uri-js/dist/es5/uri.all.js: Read -node_modules/uri-js/dist/package.json: Read -node_modules/uri-js/package.json: Read -node_modules/use-callback-ref/dist/es2015/assignRef.js: Read -node_modules/use-callback-ref/dist/es2015/createRef.js: Read -node_modules/use-callback-ref/dist/es2015/index.js: Read -node_modules/use-callback-ref/dist/es2015/mergeRef.js: Read -node_modules/use-callback-ref/dist/es2015/package.json: Read -node_modules/use-callback-ref/dist/es2015/refToCallback.js: Read -node_modules/use-callback-ref/dist/es2015/transformRef.js: Read -node_modules/use-callback-ref/dist/es2015/useMergeRef.js: Read -node_modules/use-callback-ref/dist/es2015/useRef.js: Read -node_modules/use-callback-ref/dist/es2015/useTransformRef.js: Read -node_modules/use-callback-ref/dist/package.json: Read -node_modules/use-callback-ref/package.json: Read -node_modules/use-sidecar/dist/es2015/config.js: Read -node_modules/use-sidecar/dist/es2015/env.js: Read -node_modules/use-sidecar/dist/es2015/exports.js: Read -node_modules/use-sidecar/dist/es2015/hoc.js: Read -node_modules/use-sidecar/dist/es2015/hook.js: Read -node_modules/use-sidecar/dist/es2015/index.js: Read -node_modules/use-sidecar/dist/es2015/medium.js: Read -node_modules/use-sidecar/dist/es2015/package.json: Read -node_modules/use-sidecar/dist/es2015/renderProp.js: Read -node_modules/use-sidecar/dist/package.json: Read -node_modules/use-sidecar/package.json: Read -node_modules/use-sync-external-store/cjs/package.json: Read -node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.js: Read -node_modules/use-sync-external-store/package.json: Read -node_modules/use-sync-external-store/shim/index.js: Read -node_modules/use-sync-external-store/shim/package.json: Read -node_modules/uuid/dist/esm-browser/index.js: Read -node_modules/uuid/dist/esm-browser/md5.js: Read -node_modules/uuid/dist/esm-browser/nil.js: Read -node_modules/uuid/dist/esm-browser/package.json: Read -node_modules/uuid/dist/esm-browser/parse.js: Read -node_modules/uuid/dist/esm-browser/regex.js: Read -node_modules/uuid/dist/esm-browser/rng.js: Read -node_modules/uuid/dist/esm-browser/sha1.js: Read -node_modules/uuid/dist/esm-browser/stringify.js: Read -node_modules/uuid/dist/esm-browser/v1.js: Read -node_modules/uuid/dist/esm-browser/v3.js: Read -node_modules/uuid/dist/esm-browser/v35.js: Read -node_modules/uuid/dist/esm-browser/v4.js: Read -node_modules/uuid/dist/esm-browser/v5.js: Read -node_modules/uuid/dist/esm-browser/validate.js: Read -node_modules/uuid/dist/esm-browser/version.js: Read -node_modules/uuid/dist/package.json: Read -node_modules/uuid/package.json: Read -node_modules/validator/index.js: Read -node_modules/validator/lib/alpha.js: Read -node_modules/validator/lib/blacklist.js: Read -node_modules/validator/lib/contains.js: Read -node_modules/validator/lib/equals.js: Read -node_modules/validator/lib/escape.js: Read -node_modules/validator/lib/isAbaRouting.js: Read -node_modules/validator/lib/isAfter.js: Read -node_modules/validator/lib/isAlpha.js: Read -node_modules/validator/lib/isAlphanumeric.js: Read -node_modules/validator/lib/isAscii.js: Read -node_modules/validator/lib/isBIC.js: Read -node_modules/validator/lib/isBase32.js: Read -node_modules/validator/lib/isBase58.js: Read -node_modules/validator/lib/isBase64.js: Read -node_modules/validator/lib/isBefore.js: Read -node_modules/validator/lib/isBoolean.js: Read -node_modules/validator/lib/isBtcAddress.js: Read -node_modules/validator/lib/isByteLength.js: Read -node_modules/validator/lib/isCreditCard.js: Read -node_modules/validator/lib/isCurrency.js: Read -node_modules/validator/lib/isDataURI.js: Read -node_modules/validator/lib/isDate.js: Read -node_modules/validator/lib/isDecimal.js: Read -node_modules/validator/lib/isDivisibleBy.js: Read -node_modules/validator/lib/isEAN.js: Read -node_modules/validator/lib/isEmail.js: Read -node_modules/validator/lib/isEmpty.js: Read -node_modules/validator/lib/isEthereumAddress.js: Read -node_modules/validator/lib/isFQDN.js: Read -node_modules/validator/lib/isFloat.js: Read -node_modules/validator/lib/isFullWidth.js: Read -node_modules/validator/lib/isHSL.js: Read -node_modules/validator/lib/isHalfWidth.js: Read -node_modules/validator/lib/isHash.js: Read -node_modules/validator/lib/isHexColor.js: Read -node_modules/validator/lib/isHexadecimal.js: Read -node_modules/validator/lib/isIBAN.js: Read -node_modules/validator/lib/isIMEI.js: Read -node_modules/validator/lib/isIP.js: Read -node_modules/validator/lib/isIPRange.js: Read -node_modules/validator/lib/isISBN.js: Read -node_modules/validator/lib/isISIN.js: Read -node_modules/validator/lib/isISO15924.js: Read -node_modules/validator/lib/isISO31661Alpha2.js: Read -node_modules/validator/lib/isISO31661Alpha3.js: Read -node_modules/validator/lib/isISO31661Numeric.js: Read -node_modules/validator/lib/isISO4217.js: Read -node_modules/validator/lib/isISO6346.js: Read -node_modules/validator/lib/isISO6391.js: Read -node_modules/validator/lib/isISO8601.js: Read -node_modules/validator/lib/isISRC.js: Read -node_modules/validator/lib/isISSN.js: Read -node_modules/validator/lib/isIdentityCard.js: Read -node_modules/validator/lib/isIn.js: Read -node_modules/validator/lib/isInt.js: Read -node_modules/validator/lib/isJSON.js: Read -node_modules/validator/lib/isJWT.js: Read -node_modules/validator/lib/isLatLong.js: Read -node_modules/validator/lib/isLength.js: Read -node_modules/validator/lib/isLicensePlate.js: Read -node_modules/validator/lib/isLocale.js: Read -node_modules/validator/lib/isLowercase.js: Read -node_modules/validator/lib/isLuhnNumber.js: Read -node_modules/validator/lib/isMACAddress.js: Read -node_modules/validator/lib/isMD5.js: Read -node_modules/validator/lib/isMagnetURI.js: Read -node_modules/validator/lib/isMailtoURI.js: Read -node_modules/validator/lib/isMimeType.js: Read -node_modules/validator/lib/isMobilePhone.js: Read -node_modules/validator/lib/isMongoId.js: Read -node_modules/validator/lib/isMultibyte.js: Read -node_modules/validator/lib/isNumeric.js: Read -node_modules/validator/lib/isOctal.js: Read -node_modules/validator/lib/isPassportNumber.js: Read -node_modules/validator/lib/isPort.js: Read -node_modules/validator/lib/isPostalCode.js: Read -node_modules/validator/lib/isRFC3339.js: Read -node_modules/validator/lib/isRgbColor.js: Read -node_modules/validator/lib/isSemVer.js: Read -node_modules/validator/lib/isSlug.js: Read -node_modules/validator/lib/isStrongPassword.js: Read -node_modules/validator/lib/isSurrogatePair.js: Read -node_modules/validator/lib/isTaxID.js: Read -node_modules/validator/lib/isTime.js: Read -node_modules/validator/lib/isULID.js: Read -node_modules/validator/lib/isURL.js: Read -node_modules/validator/lib/isUUID.js: Read -node_modules/validator/lib/isUppercase.js: Read -node_modules/validator/lib/isVAT.js: Read -node_modules/validator/lib/isVariableWidth.js: Read -node_modules/validator/lib/isWhitelisted.js: Read -node_modules/validator/lib/ltrim.js: Read -node_modules/validator/lib/matches.js: Read -node_modules/validator/lib/normalizeEmail.js: Read -node_modules/validator/lib/package.json: Read -node_modules/validator/lib/rtrim.js: Read -node_modules/validator/lib/stripLow.js: Read -node_modules/validator/lib/toBoolean.js: Read -node_modules/validator/lib/toDate.js: Read -node_modules/validator/lib/toFloat.js: Read -node_modules/validator/lib/toInt.js: Read -node_modules/validator/lib/trim.js: Read -node_modules/validator/lib/unescape.js: Read -node_modules/validator/lib/util/algorithms.js: Read -node_modules/validator/lib/util/assertString.js: Read -node_modules/validator/lib/util/checkHost.js: Read -node_modules/validator/lib/util/includes.js: Read -node_modules/validator/lib/util/merge.js: Read -node_modules/validator/lib/util/multilineRegex.js: Read -node_modules/validator/lib/util/nullUndefinedCheck.js: Read -node_modules/validator/lib/util/package.json: Read -node_modules/validator/lib/util/toString.js: Read -node_modules/validator/lib/whitelist.js: Read -node_modules/validator/package.json: Read -node_modules/value-equal/esm/package.json: Read -node_modules/value-equal/esm/value-equal.js: Read -node_modules/value-equal/package.json: Read -node_modules/vaul/dist/index.mjs: Read -node_modules/vaul/dist/package.json: Read -node_modules/vaul/package.json: Read -node_modules/vite-plugin-pwa/dist/index.cjs: Read -node_modules/vite-plugin-pwa/dist/package.json: Read -node_modules/vite-plugin-pwa/package.json: Read -node_modules/vite-plugin-static-copy/dist/index.cjs: Read -node_modules/vite-plugin-static-copy/dist/package.json: Read -node_modules/vite-plugin-static-copy/package.json: Read -node_modules/vite/bin/package.json: Read -node_modules/vite/bin/vite.js: Read -node_modules/vite/dist/node/chunks/dep-8mtZXkH1.js: Read -node_modules/vite/dist/node/chunks/dep-DeB1US-Z.js: Read -node_modules/vite/dist/node/chunks/dep-Drtntmtt.js: Read -node_modules/vite/dist/node/chunks/dep-ZGQIJf1v.js: Read -node_modules/vite/dist/node/chunks/package.json: Read -node_modules/vite/dist/node/cli.js: Read -node_modules/vite/dist/node/index.js: Read -node_modules/vite/dist/node/module-runner.js: Read -node_modules/vite/dist/node/package.json: Read -node_modules/vite/dist/package.json: Read -node_modules/vite/misc/package.json: Read -node_modules/vite/misc/true.js: Read -node_modules/vite/node_modules/bufferutil/package.json: Read -node_modules/vite/node_modules/fsevents/package.json: Read -node_modules/vite/node_modules/picomatch/index.js: Read -node_modules/vite/node_modules/picomatch/lib/constants.js: Read -node_modules/vite/node_modules/picomatch/lib/package.json: Read -node_modules/vite/node_modules/picomatch/lib/parse.js: Read -node_modules/vite/node_modules/picomatch/lib/picomatch.js: Read -node_modules/vite/node_modules/picomatch/lib/scan.js: Read -node_modules/vite/node_modules/picomatch/lib/utils.js: Read -node_modules/vite/node_modules/picomatch/package.json: Read -node_modules/vite/node_modules/supports-color/package.json: Read -node_modules/vite/package.json: Read -node_modules/void-elements/index.js: Read -node_modules/void-elements/package.json: Read -node_modules/vscode-jsonrpc/lib/common/cancellation.js: Read -node_modules/vscode-jsonrpc/lib/common/events.js: Read -node_modules/vscode-jsonrpc/lib/common/is.js: Read -node_modules/vscode-jsonrpc/lib/common/package.json: Read -node_modules/vscode-jsonrpc/lib/common/ral.js: Read -node_modules/vscode-jsonrpc/lib/package.json: Read -node_modules/vscode-jsonrpc/package.json: Read -node_modules/vscode-languageserver-textdocument/lib/esm/main.js: Read -node_modules/vscode-languageserver-textdocument/lib/esm/package.json: Read -node_modules/vscode-languageserver-textdocument/lib/package.json: Read -node_modules/vscode-languageserver-textdocument/package.json: Read -node_modules/vscode-languageserver-types/lib/esm/main.js: Read -node_modules/vscode-languageserver-types/lib/esm/package.json: Read -node_modules/vscode-languageserver-types/lib/package.json: Read -node_modules/vscode-languageserver-types/package.json: Read -node_modules/vscode-uri/lib/esm/index.mjs: Read -node_modules/vscode-uri/lib/esm/package.json: Read -node_modules/vscode-uri/lib/package.json: Read -node_modules/vscode-uri/package.json: Read -node_modules/w3c-keyname/index.es.js: Read -node_modules/w3c-keyname/package.json: Read -node_modules/warning/package.json: Read -node_modules/warning/warning.js: Read -node_modules/workbox-build/build/cdn-details.json: Read -node_modules/workbox-build/build/generate-sw.js: Read -node_modules/workbox-build/build/get-manifest.js: Read -node_modules/workbox-build/build/index.js: Read -node_modules/workbox-build/build/inject-manifest.js: Read -node_modules/workbox-build/build/lib/additional-manifest-entries-transform.js: Read -node_modules/workbox-build/build/lib/bundle.js: Read -node_modules/workbox-build/build/lib/cdn-utils.js: Read -node_modules/workbox-build/build/lib/copy-workbox-libraries.js: Read -node_modules/workbox-build/build/lib/errors.js: Read -node_modules/workbox-build/build/lib/escape-regexp.js: Read -node_modules/workbox-build/build/lib/get-composite-details.js: Read -node_modules/workbox-build/build/lib/get-file-details.js: Read -node_modules/workbox-build/build/lib/get-file-hash.js: Read -node_modules/workbox-build/build/lib/get-file-manifest-entries.js: Read -node_modules/workbox-build/build/lib/get-file-size.js: Read -node_modules/workbox-build/build/lib/get-source-map-url.js: Read -node_modules/workbox-build/build/lib/get-string-details.js: Read -node_modules/workbox-build/build/lib/get-string-hash.js: Read -node_modules/workbox-build/build/lib/maximum-size-transform.js: Read -node_modules/workbox-build/build/lib/modify-url-prefix-transform.js: Read -node_modules/workbox-build/build/lib/module-registry.js: Read -node_modules/workbox-build/build/lib/no-revision-for-urls-matching-transform.js: Read -node_modules/workbox-build/build/lib/package.json: Read -node_modules/workbox-build/build/lib/populate-sw-template.js: Read -node_modules/workbox-build/build/lib/rebase-path.js: Read -node_modules/workbox-build/build/lib/replace-and-update-source-map.js: Read -node_modules/workbox-build/build/lib/runtime-caching-converter.js: Read -node_modules/workbox-build/build/lib/stringify-without-comments.js: Read -node_modules/workbox-build/build/lib/transform-manifest.js: Read -node_modules/workbox-build/build/lib/translate-url-to-sourcemap-paths.js: Read -node_modules/workbox-build/build/lib/validate-options.js: Read -node_modules/workbox-build/build/lib/write-sw-using-default-template.js: Read -node_modules/workbox-build/build/package.json: Read -node_modules/workbox-build/build/schema/GenerateSWOptions.json: Read -node_modules/workbox-build/build/templates/package.json: Read -node_modules/workbox-build/build/templates/sw-template.js: Read -node_modules/workbox-build/build/types.js: Read -node_modules/workbox-build/node_modules/@apideck/better-ajv-errors/package.json: Read -node_modules/workbox-build/node_modules/@babel/preset-env/package.json: Read -node_modules/workbox-build/node_modules/@rollup/plugin-babel/package.json: Read -node_modules/workbox-build/node_modules/@rollup/plugin-node-resolve/package.json: Read -node_modules/workbox-build/node_modules/@rollup/plugin-replace/package.json: Read -node_modules/workbox-build/node_modules/@rollup/plugin-terser/package.json: Read -node_modules/workbox-build/node_modules/@surma/rollup-plugin-off-main-thread/package.json: Read -node_modules/workbox-build/node_modules/ajv/package.json: Read -node_modules/workbox-build/node_modules/at-least-node/package.json: Read -node_modules/workbox-build/node_modules/common-tags/package.json: Read -node_modules/workbox-build/node_modules/fast-json-stable-stringify/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/copy-sync/copy-sync.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/copy-sync/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/copy-sync/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/copy/copy.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/copy/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/copy/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/empty/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/empty/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/ensure/file.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/ensure/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/ensure/link.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/ensure/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/ensure/symlink-paths.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/ensure/symlink-type.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/ensure/symlink.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/fs/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/fs/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/json/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/json/jsonfile.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/json/output-json-sync.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/json/output-json.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/json/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/mkdirs/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/mkdirs/make-dir.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/mkdirs/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/move-sync/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/move-sync/move-sync.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/move-sync/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/move/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/move/move.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/move/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/output/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/output/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/path-exists/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/path-exists/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/remove/index.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/remove/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/remove/rimraf.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/util/package.json: Read -node_modules/workbox-build/node_modules/fs-extra/lib/util/stat.js: Read -node_modules/workbox-build/node_modules/fs-extra/lib/util/utimes.js: Read -node_modules/workbox-build/node_modules/fs-extra/package.json: Read -node_modules/workbox-build/node_modules/fs.realpath/package.json: Read -node_modules/workbox-build/node_modules/glob/common.js: Read -node_modules/workbox-build/node_modules/glob/glob.js: Read -node_modules/workbox-build/node_modules/glob/package.json: Read -node_modules/workbox-build/node_modules/glob/sync.js: Read -node_modules/workbox-build/node_modules/graceful-fs/package.json: Read -node_modules/workbox-build/node_modules/inflight/package.json: Read -node_modules/workbox-build/node_modules/inherits/package.json: Read -node_modules/workbox-build/node_modules/jsonfile/package.json: Read -node_modules/workbox-build/node_modules/lodash/package.json: Read -node_modules/workbox-build/node_modules/minimatch/package.json: Read -node_modules/workbox-build/node_modules/once/package.json: Read -node_modules/workbox-build/node_modules/path-is-absolute/package.json: Read -node_modules/workbox-build/node_modules/pretty-bytes/index.js: Read -node_modules/workbox-build/node_modules/pretty-bytes/package.json: Read -node_modules/workbox-build/node_modules/rollup/package.json: Read -node_modules/workbox-build/node_modules/source-map/lib/array-set.js: Read -node_modules/workbox-build/node_modules/source-map/lib/base64-vlq.js: Read -node_modules/workbox-build/node_modules/source-map/lib/base64.js: Read -node_modules/workbox-build/node_modules/source-map/lib/binary-search.js: Read -node_modules/workbox-build/node_modules/source-map/lib/mapping-list.js: Read -node_modules/workbox-build/node_modules/source-map/lib/package.json: Read -node_modules/workbox-build/node_modules/source-map/lib/read-wasm.js: Read -node_modules/workbox-build/node_modules/source-map/lib/source-map-consumer.js: Read -node_modules/workbox-build/node_modules/source-map/lib/source-map-generator.js: Read -node_modules/workbox-build/node_modules/source-map/lib/source-node.js: Read -node_modules/workbox-build/node_modules/source-map/lib/url.js: Read -node_modules/workbox-build/node_modules/source-map/lib/util.js: Read -node_modules/workbox-build/node_modules/source-map/lib/wasm.js: Read -node_modules/workbox-build/node_modules/source-map/package.json: Read -node_modules/workbox-build/node_modules/source-map/source-map.js: Read -node_modules/workbox-build/node_modules/stringify-object/package.json: Read -node_modules/workbox-build/node_modules/strip-comments/package.json: Read -node_modules/workbox-build/node_modules/tempy/package.json: Read -node_modules/workbox-build/node_modules/universalify/package.json: Read -node_modules/workbox-build/node_modules/upath/package.json: Read -node_modules/workbox-build/node_modules/workbox-cacheable-response/package.json: Read -node_modules/workbox-build/node_modules/workbox-core/package.json: Read -node_modules/workbox-build/node_modules/workbox-expiration/package.json: Read -node_modules/workbox-build/node_modules/workbox-precaching/package.json: Read -node_modules/workbox-build/node_modules/workbox-routing/package.json: Read -node_modules/workbox-build/node_modules/workbox-strategies/package.json: Read -node_modules/workbox-build/package.json: Read -node_modules/workbox-cacheable-response/CacheableResponse.js: Read -node_modules/workbox-cacheable-response/CacheableResponsePlugin.js: Read -node_modules/workbox-cacheable-response/CacheableResponsePlugin.mjs: Read -node_modules/workbox-cacheable-response/_version.js: Read -node_modules/workbox-cacheable-response/package.json: Read -node_modules/workbox-core/_private/Deferred.js: Read -node_modules/workbox-core/_private/WorkboxError.js: Read -node_modules/workbox-core/_private/assert.js: Read -node_modules/workbox-core/_private/cacheMatchIgnoreParams.js: Read -node_modules/workbox-core/_private/cacheNames.js: Read -node_modules/workbox-core/_private/canConstructResponseFromBodyStream.js: Read -node_modules/workbox-core/_private/dontWaitFor.js: Read -node_modules/workbox-core/_private/executeQuotaErrorCallbacks.js: Read -node_modules/workbox-core/_private/getFriendlyURL.js: Read -node_modules/workbox-core/_private/logger.js: Read -node_modules/workbox-core/_private/timeout.js: Read -node_modules/workbox-core/_private/waitUntil.js: Read -node_modules/workbox-core/_version.js: Read -node_modules/workbox-core/clientsClaim.js: Read -node_modules/workbox-core/clientsClaim.mjs: Read -node_modules/workbox-core/copyResponse.js: Read -node_modules/workbox-core/models/messages/messageGenerator.js: Read -node_modules/workbox-core/models/messages/messages.js: Read -node_modules/workbox-core/models/quotaErrorCallbacks.js: Read -node_modules/workbox-core/package.json: Read -node_modules/workbox-core/registerQuotaErrorCallback.js: Read -node_modules/workbox-expiration/CacheExpiration.js: Read -node_modules/workbox-expiration/ExpirationPlugin.js: Read -node_modules/workbox-expiration/ExpirationPlugin.mjs: Read -node_modules/workbox-expiration/_version.js: Read -node_modules/workbox-expiration/models/CacheTimestampsModel.js: Read -node_modules/workbox-expiration/package.json: Read -node_modules/workbox-precaching/PrecacheController.js: Read -node_modules/workbox-precaching/PrecacheRoute.js: Read -node_modules/workbox-precaching/PrecacheStrategy.js: Read -node_modules/workbox-precaching/_version.js: Read -node_modules/workbox-precaching/addRoute.js: Read -node_modules/workbox-precaching/cleanupOutdatedCaches.js: Read -node_modules/workbox-precaching/cleanupOutdatedCaches.mjs: Read -node_modules/workbox-precaching/package.json: Read -node_modules/workbox-precaching/precache.js: Read -node_modules/workbox-precaching/precacheAndRoute.js: Read -node_modules/workbox-precaching/precacheAndRoute.mjs: Read -node_modules/workbox-precaching/utils/PrecacheCacheKeyPlugin.js: Read -node_modules/workbox-precaching/utils/PrecacheInstallReportPlugin.js: Read -node_modules/workbox-precaching/utils/createCacheKey.js: Read -node_modules/workbox-precaching/utils/deleteOutdatedCaches.js: Read -node_modules/workbox-precaching/utils/generateURLVariations.js: Read -node_modules/workbox-precaching/utils/getOrCreatePrecacheController.js: Read -node_modules/workbox-precaching/utils/printCleanupDetails.js: Read -node_modules/workbox-precaching/utils/printInstallDetails.js: Read -node_modules/workbox-precaching/utils/removeIgnoredSearchParams.js: Read -node_modules/workbox-routing/RegExpRoute.js: Read -node_modules/workbox-routing/Route.js: Read -node_modules/workbox-routing/Router.js: Read -node_modules/workbox-routing/_version.js: Read -node_modules/workbox-routing/package.json: Read -node_modules/workbox-routing/registerRoute.js: Read -node_modules/workbox-routing/registerRoute.mjs: Read -node_modules/workbox-routing/utils/constants.js: Read -node_modules/workbox-routing/utils/getOrCreateDefaultRouter.js: Read -node_modules/workbox-routing/utils/normalizeHandler.js: Read -node_modules/workbox-strategies/CacheFirst.js: Read -node_modules/workbox-strategies/CacheFirst.mjs: Read -node_modules/workbox-strategies/CacheOnly.js: Read -node_modules/workbox-strategies/CacheOnly.mjs: Read -node_modules/workbox-strategies/Strategy.js: Read -node_modules/workbox-strategies/StrategyHandler.js: Read -node_modules/workbox-strategies/_version.js: Read -node_modules/workbox-strategies/package.json: Read -node_modules/workbox-strategies/utils/messages.js: Read -node_modules/wrappy/package.json: Read -node_modules/wrappy/wrappy.js: Read -node_modules/xtend/immutable.js: Read -node_modules/xtend/package.json: Read -node_modules/y-indexeddb/node_modules/package.json: Read -node_modules/y-indexeddb/node_modules/yjs/package.json: Read -node_modules/y-indexeddb/package.json: Read -node_modules/y-indexeddb/src/package.json: Read -node_modules/y-indexeddb/src/y-indexeddb.js: Read -node_modules/y-prosemirror/node_modules/package.json: Read -node_modules/y-prosemirror/node_modules/prosemirror-model/package.json: Read -node_modules/y-prosemirror/node_modules/prosemirror-state/package.json: Read -node_modules/y-prosemirror/node_modules/prosemirror-view/package.json: Read -node_modules/y-prosemirror/node_modules/yjs/package.json: Read -node_modules/y-prosemirror/package.json: Read -node_modules/y-prosemirror/src/lib.js: Read -node_modules/y-prosemirror/src/package.json: Read -node_modules/y-prosemirror/src/plugins/cursor-plugin.js: Read -node_modules/y-prosemirror/src/plugins/keys.js: Read -node_modules/y-prosemirror/src/plugins/package.json: Read -node_modules/y-prosemirror/src/plugins/sync-plugin.js: Read -node_modules/y-prosemirror/src/plugins/undo-plugin.js: Read -node_modules/y-prosemirror/src/utils.js: Read -node_modules/y-prosemirror/src/y-prosemirror.js: Read -node_modules/y-protocols/awareness.js: Read -node_modules/y-protocols/node_modules/package.json: Read -node_modules/y-protocols/node_modules/yjs/package.json: Read -node_modules/y-protocols/package.json: Read -node_modules/yallist/iterator.js: Read -node_modules/yallist/package.json: Read -node_modules/yallist/yallist.js: Read -node_modules/yjs/dist/package.json: Read -node_modules/yjs/dist/yjs.mjs: Read -node_modules/yjs/package.json: Read -package.json: Read -plugins: ReadDir -plugins/azure: ReadDir -plugins/azure/client: ReadDir -plugins/azure/client/Icon.tsx: Read -plugins/azure/client/index.tsx: Read -plugins/azure/client/package.json: Read -plugins/azure/package.json: Read -plugins/azure/plugin.json: Read -plugins/azure/server: ReadDir -plugins/azure/server/auth: ReadDir -plugins/discord: ReadDir -plugins/discord/client: ReadDir -plugins/discord/client/Icon.tsx: Read -plugins/discord/client/index.tsx: Read -plugins/discord/client/package.json: Read -plugins/discord/package.json: Read -plugins/discord/plugin.json: Read -plugins/discord/server: ReadDir -plugins/discord/server/auth: ReadDir -plugins/email: ReadDir -plugins/email/server: ReadDir -plugins/email/server/auth: ReadDir -plugins/github: ReadDir -plugins/github/client: ReadDir -plugins/github/client/Icon.tsx: Read -plugins/github/client/Settings.tsx: Read -plugins/github/client/components: ReadDir -plugins/github/client/components/GitHubButton.tsx: Read -plugins/github/client/components/package.json: Read -plugins/github/client/index.tsx: Read -plugins/github/client/package.json: Read -plugins/github/package.json: Read -plugins/github/plugin.json: Read -plugins/github/server: ReadDir -plugins/github/server/api: ReadDir -plugins/github/server/tasks: ReadDir -plugins/github/shared: ReadDir -plugins/github/shared/GitHubUtils.ts: Read -plugins/github/shared/package.json: Read -plugins/google: ReadDir -plugins/google/client: ReadDir -plugins/google/client/Icon.tsx: Read -plugins/google/client/index.tsx: Read -plugins/google/client/package.json: Read -plugins/google/package.json: Read -plugins/google/plugin.json: Read -plugins/google/server: ReadDir -plugins/google/server/auth: ReadDir -plugins/googleanalytics: ReadDir -plugins/googleanalytics/client: ReadDir -plugins/googleanalytics/client/Icon.tsx: Read -plugins/googleanalytics/client/Settings.tsx: Read -plugins/googleanalytics/client/index.tsx: Read -plugins/googleanalytics/client/package.json: Read -plugins/googleanalytics/package.json: Read -plugins/googleanalytics/plugin.json: Read -plugins/iframely: ReadDir -plugins/iframely/server: ReadDir -plugins/linear: ReadDir -plugins/linear/client: ReadDir -plugins/linear/client/Icon.tsx: Read -plugins/linear/client/Settings.tsx: Read -plugins/linear/client/components: ReadDir -plugins/linear/client/components/LinearButton.tsx: Read -plugins/linear/client/components/package.json: Read -plugins/linear/client/index.tsx: Read -plugins/linear/client/package.json: Read -plugins/linear/package.json: Read -plugins/linear/plugin.json: Read -plugins/linear/server: ReadDir -plugins/linear/server/api: ReadDir -plugins/linear/server/tasks: ReadDir -plugins/linear/shared: ReadDir -plugins/linear/shared/LinearUtils.ts: Read -plugins/linear/shared/package.json: Read -plugins/matomo: ReadDir -plugins/matomo/client: ReadDir -plugins/matomo/client/Icon.tsx: Read -plugins/matomo/client/Settings.tsx: Read -plugins/matomo/client/index.tsx: Read -plugins/matomo/client/package.json: Read -plugins/matomo/package.json: Read -plugins/matomo/plugin.json: Read -plugins/notion: ReadDir -plugins/notion/client: ReadDir -plugins/notion/client/Imports.tsx: Read -plugins/notion/client/components: ReadDir -plugins/notion/client/components/ImportDialog.tsx: Read -plugins/notion/client/components/package.json: Read -plugins/notion/client/index.tsx: Read -plugins/notion/client/package.json: Read -plugins/notion/package.json: Read -plugins/notion/plugin.json: Read -plugins/notion/server: ReadDir -plugins/notion/server/api: ReadDir -plugins/notion/server/processors: ReadDir -plugins/notion/server/tasks: ReadDir -plugins/notion/server/utils: ReadDir -plugins/notion/server/utils/__snapshots__: ReadDir -plugins/notion/shared: ReadDir -plugins/notion/shared/NotionUtils.ts: Read -plugins/notion/shared/package.json: Read -plugins/oidc: ReadDir -plugins/oidc/server: ReadDir -plugins/oidc/server/auth: ReadDir -plugins/package.json: Read -plugins/slack: ReadDir -plugins/slack/client: ReadDir -plugins/slack/client/Icon.tsx: Read -plugins/slack/client/Settings.tsx: Read -plugins/slack/client/components: ReadDir -plugins/slack/client/components/SlackButton.tsx: Read -plugins/slack/client/components/SlackListItem.tsx: Read -plugins/slack/client/components/package.json: Read -plugins/slack/client/index.tsx: Read -plugins/slack/client/package.json: Read -plugins/slack/package.json: Read -plugins/slack/plugin.json: Read -plugins/slack/server: ReadDir -plugins/slack/server/api: ReadDir -plugins/slack/server/auth: ReadDir -plugins/slack/server/presenters: ReadDir -plugins/slack/server/processors: ReadDir -plugins/slack/shared: ReadDir -plugins/slack/shared/SlackUtils.ts: Read -plugins/slack/shared/package.json: Read -plugins/storage: ReadDir -plugins/storage/server: ReadDir -plugins/storage/server/api: ReadDir -plugins/storage/server/test: ReadDir -plugins/storage/server/test/fixtures: ReadDir -plugins/umami: ReadDir -plugins/umami/client: ReadDir -plugins/umami/client/Icon.tsx: Read -plugins/umami/client/Settings.tsx: Read -plugins/umami/client/index.tsx: Read -plugins/umami/client/package.json: Read -plugins/umami/package.json: Read -plugins/umami/plugin.json: Read -plugins/webhooks: ReadDir -plugins/webhooks/client: ReadDir -plugins/webhooks/client/Icon.tsx: Read -plugins/webhooks/client/Settings.tsx: Read -plugins/webhooks/client/components: ReadDir -plugins/webhooks/client/components/WebhookSubscriptionDeleteDialog.tsx: Read -plugins/webhooks/client/components/WebhookSubscriptionEdit.tsx: Read -plugins/webhooks/client/components/WebhookSubscriptionForm.tsx: Read -plugins/webhooks/client/components/WebhookSubscriptionListItem.tsx: Read -plugins/webhooks/client/components/WebhookSubscriptionNew.tsx: Read -plugins/webhooks/client/components/package.json: Read -plugins/webhooks/client/index.tsx: Read -plugins/webhooks/client/package.json: Read -plugins/webhooks/package.json: Read -plugins/webhooks/plugin.json: Read -plugins/webhooks/server: ReadDir -plugins/webhooks/server/api: ReadDir -plugins/webhooks/server/presenters: ReadDir -plugins/webhooks/server/processors: ReadDir -plugins/webhooks/server/tasks: ReadDir -plugins/zapier: ReadDir -plugins/zapier/client: ReadDir -plugins/zapier/client/Icon.tsx: Read -plugins/zapier/client/Settings.tsx: Read -plugins/zapier/client/index.tsx: Read -plugins/zapier/client/package.json: Read -plugins/zapier/package.json: Read -plugins/zapier/plugin.json: Read -public/images: ReadDir -public/images/Icon-1024.png: Read -public/images/abstract.png: Read -public/images/airtable.png: Read -public/images/apple-touch-icon.png: Read -public/images/berrycast.png: Read -public/images/bilibili.png: Read -public/images/camunda.png: Read -public/images/canva.png: Read -public/images/cawemo.png: Read -public/images/clickup.png: Read -public/images/codepen.png: Read -public/images/confluence.png: Read -public/images/dbdiagram.png: Read -public/images/descript.png: Read -public/images/diagrams.png: Read -public/images/dropbox.png: Read -public/images/favicon-16.png: Read -public/images/favicon-32.png: Read -public/images/figma.png: Read -public/images/framer.png: Read -public/images/github-gist.png: Read -public/images/gitlab.png: Read -public/images/gliffy.png: Read -public/images/google-calendar.png: Read -public/images/google-docs.png: Read -public/images/google-drawings.png: Read -public/images/google-drive.png: Read -public/images/google-forms.png: Read -public/images/google-lookerstudio.png: Read -public/images/google-maps.png: Read -public/images/google-sheets.png: Read -public/images/google-slides.png: Read -public/images/grist.png: Read -public/images/icon-192.png: Read -public/images/icon-512.png: Read -public/images/instagram.png: Read -public/images/invision.png: Read -public/images/jsfiddle.png: Read -public/images/linkedin.png: Read -public/images/loom.png: Read -public/images/lucidchart.png: Read -public/images/marvel.png: Read -public/images/mermaidjs.png: Read -public/images/mindmeister.png: Read -public/images/miro.png: Read -public/images/mode-analytics.png: Read -public/images/notion.png: Read -public/images/otter.png: Read -public/images/pinterest.png: Read -public/images/pitch.png: Read -public/images/prezi.png: Read -public/images/scribe.png: Read -public/images/slack.png: Read -public/images/smartsuite.png: Read -public/images/spotify.png: Read -public/images/tella.png: Read -public/images/tldraw.png: Read -public/images/trello.png: Read -public/images/typeform.png: Read -public/images/valtown.png: Read -public/images/vimeo.png: Read -public/images/whimsical.png: Read -public/images/youtube.png: Read -public/images/zapier.png: Read -server/package.json: Read -server/static: ReadDir -server/static/error.dev.html: Read -server/static/error.prod.html: Read -server/static/index.html: Read -server/static/static/images: ReadDir -server/utils/environment.ts: Read -server/utils/package.json: Read -shared/collaboration/CloseEvents.ts: Read -shared/collaboration/package.json: Read -shared/components/Backticks.tsx: Read -shared/components/EmojiIcon.tsx: Read -shared/components/EventBoundary.tsx: Read -shared/components/Flex.tsx: Read -shared/components/Icon.tsx: Read -shared/components/IssueStatusIcon/GitHubIssueStatusIcon.tsx: Read -shared/components/IssueStatusIcon/LinearIssueStatusIcon.tsx: Read -shared/components/IssueStatusIcon/index.tsx: Read -shared/components/IssueStatusIcon/package.json: Read -shared/components/LetterIcon.tsx: Read -shared/components/PullRequestIcon.tsx: Read -shared/components/Spinner.tsx: Read -shared/components/Squircle.tsx: Read -shared/components/Text.tsx: Read -shared/components/package.json: Read -shared/constants.ts: Read -shared/editor/commands/addMark.ts: Read -shared/editor/commands/backspaceToParagraph.ts: Read -shared/editor/commands/clearNodes.ts: Read -shared/editor/commands/codeFence.ts: Read -shared/editor/commands/collapseSelection.ts: Read -shared/editor/commands/deleteEmptyFirstParagraph.ts: Read -shared/editor/commands/insertFiles.ts: Read -shared/editor/commands/package.json: Read -shared/editor/commands/selectAll.ts: Read -shared/editor/commands/splitHeading.ts: Read -shared/editor/commands/table.ts: Read -shared/editor/commands/toggleBlockType.ts: Read -shared/editor/commands/toggleCheckboxItem.ts: Read -shared/editor/commands/toggleList.ts: Read -shared/editor/commands/toggleMark.ts: Read -shared/editor/commands/toggleWrap.ts: Read -shared/editor/components/Caption.tsx: Read -shared/editor/components/DisabledEmbed.tsx: Read -shared/editor/components/Embed.tsx: Read -shared/editor/components/FileExtension.tsx: Read -shared/editor/components/Frame.tsx: Read -shared/editor/components/Image.tsx: Read -shared/editor/components/ImageZoom.tsx: Read -shared/editor/components/Img.tsx: Read -shared/editor/components/Mentions.tsx: Read -shared/editor/components/ResizeHandle.tsx: Read -shared/editor/components/Styles.ts: Read -shared/editor/components/Video.tsx: Read -shared/editor/components/Widget.tsx: Read -shared/editor/components/hooks/package.json: Read -shared/editor/components/hooks/useDragResize.ts: Read -shared/editor/components/package.json: Read -shared/editor/embeds/Berrycast.tsx: Read -shared/editor/embeds/Diagrams.tsx: Read -shared/editor/embeds/Dropbox.tsx: Read -shared/editor/embeds/Gist.tsx: Read -shared/editor/embeds/GitLabSnippet.tsx: Read -shared/editor/embeds/InVision.tsx: Read -shared/editor/embeds/JSFiddle.tsx: Read -shared/editor/embeds/Linkedin.tsx: Read -shared/editor/embeds/Pinterest.tsx: Read -shared/editor/embeds/Spotify.tsx: Read -shared/editor/embeds/Trello.tsx: Read -shared/editor/embeds/Vimeo.tsx: Read -shared/editor/embeds/YouTube.tsx: Read -shared/editor/embeds/index.tsx: Read -shared/editor/embeds/package.json: Read -shared/editor/extensions/CodeHighlighting.ts: Read -shared/editor/extensions/DateTime.ts: Read -shared/editor/extensions/History.ts: Read -shared/editor/extensions/Math.ts: Read -shared/editor/extensions/MaxLength.ts: Read -shared/editor/extensions/Mermaid.ts: Read -shared/editor/extensions/TrailingNode.ts: Read -shared/editor/extensions/package.json: Read -shared/editor/lib/Extension.ts: Read -shared/editor/lib/ExtensionManager.ts: Read -shared/editor/lib/FileHelper.ts: Read -shared/editor/lib/InputRule.ts: Read -shared/editor/lib/chainTransactions.ts: Read -shared/editor/lib/changedDescendants.ts: Read -shared/editor/lib/code.ts: Read -shared/editor/lib/embeds.ts: Read -shared/editor/lib/emoji.ts: Read -shared/editor/lib/filterExcessSeparators.ts: Read -shared/editor/lib/headingToSlug.ts: Read -shared/editor/lib/isCode.ts: Read -shared/editor/lib/isMarkdown.ts: Read -shared/editor/lib/markInputRule.ts: Read -shared/editor/lib/markdown/normalize.ts: Read -shared/editor/lib/markdown/package.json: Read -shared/editor/lib/markdown/rules.ts: Read -shared/editor/lib/markdown/serializer.ts: Read -shared/editor/lib/mention.ts: Read -shared/editor/lib/multiplayer.ts: Read -shared/editor/lib/package.json: Read -shared/editor/lib/prosemirror-recreate-transform/copy.ts: Read -shared/editor/lib/prosemirror-recreate-transform/getFromPath.ts: Read -shared/editor/lib/prosemirror-recreate-transform/getReplaceStep.ts: Read -shared/editor/lib/prosemirror-recreate-transform/index.ts: Read -shared/editor/lib/prosemirror-recreate-transform/package.json: Read -shared/editor/lib/prosemirror-recreate-transform/recreateTransform.ts: Read -shared/editor/lib/prosemirror-recreate-transform/removeMarks.ts: Read -shared/editor/lib/prosemirror-recreate-transform/simplifyTransform.ts: Read -shared/editor/lib/table.ts: Read -shared/editor/lib/textBetween.ts: Read -shared/editor/lib/textSerializers.ts: Read -shared/editor/lib/uploadPlaceholder.tsx: Read -shared/editor/marks/Bold.ts: Read -shared/editor/marks/Code.ts: Read -shared/editor/marks/Comment.ts: Read -shared/editor/marks/Highlight.ts: Read -shared/editor/marks/Italic.ts: Read -shared/editor/marks/Link.tsx: Read -shared/editor/marks/Mark.ts: Read -shared/editor/marks/Placeholder.ts: Read -shared/editor/marks/Strikethrough.ts: Read -shared/editor/marks/Underline.ts: Read -shared/editor/marks/package.json: Read -shared/editor/nodes/Attachment.tsx: Read -shared/editor/nodes/Blockquote.ts: Read -shared/editor/nodes/BulletList.ts: Read -shared/editor/nodes/CheckboxItem.ts: Read -shared/editor/nodes/CheckboxList.ts: Read -shared/editor/nodes/CodeBlock.ts: Read -shared/editor/nodes/CodeFence.ts: Read -shared/editor/nodes/Doc.ts: Read -shared/editor/nodes/Embed.tsx: Read -shared/editor/nodes/Emoji.tsx: Read -shared/editor/nodes/HardBreak.ts: Read -shared/editor/nodes/Heading.ts: Read -shared/editor/nodes/HorizontalRule.ts: Read -shared/editor/nodes/Image.tsx: Read -shared/editor/nodes/ListItem.ts: Read -shared/editor/nodes/Math.ts: Read -shared/editor/nodes/MathBlock.ts: Read -shared/editor/nodes/Mention.tsx: Read -shared/editor/nodes/Node.ts: Read -shared/editor/nodes/Notice.tsx: Read -shared/editor/nodes/OrderedList.ts: Read -shared/editor/nodes/Paragraph.ts: Read -shared/editor/nodes/SimpleImage.tsx: Read -shared/editor/nodes/Table.ts: Read -shared/editor/nodes/TableCell.ts: Read -shared/editor/nodes/TableHeader.ts: Read -shared/editor/nodes/TableRow.ts: Read -shared/editor/nodes/TableView.ts: Read -shared/editor/nodes/Text.ts: Read -shared/editor/nodes/Video.tsx: Read -shared/editor/nodes/index.ts: Read -shared/editor/nodes/package.json: Read -shared/editor/package.json: Read -shared/editor/plugins/CodeWordDecorations.ts: Read -shared/editor/plugins/FixTables.ts: Read -shared/editor/plugins/PlaceholderPlugin.ts: Read -shared/editor/plugins/Suggestions.ts: Read -shared/editor/plugins/TableLayoutPlugin.ts: Read -shared/editor/plugins/UploadPlugin.ts: Read -shared/editor/plugins/package.json: Read -shared/editor/queries/findChildren.ts: Read -shared/editor/queries/findCollapsedNodes.ts: Read -shared/editor/queries/findNewlines.ts: Read -shared/editor/queries/findParentNode.ts: Read -shared/editor/queries/getCurrentBlock.ts: Read -shared/editor/queries/getMarkRange.ts: Read -shared/editor/queries/getMarksBetween.ts: Read -shared/editor/queries/getParentListItem.ts: Read -shared/editor/queries/isInCode.ts: Read -shared/editor/queries/isInList.ts: Read -shared/editor/queries/isInNotice.ts: Read -shared/editor/queries/isList.ts: Read -shared/editor/queries/isMarkActive.ts: Read -shared/editor/queries/isNodeActive.ts: Read -shared/editor/queries/package.json: Read -shared/editor/queries/table.ts: Read -shared/editor/rules/breaks.ts: Read -shared/editor/rules/checkboxes.ts: Read -shared/editor/rules/embeds.ts: Read -shared/editor/rules/emoji.ts: Read -shared/editor/rules/links.ts: Read -shared/editor/rules/mark.ts: Read -shared/editor/rules/math.ts: Read -shared/editor/rules/mention.ts: Read -shared/editor/rules/notices.ts: Read -shared/editor/rules/package.json: Read -shared/editor/rules/tables.ts: Read -shared/editor/rules/underlines.ts: Read -shared/editor/styles/EditorStyleHelper.ts: Read -shared/editor/styles/package.json: Read -shared/editor/styles/utils.ts: Read -shared/editor/types/index.ts: Read -shared/editor/types/package.json: Read -shared/editor/version.ts: Read -shared/env.ts: Read -shared/hooks/package.json: Read -shared/hooks/useIsMounted.ts: Read -shared/hooks/useStores.ts: Read -shared/i18n/index.ts: Read -shared/i18n/package.json: Read -shared/package.json: Read -shared/random.ts: Read -shared/styles/breakpoints.ts: Read -shared/styles/depths.ts: Read -shared/styles/globals.ts: Read -shared/styles/index.ts: Read -shared/styles/package.json: Read -shared/styles/theme.ts: Read -shared/types.ts: Read -shared/utils/IconLibrary.tsx: Read -shared/utils/ProsemirrorHelper.ts: Read -shared/utils/RevisionHelper.ts: Read -shared/utils/Storage.ts: Read -shared/utils/TextHelper.ts: Read -shared/utils/UrlHelper.ts: Read -shared/utils/UserRoleHelper.ts: Read -shared/utils/browser.ts: Read -shared/utils/collections.ts: Read -shared/utils/color.ts: Read -shared/utils/csv.ts: Read -shared/utils/date.ts: Read -shared/utils/domains.ts: Read -shared/utils/email.ts: Read -shared/utils/emoji.ts: Read -shared/utils/events.ts: Read -shared/utils/files.ts: Read -shared/utils/icon.ts: Read -shared/utils/keyboard.ts: Read -shared/utils/naturalSort.ts: Read -shared/utils/package.json: Read -shared/utils/parseCollectionSlug.ts: Read -shared/utils/parseDocumentSlug.ts: Read -shared/utils/routeHelpers.ts: Read -shared/utils/rtl.ts: Read -shared/utils/slugify.ts: Read -shared/utils/time.ts: Read -shared/utils/urls.ts: Read -shared/validations.ts: Read -tsconfig.json: Read -vite.config.ts: Read diff --git a/crates/fspy_e2e/src/main.rs b/crates/fspy_e2e/src/main.rs deleted file mode 100644 index 9d6a30525..000000000 --- a/crates/fspy_e2e/src/main.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::{ - collections::{BTreeMap, btree_map::Entry}, - env::{self, args}, - fs::{File, read}, - io::{BufWriter, Write as _, stderr}, - path::PathBuf, - process::{self, Stdio}, -}; - -use fspy::{AccessMode, PathAccess}; -use rustc_hash::FxHashMap; -use serde::{Deserialize, Serialize}; -use tokio::io::AsyncReadExt; - -#[derive(Serialize, Deserialize)] -struct Config { - cases: FxHashMap, -} - -#[derive(Serialize, Deserialize)] -struct Case { - dir: String, - cmd: Vec, -} - -struct AccessCollector { - dir: PathBuf, - accesses: BTreeMap, -} - -impl AccessCollector { - pub const fn new(dir: PathBuf) -> Self { - Self { dir, accesses: BTreeMap::new() } - } - - pub fn iter(&self) -> impl Iterator { - self.accesses.iter().map(|(k, v)| (k.as_str(), *v)) - } - - pub fn add(&mut self, access: PathAccess) { - let Some(relative_path) = access.path.strip_path_prefix(&self.dir, |result| { - result.ok().and_then(|p| p.to_str().map(str::to_owned)) - }) else { - return; - }; - match self.accesses.entry(relative_path) { - Entry::Vacant(vacant) => { - vacant.insert(access.mode); - } - Entry::Occupied(mut occupied) => { - let occupied_mode = occupied.get_mut(); - occupied_mode.insert(access.mode); - } - } - } -} - -#[tokio::main] -#[expect( - clippy::print_stdout, - clippy::print_stderr, - reason = "CLI tool that outputs results and errors to stdout/stderr" -)] -async fn main() { - let mut args = args(); - args.next(); // skip the first argument (the program name) - let filter = args.next(); - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let config = read(manifest_dir.join("e2e_config.toml")).unwrap(); - let config: Config = toml::from_slice(&config).unwrap(); - for (name, case) in config.cases { - if let Some(filter) = &filter - && !name.contains(filter) - { - continue; - } - println!("Running case `{}` in dir `{}`", name, case.dir); - let mut cmd = fspy::Command::new(case.cmd[0].clone()); - let dir = manifest_dir.join(&case.dir); - cmd.args(&case.cmd[1..]) - .envs(env::vars_os()) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .current_dir(&dir); - - let mut tracked_child = - cmd.spawn(tokio_util::sync::CancellationToken::new()).await.unwrap(); - - let mut stdout_bytes = Vec::::new(); - tracked_child.stdout.take().unwrap().read_to_end(&mut stdout_bytes).await.unwrap(); - - let mut stderr_bytes = Vec::::new(); - tracked_child.stderr.take().unwrap().read_to_end(&mut stderr_bytes).await.unwrap(); - - let termination = tracked_child.wait_handle.await.unwrap(); - - if !termination.status.success() { - eprintln!("----- stdout begin -----"); - stderr().write_all(&stdout_bytes).unwrap(); - eprintln!("----- stdout end -----"); - eprintln!("----- stderr begin-----"); - stderr().write_all(&stderr_bytes).unwrap(); - eprintln!("----- stderr end -----"); - - eprintln!("Case `{}` failed with status: {}", name, termination.status); - process::exit(1); - } - - let mut collector = AccessCollector::new(dir); - for access in termination.path_accesses.iter() { - collector.add(access); - } - let snap_file = File::create(manifest_dir.join(format!("snaps/{name}.txt"))).unwrap(); - let mut snap_writer = BufWriter::new(snap_file); - for (path, mode) in collector.iter() { - writeln!(snap_writer, "{path}: {mode:?}").unwrap(); - } - } -} diff --git a/crates/fspy_preload_unix/.clippy.toml b/crates/fspy_preload_unix/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/fspy_preload_unix/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/fspy_preload_unix/Cargo.toml b/crates/fspy_preload_unix/Cargo.toml deleted file mode 100644 index a430d1fa4..000000000 --- a/crates/fspy_preload_unix/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "fspy_preload_unix" -edition = "2024" -license.workspace = true -publish = false - -[lib] -crate-type = ["cdylib"] - -[target.'cfg(unix)'.dependencies] -anyhow = { workspace = true } -wincode = { workspace = true } -bstr = { workspace = true, default-features = false } -ctor = { workspace = true } -fspy_shared = { workspace = true } -fspy_shared_unix = { workspace = true } -libc = { workspace = true } -nix = { workspace = true, features = ["signal", "fs", "socket", "mman", "time"] } - -[lints] -workspace = true diff --git a/crates/fspy_preload_unix/README.md b/crates/fspy_preload_unix/README.md deleted file mode 100644 index d0e9644c8..000000000 --- a/crates/fspy_preload_unix/README.md +++ /dev/null @@ -1,5 +0,0 @@ -## fspy_preload_unix - -The shared library injected by `DYLD_INSERT_LIBRARIES` on macOS and `LD_PRELOAD` on Linux to intercept file system calls. - -This crates only contains code the shared library itself. The injection process is done in `fspy` crate. diff --git a/crates/fspy_preload_unix/src/client/convert.rs b/crates/fspy_preload_unix/src/client/convert.rs deleted file mode 100644 index c42854b32..000000000 --- a/crates/fspy_preload_unix/src/client/convert.rs +++ /dev/null @@ -1,129 +0,0 @@ -#[cfg(target_os = "linux")] -use std::ffi::CString; -use std::{ - ffi::{CStr, OsStr}, - os::{fd::RawFd, unix::ffi::OsStrExt as _}, - path::PathBuf, -}; - -use bstr::{BStr, ByteSlice}; -use fspy_shared::ipc::AccessMode; -use libc::{c_char, c_int}; -use nix::unistd::getcwd; - -#[cfg(target_os = "linux")] -fn get_fd_path(fd: RawFd) -> nix::Result> { - if fd == libc::AT_FDCWD { - return Ok(Some(getcwd()?)); - } - match nix::fcntl::readlink(CString::new(format!("/proc/self/fd/{fd}")).unwrap().as_c_str()) { - Ok(path) => Ok(Some(path.into())), - Err(nix::Error::EBADF | nix::Error::ENOENT) => Ok(None), // invalid fd or no such file (Most likely a stdio fd) - Err(e) => Err(e), - } -} - -#[cfg(target_os = "macos")] -fn get_fd_path(fd: RawFd) -> nix::Result> { - if fd == libc::AT_FDCWD { - return Ok(Some(getcwd()?)); - } - let mut path = std::path::PathBuf::new(); - match nix::fcntl::fcntl( - // SAFETY: fd is a valid file descriptor provided by the caller, and the borrow does not outlive this function call - unsafe { std::os::fd::BorrowedFd::borrow_raw(fd) }, - nix::fcntl::FcntlArg::F_GETPATH(&mut path), - ) { - Ok(_) => Ok(Some(path)), - Err(nix::Error::EBADF | nix::Error::ENOENT) => Ok(None), // invalid fd or no such file (Most likely a stdio fd) - Err(e) => Err(e), - } -} - -pub trait ToAbsolutePath { - unsafe fn to_absolute_path) -> nix::Result>( - self, - f: F, - ) -> nix::Result; -} - -pub struct Fd(pub c_int); -impl ToAbsolutePath for Fd { - unsafe fn to_absolute_path) -> nix::Result>( - self, - f: F, - ) -> nix::Result { - let path = get_fd_path(self.0)?; - f(path.as_ref().map(|p| p.as_os_str().as_bytes().as_bstr())) - } -} - -pub struct PathAt(pub c_int, pub *const c_char); - -impl ToAbsolutePath for PathAt { - unsafe fn to_absolute_path) -> nix::Result>( - self, - f: F, - ) -> nix::Result { - // SAFETY: self.1 is a non-null pointer to a valid null-terminated C string, as guaranteed by the libc calling convention - let pathname = unsafe { CStr::from_ptr(self.1) }.to_bytes().as_bstr(); - - if pathname.first().copied() == Some(b'/') { - f(pathname.into()) - } else { - let Some(mut abs_path) = get_fd_path(self.0)? else { - return f(None); - }; - if !pathname.is_empty() { - abs_path.push(OsStr::from_bytes(pathname)); - } - f(Some(abs_path.as_os_str().as_bytes().as_bstr())) - } - } -} - -impl ToAbsolutePath for *const c_char { - unsafe fn to_absolute_path) -> nix::Result>( - self, - f: F, - ) -> nix::Result { - // SAFETY: delegates to PathAt::to_absolute_path with AT_FDCWD and the caller-provided C string pointer - unsafe { PathAt(libc::AT_FDCWD, self).to_absolute_path(f) } - } -} - -pub trait ToAccessMode { - unsafe fn to_access_mode(self) -> AccessMode; -} - -impl ToAccessMode for AccessMode { - unsafe fn to_access_mode(self) -> AccessMode { - self - } -} - -pub struct OpenFlags(pub c_int); -impl ToAccessMode for OpenFlags { - unsafe fn to_access_mode(self) -> AccessMode { - match self.0 & libc::O_ACCMODE { - libc::O_RDWR => AccessMode::READ | AccessMode::WRITE, - libc::O_WRONLY => AccessMode::WRITE, - _ => AccessMode::READ, - } - } -} - -pub struct ModeStr(pub *const c_char); -impl ToAccessMode for ModeStr { - unsafe fn to_access_mode(self) -> AccessMode { - // SAFETY: self.0 is a non-null pointer to a valid null-terminated C string, as guaranteed by the libc calling convention - let mode_str = unsafe { CStr::from_ptr(self.0) }.to_bytes().as_bstr(); - let has_read = mode_str.contains(&b'r'); - let has_write = mode_str.contains(&b'w') || mode_str.contains(&b'a'); - match (has_read, has_write) { - (false, true) => AccessMode::WRITE, - (true, true) => AccessMode::READ | AccessMode::WRITE, - _ => AccessMode::READ, - } - } -} diff --git a/crates/fspy_preload_unix/src/client/mod.rs b/crates/fspy_preload_unix/src/client/mod.rs deleted file mode 100644 index daae12f5a..000000000 --- a/crates/fspy_preload_unix/src/client/mod.rs +++ /dev/null @@ -1,165 +0,0 @@ -pub mod convert; -pub mod raw_exec; - -use std::{ - cell::Cell, ffi::OsStr, fmt::Debug, num::NonZeroUsize, os::unix::ffi::OsStrExt as _, - path::Path, sync::OnceLock, -}; - -use convert::{ToAbsolutePath, ToAccessMode}; -use fspy_shared::ipc::{PathAccess, channel::Sender}; -use fspy_shared_unix::{ - exec::ExecResolveConfig, - payload::EncodedPayload, - spawn::{PreExec, handle_exec}, -}; -use raw_exec::RawExec; -use wincode::Serialize as _; - -pub struct Client { - encoded_payload: EncodedPayload, - ipc_sender: Option, -} - -// SAFETY: Client fields are only mutated during initialization in the ctor; after that, all access is read-only -#[cfg(target_os = "macos")] -unsafe impl Sync for Client {} -// SAFETY: Client is only sent once during initialization; after that it lives in a static OnceLock -#[cfg(target_os = "macos")] -unsafe impl Send for Client {} - -impl Debug for Client { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Client").finish() - } -} - -impl Client { - #[expect( - clippy::print_stderr, - reason = "preload library intentionally uses stderr for error reporting" - )] - #[cfg(not(test))] - fn from_env() -> Self { - use fspy_shared_unix::payload::decode_payload_from_env; - - let encoded_payload = decode_payload_from_env().unwrap(); - - let ipc_sender = match encoded_payload.payload.ipc_channel_conf.sender() { - Ok(sender) => Some(sender), - Err(err) => { - // this can happen if the process is started after the root target process has exited. - // By that time the channel would have been closed in the receiver side. - // In this case we just leave a message and skip sending any path accesses. - eprintln!("fspy: failed to create ipc sender: {err}"); - None - } - }; - - Self { encoded_payload, ipc_sender } - } - - fn send(&self, mode: fspy_shared::ipc::AccessMode, path: &Path) -> anyhow::Result<()> { - let Some(ipc_sender) = &self.ipc_sender else { - // ipc channel not available, skip sending - return Ok(()); - }; - let path_bytes = path.as_os_str().as_bytes(); - if path_bytes.starts_with(b"/dev/") - || (cfg!(target_os = "linux") - && (path_bytes.starts_with(b"/proc/") || path_bytes.starts_with(b"/sys/"))) - { - return Ok(()); - } - let path_access = PathAccess { mode, path: path.into() }; - let serialized_size = usize::try_from(PathAccess::serialized_size(&path_access)?) - .expect("serialized size exceeds usize"); - - let frame_size = NonZeroUsize::new(serialized_size) - .expect("fspy: encoded PathAccess should never be empty"); - - let mut frame = ipc_sender - .claim_frame(frame_size) - .expect("fspy: failed to claim frame in shared memory"); - let mut writer: &mut [u8] = &mut frame; - PathAccess::serialize_into(&mut writer, &path_access)?; - assert_eq!(writer.len(), 0); - - Ok(()) - } - - pub unsafe fn handle_exec( - &self, - config: ExecResolveConfig, - raw_exec: RawExec, - f: impl FnOnce(RawExec, Option) -> nix::Result, - ) -> nix::Result { - // SAFETY: raw_exec contains valid pointers to C strings and null-terminated arrays, as provided by the caller - let mut exec = unsafe { raw_exec.to_exec() }; - let pre_exec = handle_exec(&mut exec, config, &self.encoded_payload, |mode, path| { - self.send(mode, path).unwrap(); - })?; - RawExec::from_exec(exec, |raw_command| f(raw_command, pre_exec)) - } - - pub unsafe fn try_handle_open( - &self, - path: impl ToAbsolutePath, - mode: impl ToAccessMode, - ) -> anyhow::Result<()> { - // SAFETY: mode contains a valid pointer (if ModeStr) or a plain value, as provided by the caller - let mode = unsafe { mode.to_access_mode() }; - // SAFETY: path contains valid pointers to C strings/file descriptors, as provided by the caller - let () = unsafe { - path.to_absolute_path(|abs_path| { - let Some(abs_path) = abs_path else { - return Ok(Ok(())); - }; - Ok(self.send(mode, Path::new(OsStr::from_bytes(abs_path)))) - }) - }??; - - Ok(()) - } -} - -static CLIENT: OnceLock = OnceLock::new(); - -// Resolving and reporting a file access can call another interposed function. -// Suppress same-thread re-entry to prevent recursive access handling while -// still recording accesses from other threads. -thread_local! { - static HANDLING_OPEN: Cell = const { Cell::new(false) }; -} - -struct ResetHandling<'a>(&'a Cell); -impl Drop for ResetHandling<'_> { - fn drop(&mut self) { - self.0.set(false); - } -} - -pub fn global_client() -> Option<&'static Client> { - CLIENT.get() -} - -pub unsafe fn handle_open(path: impl ToAbsolutePath, mode: impl ToAccessMode) { - HANDLING_OPEN.with(|handling| { - if handling.replace(true) { - return; - } - - let _reset = ResetHandling(handling); - - if let Some(client) = global_client() { - // SAFETY: path and mode contain valid pointers/values forwarded from the interposed function's caller - unsafe { client.try_handle_open(path, mode) }.unwrap(); - } - }); -} - -#[cfg(not(test))] -#[ctor::ctor(unsafe)] -fn init_client() { - CLIENT.set(Client::from_env()).unwrap(); -} diff --git a/crates/fspy_preload_unix/src/client/raw_exec.rs b/crates/fspy_preload_unix/src/client/raw_exec.rs deleted file mode 100644 index 250f9e282..000000000 --- a/crates/fspy_preload_unix/src/client/raw_exec.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::{ffi::CStr, ptr::null}; - -use bstr::{BStr, BString, ByteSlice}; -use fspy_shared_unix::exec::Exec; - -#[derive(Clone, Copy)] -pub struct RawExec { - pub prog: *const libc::c_char, - pub argv: *const *const libc::c_char, - pub envp: *const *const libc::c_char, -} - -impl RawExec { - unsafe fn collect_c_str_array( - strs: *const *const libc::c_char, - mut map_fn: impl FnMut(&BStr) -> T, - ) -> Vec { - let mut count = 0usize; - let mut cur_str = strs; - // SAFETY: cur_str points into a valid null-terminated array of C string pointers (argv/envp convention) - while !(unsafe { *cur_str }).is_null() { - count += 1; - // SAFETY: advancing within the bounds of the null-terminated pointer array - cur_str = unsafe { cur_str.add(1) }; - } - - let mut str_vec = Vec::::with_capacity(count); - for i in 0..count { - // SAFETY: i < count, so strs.add(i) is within the bounds of the pointer array - let cur_str = unsafe { strs.add(i) }; - // SAFETY: *cur_str is a non-null pointer to a valid null-terminated C string (verified by the counting loop above) - str_vec.push(map_fn(unsafe { CStr::from_ptr(*cur_str) }.to_bytes().as_bstr())); - } - str_vec - } - - pub fn to_c_str(mut s: BString, f: impl FnOnce(*const libc::c_char) -> R) -> R { - s.push(0); - f(s.as_ptr().cast()) - } - - fn to_c_str_array( - mut strs: Vec, - f: impl FnOnce(*const *const libc::c_char) -> R, - ) -> R { - let mut ptr_vec = Vec::<*const libc::c_char>::with_capacity(strs.len() + 1); - for s in &mut strs { - s.push(0); - ptr_vec.push(s.as_ptr().cast()); - } - ptr_vec.push(null()); - f(ptr_vec.as_ptr()) - } - - pub unsafe fn to_exec(self) -> Exec { - // SAFETY: self.prog is a non-null pointer to a valid null-terminated C string, as guaranteed by the libc exec calling convention - let program = unsafe { CStr::from_ptr(self.prog) }.to_bytes().as_bstr().to_owned(); - - // SAFETY: self.argv is a valid null-terminated array of C string pointers, as guaranteed by the libc exec calling convention - let args = unsafe { Self::collect_c_str_array(self.argv, BStr::to_owned) }; - - // SAFETY: self.envp is a valid null-terminated array of C string pointers, as guaranteed by the libc exec calling convention - let envs = unsafe { - Self::collect_c_str_array(self.envp, |env| { - env.iter().position(|b| *b == b'=').map_or_else( - || (env.to_owned(), None), - |eq_pos| (env[..eq_pos].to_owned(), Some(env[(eq_pos + 1)..].to_owned())), - ) - }) - }; - - Exec { program, args, envs } - } - - pub fn from_exec(cmd: Exec, f: impl FnOnce(Self) -> R) -> R { - let envs: Vec = cmd - .envs - .into_iter() - .map(|(name, value)| { - let mut env = name; - if let Some(value) = value { - env.push(b'='); - env.extend_from_slice(&value); - } - env - }) - .collect(); - - Self::to_c_str(cmd.program, |prog| { - Self::to_c_str_array(cmd.args, |argv| { - Self::to_c_str_array(envs, |envp| f(Self { prog, argv, envp })) - }) - }) - } -} diff --git a/crates/fspy_preload_unix/src/interceptions/access.rs b/crates/fspy_preload_unix/src/interceptions/access.rs deleted file mode 100644 index 0cae39220..000000000 --- a/crates/fspy_preload_unix/src/interceptions/access.rs +++ /dev/null @@ -1,32 +0,0 @@ -use fspy_shared::ipc::AccessMode; -use libc::{c_char, c_int}; - -use crate::{ - client::{convert::PathAt, handle_open}, - macros::intercept, -}; - -intercept!(access(64): unsafe extern "C" fn(pathname: *const c_char, mode: c_int) -> c_int); -unsafe extern "C" fn access(pathname: *const c_char, mode: c_int) -> c_int { - // SAFETY: pathname is a valid C string pointer provided by the caller of the interposed function - unsafe { - handle_open(pathname, AccessMode::READ); - } - // SAFETY: calling the original libc access() with the same arguments forwarded from the interposed function - unsafe { access::original()(pathname, mode) } -} - -intercept!(faccessat(64): unsafe extern "C" fn(dirfd: c_int, pathname: *const c_char, mode: c_int, flags: c_int) -> c_int); -unsafe extern "C" fn faccessat( - dirfd: c_int, - pathname: *const c_char, - mode: c_int, - flags: c_int, -) -> c_int { - // SAFETY: dirfd and pathname are valid arguments provided by the caller of the interposed function - unsafe { - handle_open(PathAt(dirfd, pathname), AccessMode::READ); - } - // SAFETY: calling the original libc faccessat() with the same arguments forwarded from the interposed function - unsafe { faccessat::original()(dirfd, pathname, mode, flags) } -} diff --git a/crates/fspy_preload_unix/src/interceptions/dirent.rs b/crates/fspy_preload_unix/src/interceptions/dirent.rs deleted file mode 100644 index d2b11d39a..000000000 --- a/crates/fspy_preload_unix/src/interceptions/dirent.rs +++ /dev/null @@ -1,90 +0,0 @@ -use fspy_shared::ipc::AccessMode; -use libc::{DIR, c_char, c_int, c_long, c_void}; - -use crate::{ - client::{convert::Fd, handle_open}, - macros::intercept, -}; - -intercept!(scandir(64): unsafe extern "C" fn ( - dirname: *const c_char, - namelist: *mut c_void, - select: *const c_void, - compar: *const c_void, -) -> c_int); -unsafe extern "C" fn scandir( - dirname: *const c_char, - namelist: *mut c_void, - select: *const c_void, - compar: *const c_void, -) -> c_int { - // SAFETY: dirname is a valid C string pointer provided by the caller of the interposed function - unsafe { handle_open(dirname, AccessMode::READ_DIR) } - // SAFETY: calling the original libc scandir() with the same arguments forwarded from the interposed function - unsafe { scandir::original()(dirname, namelist, select, compar) } -} - -#[cfg(target_os = "macos")] -mod macos_only { - use super::{AccessMode, Fd, c_char, c_int, c_void, handle_open, intercept}; - - intercept!(scandir_b: unsafe extern "C" fn ( - dirname: *const c_char, - namelist: *mut c_void, - select: *const c_void, - compar: *const c_void, - ) -> c_int); - unsafe extern "C" fn scandir_b( - dirname: *const c_char, - namelist: *mut c_void, - select: *const c_void, - compar: *const c_void, - ) -> c_int { - // SAFETY: dirname is a valid C string pointer provided by the caller of the interposed function - unsafe { handle_open(dirname, AccessMode::READ_DIR) }; - // SAFETY: calling the original libc scandir_b() with the same arguments forwarded from the interposed function - unsafe { scandir_b::original()(dirname, namelist, select, compar) } - } - - intercept!(__getdirentries64: unsafe extern "C" fn(c_int, *mut u8, usize, *mut i64) -> isize); - unsafe extern "C" fn __getdirentries64( - fd: c_int, - buf: *mut u8, - buf_len: usize, - basep: *mut i64, - ) -> isize { - // SAFETY: fd is a valid file descriptor provided by the caller of __getdirentries64 - unsafe { handle_open(Fd(fd), AccessMode::READ_DIR) }; - // SAFETY: calling the original libc __getdirentries64() with the same arguments forwarded from the interposed function - unsafe { __getdirentries64::original()(fd, buf, buf_len, basep) } - } -} - -intercept!(getdirentries(64): unsafe extern "C" fn (fd: c_int, buf: *mut c_char, nbytes: c_int, basep: *mut c_long) -> c_int); -unsafe extern "C" fn getdirentries( - fd: c_int, - buf: *mut c_char, - nbytes: c_int, - basep: *mut c_long, -) -> c_int { - // SAFETY: fd is a valid file descriptor provided by the caller of the interposed function - unsafe { handle_open(Fd(fd), AccessMode::READ_DIR) }; - // SAFETY: calling the original libc getdirentries() with the same arguments forwarded from the interposed function - unsafe { getdirentries::original()(fd, buf, nbytes, basep) } -} - -intercept!(fdopendir(64): unsafe extern "C" fn (fd: c_int) -> *mut DIR); -unsafe extern "C" fn fdopendir(fd: c_int) -> *mut DIR { - // SAFETY: fd is a valid file descriptor provided by the caller of the interposed function - unsafe { handle_open(Fd(fd), AccessMode::READ_DIR) }; - // SAFETY: calling the original libc fdopendir() with the same arguments forwarded from the interposed function - unsafe { fdopendir::original()(fd) } -} - -intercept!(opendir(64): unsafe extern "C" fn (*const c_char) -> *mut DIR); -unsafe extern "C" fn opendir(dir_name: *const c_char) -> *mut DIR { - // SAFETY: dir_name is a valid C string pointer provided by the caller of the interposed function - unsafe { handle_open(dir_name, AccessMode::READ_DIR) }; - // SAFETY: calling the original libc opendir() with the same arguments forwarded from the interposed function - unsafe { opendir::original()(dir_name) } -} diff --git a/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs b/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs deleted file mode 100644 index 0731017d7..000000000 --- a/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs +++ /dev/null @@ -1,53 +0,0 @@ -use fspy_shared::ipc::AccessMode; -use libc::{c_char, c_int, c_long}; - -use crate::{ - client::{ - convert::{Fd, PathAt}, - handle_open, - }, - macros::intercept, -}; - -intercept!(syscall(64): unsafe extern "C" fn(c_long, args: ...) -> c_long); -unsafe extern "C" fn syscall(syscall_no: c_long, mut args: ...) -> c_long { - // https://github.com/bminor/glibc/blob/efc8642051e6c4fe5165e8986c1338ba2c180de6/sysdeps/unix/sysv/linux/syscall.c#L23 - // SAFETY: extracting variadic arguments matching the syscall ABI; the caller passes at least 6 c_long arguments - let a0 = unsafe { args.next_arg::() }; - // SAFETY: extracting variadic arguments matching the syscall ABI - let a1 = unsafe { args.next_arg::() }; - // SAFETY: extracting variadic arguments matching the syscall ABI - let a2 = unsafe { args.next_arg::() }; - // SAFETY: extracting variadic arguments matching the syscall ABI - let a3 = unsafe { args.next_arg::() }; - // SAFETY: extracting variadic arguments matching the syscall ABI - let a4 = unsafe { args.next_arg::() }; - // SAFETY: extracting variadic arguments matching the syscall ABI - let a5 = unsafe { args.next_arg::() }; - - if syscall_no == libc::SYS_statx { - // C-style conversions are expected for the variadic syscall arguments. - #[expect( - clippy::cast_possible_truncation, - reason = "C-style conversion from c_long syscall arguments to c_int" - )] - let dirfd = a0 as c_int; - let pathname = a1 as *const c_char; - #[expect( - clippy::cast_possible_truncation, - reason = "C-style conversion from c_long syscall arguments to c_int" - )] - let flags = a2 as c_int; - if pathname.is_null() { - if flags & libc::AT_EMPTY_PATH != 0 { - // SAFETY: dirfd is provided by the statx syscall caller. - unsafe { handle_open(Fd(dirfd), AccessMode::READ) }; - } - } else { - // SAFETY: pathname is a non-null C string pointer provided by the statx syscall caller. - unsafe { handle_open(PathAt(dirfd, pathname), AccessMode::READ) }; - } - } - // SAFETY: forwarding the syscall to the original libc syscall function with the extracted arguments - unsafe { syscall::original()(syscall_no, a0, a1, a2, a3, a4, a5) } -} diff --git a/crates/fspy_preload_unix/src/interceptions/mod.rs b/crates/fspy_preload_unix/src/interceptions/mod.rs deleted file mode 100644 index 0d3742ea7..000000000 --- a/crates/fspy_preload_unix/src/interceptions/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod access; -mod dirent; -mod open; -mod spawn; -mod stat; - -#[cfg(target_os = "linux")] -mod linux_syscall; diff --git a/crates/fspy_preload_unix/src/interceptions/open.rs b/crates/fspy_preload_unix/src/interceptions/open.rs deleted file mode 100644 index 641593a13..000000000 --- a/crates/fspy_preload_unix/src/interceptions/open.rs +++ /dev/null @@ -1,122 +0,0 @@ -use libc::FILE; - -use crate::{ - client::{ - convert::{ModeStr, OpenFlags, PathAt}, - handle_open, - }, - libc::{c_char, c_int}, - macros::intercept, -}; - -const fn has_mode_arg(o_flags: c_int) -> bool { - if o_flags & libc::O_CREAT != 0 { - return true; - } - #[cfg(target_os = "linux")] - if o_flags & libc::O_TMPFILE != 0 { - return true; - } - false -} - -#[cfg(not(target_os = "macos"))] -type Mode = libc::mode_t; -#[cfg(target_os = "macos")] // https://github.com/tailhook/openat/issues/21#issuecomment-535914957 -type Mode = c_int; - -intercept!(open(64): unsafe extern "C" fn(*const c_char, c_int, args: ...) -> c_int); -unsafe extern "C" fn open(path: *const c_char, flags: c_int, mut args: ...) -> c_int { - // SAFETY: path is a valid C string pointer provided by the caller of the interposed function - unsafe { handle_open(path, OpenFlags(flags)) }; - if has_mode_arg(flags) { - // SAFETY: when O_CREAT or O_TMPFILE is set, a mode_t argument is required by the open() contract - let mode: Mode = unsafe { args.next_arg() }; - // SAFETY: calling the original libc open() with the same arguments forwarded from the interposed function - unsafe { open::original()(path, flags, mode) } - } else { - // SAFETY: calling the original libc open() with the same arguments forwarded from the interposed function - unsafe { open::original()(path, flags) } - } -} - -intercept!(openat(64): unsafe extern "C" fn(c_int, *const c_char, c_int, ...) -> c_int); -unsafe extern "C" fn openat( - dirfd: c_int, - path: *const c_char, - flags: c_int, - mut args: ... -) -> c_int { - // SAFETY: dirfd and path are valid arguments provided by the caller of the interposed function - unsafe { handle_open(PathAt(dirfd, path), OpenFlags(flags)) }; - - if has_mode_arg(flags) { - // https://github.com/tailhook/openat/issues/21#issuecomment-535914957 - // SAFETY: when O_CREAT or O_TMPFILE is set, a mode_t argument is required by the openat() contract - let mode: Mode = unsafe { args.next_arg() }; - // SAFETY: calling the original libc openat() with the same arguments forwarded from the interposed function - unsafe { openat::original()(dirfd, path, flags, mode) } - } else { - // SAFETY: calling the original libc openat() with the same arguments forwarded from the interposed function - unsafe { openat::original()(dirfd, path, flags) } - } -} - -#[cfg(target_os = "macos")] -intercept!(open_nocancel: unsafe extern "C" fn(*const c_char, c_int, ...) -> c_int); -#[cfg(target_os = "macos")] -unsafe extern "C" fn open_nocancel(path: *const c_char, flags: c_int, mut args: ...) -> c_int { - // SAFETY: path is a valid C string pointer provided by the caller of open$NOCANCEL - unsafe { handle_open(path, OpenFlags(flags)) }; - if has_mode_arg(flags) { - // SAFETY: O_CREAT requires a mode argument, matching the open$NOCANCEL contract - let mode: Mode = unsafe { args.next_arg() }; - // SAFETY: calling the original libc open$NOCANCEL() with the same arguments forwarded from the interposed function - unsafe { open_nocancel::original()(path, flags, mode) } - } else { - // SAFETY: calling the original libc open$NOCANCEL() with the same arguments forwarded from the interposed function - unsafe { open_nocancel::original()(path, flags) } - } -} - -#[cfg(target_os = "macos")] -intercept!(openat_nocancel: unsafe extern "C" fn(c_int, *const c_char, c_int, ...) -> c_int); -#[cfg(target_os = "macos")] -unsafe extern "C" fn openat_nocancel( - dirfd: c_int, - path: *const c_char, - flags: c_int, - mut args: ... -) -> c_int { - // SAFETY: dirfd and path are valid arguments provided by the caller of openat$NOCANCEL - unsafe { handle_open(PathAt(dirfd, path), OpenFlags(flags)) }; - if has_mode_arg(flags) { - // SAFETY: O_CREAT requires a mode argument, matching the openat$NOCANCEL contract - let mode: Mode = unsafe { args.next_arg() }; - // SAFETY: calling the original libc openat$NOCANCEL() with the same arguments forwarded from the interposed function - unsafe { openat_nocancel::original()(dirfd, path, flags, mode) } - } else { - // SAFETY: calling the original libc openat$NOCANCEL() with the same arguments forwarded from the interposed function - unsafe { openat_nocancel::original()(dirfd, path, flags) } - } -} - -intercept!(fopen(64): unsafe extern "C" fn(path: *const c_char, mode: *const c_char) -> *mut FILE); -unsafe extern "C" fn fopen(path: *const c_char, mode: *const c_char) -> *mut libc::FILE { - // SAFETY: path and mode are valid C string pointers provided by the caller of the interposed function - unsafe { handle_open(path, ModeStr(mode)) }; - // SAFETY: calling the original libc fopen() with the same arguments forwarded from the interposed function - unsafe { fopen::original()(path, mode) } -} - -intercept!(freopen(64): unsafe extern "C" fn(path: *const c_char, mode: *const c_char, stream: *mut FILE) -> *mut FILE); -unsafe extern "C" fn freopen( - path: *const c_char, - mode: *const c_char, - stream: *mut FILE, -) -> *mut FILE { - // SAFETY: path and mode are valid C string pointers provided by the caller of the interposed function - unsafe { handle_open(path, ModeStr(mode)) }; - // SAFETY: calling the original libc freopen() with the same arguments forwarded from the interposed function - unsafe { freopen::original()(path, mode, stream) } -} diff --git a/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs b/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs deleted file mode 100644 index 182226eea..000000000 --- a/crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs +++ /dev/null @@ -1,240 +0,0 @@ -mod with_argv; - -#[cfg(target_os = "linux")] -use std::ffi::CString; - -use fspy_shared_unix::exec::ExecResolveConfig; -use libc::{c_char, c_int}; -use with_argv::with_argv; - -use crate::{ - client::{global_client, raw_exec::RawExec}, - macros::intercept, -}; - -#[cfg(target_os = "macos")] -pub unsafe fn environ() -> *const *const c_char { - // SAFETY: _NSGetEnviron() always returns a valid pointer to the process's environ on macOS - unsafe { *(libc::_NSGetEnviron().cast()) } -} - -#[cfg(target_os = "linux")] -pub unsafe fn environ() -> *const *const c_char { - unsafe extern "C" { - static environ: *const *const c_char; - } - // SAFETY: environ is a valid global pointer to the process environment, as defined by POSIX - unsafe { environ } -} - -fn handle_exec( - config: ExecResolveConfig, - prog: *const libc::c_char, - argv: *const *const libc::c_char, - envp: *const *const libc::c_char, -) -> libc::c_int { - let client = - global_client().expect("exec unexpectedly called before client initialized in ctor"); - // SAFETY: prog, argv, and envp are valid pointers to C strings/arrays forwarded from the interposed exec function - let result = unsafe { - client.handle_exec(config, RawExec { prog, argv, envp }, |raw_command, pre_exec| { - if let Some(pre_exec) = pre_exec { - pre_exec.run()?; - } - Ok(execve::original()(raw_command.prog, raw_command.argv, raw_command.envp)) - }) - }; - match result { - Ok(ret) => ret, - Err(errno) => { - errno.set(); - -1 - } - } -} - -intercept!(execve(64): unsafe extern "C" fn( - prog: *const libc::c_char, - argv: *const *const libc::c_char, - envp: *const *const libc::c_char, -) -> libc::c_int); -unsafe extern "C" fn execve( - prog: *const libc::c_char, - argv: *const *const libc::c_char, - envp: *const *const libc::c_char, -) -> libc::c_int { - handle_exec(ExecResolveConfig::search_path_disabled(), prog, argv, envp) -} - -intercept!(execl(64): unsafe extern "C" fn(path: *const c_char, arg0: *const c_char, ...) -> c_int); -unsafe extern "C" fn execl(path: *const c_char, arg0: *const c_char, valist: ...) -> c_int { - #[expect( - clippy::no_effect_underscore_binding, - reason = "suppresses unused warning on *::original" - )] - let _unused = execl::original; - // SAFETY: valist and arg0 are valid variadic arguments forwarded from the interposed execl function - unsafe { - with_argv(valist, arg0, |args, _remaining| { - handle_exec(ExecResolveConfig::search_path_disabled(), path, args.as_ptr(), environ()) - }) - } -} - -intercept!(execlp(64): unsafe extern "C" fn(path: *const c_char, arg0: *const c_char, ...) -> c_int); -unsafe extern "C" fn execlp(path: *const c_char, arg0: *const c_char, valist: ...) -> c_int { - #[expect( - clippy::no_effect_underscore_binding, - reason = "suppresses unused warning on *::original" - )] - let _unused = execlp::original; - // SAFETY: valist and arg0 are valid variadic arguments forwarded from the interposed execlp function - unsafe { - with_argv(valist, arg0, |args, _remaining| { - handle_exec( - ExecResolveConfig::search_path_enabled(None), - path, - args.as_ptr(), - environ(), - ) - }) - } -} - -intercept!(execle(64): unsafe extern "C" fn(path: *const c_char, arg0: *const c_char, ...) -> c_int); -unsafe extern "C" fn execle(path: *const c_char, arg0: *const c_char, valist: ...) -> c_int { - #[expect( - clippy::no_effect_underscore_binding, - reason = "suppresses unused warning on *::original" - )] - let _unused = execle::original; - // SAFETY: valist and arg0 are valid variadic arguments forwarded from the interposed execle function - unsafe { - with_argv(valist, arg0, |args, mut remaining| { - let envp = remaining.next_arg::<*const *const c_char>(); - handle_exec(ExecResolveConfig::search_path_disabled(), path, args.as_ptr(), envp) - }) - } -} - -intercept!(execv(64): unsafe extern "C" fn(path: *const c_char, argv: *const *const c_char) -> c_int); -unsafe extern "C" fn execv(path: *const c_char, argv: *const *const c_char) -> c_int { - #[expect( - clippy::no_effect_underscore_binding, - reason = "suppresses unused warning on *::original" - )] - let _unused = execv::original; - // SAFETY: path, argv are valid pointers forwarded from the interposed function; environ() returns the process environment - unsafe { handle_exec(ExecResolveConfig::search_path_disabled(), path, argv, environ()) } -} - -intercept!(execvp(64): unsafe extern "C" fn( - prog: *const libc::c_char, - argv: *const *const libc::c_char, -) -> c_int); -unsafe extern "C" fn execvp(prog: *const c_char, argv: *const *const c_char) -> c_int { - #[expect( - clippy::no_effect_underscore_binding, - reason = "suppresses unused warning on *::original" - )] - let _unused = execvp::original; - // SAFETY: environ() returns the valid process environment pointer - handle_exec(ExecResolveConfig::search_path_enabled(None), prog, argv, unsafe { environ() }) -} - -#[cfg(target_os = "linux")] -mod linux_only { - #[expect( - clippy::useless_attribute, - reason = "allow_attributes on use items is flagged as useless but needed here" - )] - #[expect( - clippy::allow_attributes, - reason = "using allow because wildcard_imports may or may not fire depending on build target" - )] - #[allow( - clippy::wildcard_imports, - reason = "macro-generated code requires types from parent scope" - )] - use super::*; - use crate::client::convert::{PathAt, ToAbsolutePath}; - - intercept!(execvpe(64): unsafe extern "C" fn( - prog: *const libc::c_char, - argv: *const *const libc::c_char, - envp: *const *const libc::c_char, - ) -> libc::c_int); - unsafe extern "C" fn execvpe( - file: *const c_char, - argv: *const *const libc::c_char, - envp: *const *const libc::c_char, - ) -> c_int { - #[expect( - clippy::no_effect_underscore_binding, - reason = "suppresses unused warning on *::original" - )] - let _unused = execvpe::original; - handle_exec(ExecResolveConfig::search_path_enabled(None), file, argv, envp) - } - intercept!(execveat(64): unsafe extern "C" fn( - dirfd: c_int, - prog: *const libc::c_char, - argv: *const *mut libc::c_char, - envp: *const *mut libc::c_char, - flags: c_int - ) -> libc::c_int); - unsafe extern "C" fn execveat( - dirfd: c_int, - pathname: *const libc::c_char, - argv: *const *mut libc::c_char, - envp: *const *mut libc::c_char, - flags: c_int, // TODO: conform to semantics of flags - ) -> libc::c_int { - #[expect( - clippy::no_effect_underscore_binding, - reason = "suppresses unused warning on *::original" - )] - let _unused = execveat::original; - // SAFETY: PathAt wraps a valid dirfd and pathname pointer from the interposed execveat call - let abs_path_result = unsafe { - PathAt(dirfd, pathname).to_absolute_path(|path| { - let Some(path) = path else { - return Ok(None); - }; - Ok(Some(CString::new(&**path).unwrap())) - }) - }; - let abs_path = match abs_path_result { - Ok(None) => { - // SAFETY: forwarding the original arguments to the real execveat syscall - return unsafe { execveat::original()(dirfd, pathname, argv, envp, flags) }; - } - Ok(Some(path)) => path.as_ptr(), - Err(errno) => { - errno.set(); - return -1; - } - }; - handle_exec(ExecResolveConfig::search_path_disabled(), abs_path, argv.cast(), envp.cast()) - } - - intercept!(fexecve(64): unsafe extern "C" fn( - fd: c_int, - argv: *const *const libc::c_char, - envp: *const *const libc::c_char, - ) -> libc::c_int); - unsafe extern "C" fn fexecve( - fd: c_int, - argv: *const *const libc::c_char, - envp: *const *const libc::c_char, - ) -> libc::c_int { - #[expect( - clippy::no_effect_underscore_binding, - reason = "suppresses unused warning on *::original" - )] - let _unused = fexecve::original; - let prog = format!("/proc/self/fd/{fd}\0"); - let prog = prog.as_ptr(); - handle_exec(ExecResolveConfig::search_path_disabled(), prog.cast(), argv, envp) - } -} diff --git a/crates/fspy_preload_unix/src/interceptions/spawn/exec/with_argv.rs b/crates/fspy_preload_unix/src/interceptions/spawn/exec/with_argv.rs deleted file mode 100644 index c3aba13ea..000000000 --- a/crates/fspy_preload_unix/src/interceptions/spawn/exec/with_argv.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::{ - ffi::VaList, - mem::{self, MaybeUninit}, - slice, -}; - -use libc::{c_char, c_int}; -use nix::Error; - -// https://github.com/redox-os/relibc/blob/710911febb07a43716a6236cc9e5b864e227e36e/src/header/unistd/mod.rs#L1094 -#[expect(clippy::similar_names, reason = "arg0 and argc are standard C naming conventions")] -pub unsafe fn with_argv( - mut va: VaList, - arg0: *const c_char, - f: impl FnOnce(&[*const c_char], VaList) -> c_int, -) -> c_int { - let argc = 1 + { - let mut va = va.clone(); - // Safety: argv is guaranteed to be NULL-terminated - core::iter::from_fn(|| Some(unsafe { va.next_arg::<*const c_char>() })) - .position(|s| { - // Find the NULL terminator - s.is_null() - }) - .unwrap() - }; - - let mut stack: [MaybeUninit<*const c_char>; 32] = [MaybeUninit::uninit(); 32]; - - let out = if argc < 32 { - stack.as_mut_slice() - } else if argc < 4096 { - // TODO: Use ARG_MAX, not this hardcoded constant - // SAFETY: requesting a heap allocation of the correct size for argc pointers - let ptr = unsafe { libc::malloc(argc * mem::size_of::<*const c_char>()) }; - if ptr.is_null() { - Error::ENOMEM.set(); - return -1; - } - // SAFETY: ptr is non-null (checked above), properly aligned, and points to argc elements worth of allocated memory - unsafe { slice::from_raw_parts_mut(ptr.cast::>(), argc) } - } else { - Error::E2BIG.set(); - return -1; - }; - out[0].write(arg0); - - for item in out.iter_mut().take(argc).skip(1) { - // SAFETY: extracting the next *const c_char argument from the va_list; the count was pre-validated - item.write(unsafe { va.next_arg::<*const c_char>() }); - } - out[argc].write(core::ptr::null()); - // SAFETY: consuming the NULL terminator from the va_list to advance past it - unsafe { va.next_arg::<*const c_char>() }; - - // Safety: MaybeUninit<*const c_char> has the same layout as *const c_char, - // and all elements have been initialized via write() above. - f(unsafe { &*(&raw const *out as *const [*const c_char]) }, va); - - // f only returns if it fails - if argc >= 32 { - // SAFETY: out was allocated with libc::malloc above (argc >= 32 branch), so it must be freed with libc::free - unsafe { libc::free(out.as_mut_ptr().cast()) }; - } - -1 -} diff --git a/crates/fspy_preload_unix/src/interceptions/spawn/mod.rs b/crates/fspy_preload_unix/src/interceptions/spawn/mod.rs deleted file mode 100644 index 13a40774d..000000000 --- a/crates/fspy_preload_unix/src/interceptions/spawn/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod exec; -mod posix_spawn; diff --git a/crates/fspy_preload_unix/src/interceptions/spawn/posix_spawn.rs b/crates/fspy_preload_unix/src/interceptions/spawn/posix_spawn.rs deleted file mode 100644 index 9496b1153..000000000 --- a/crates/fspy_preload_unix/src/interceptions/spawn/posix_spawn.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::thread; - -use fspy_shared_unix::exec::ExecResolveConfig; -use libc::{c_char, c_int}; - -use crate::{ - client::{global_client, raw_exec::RawExec}, - macros::intercept, -}; - -type PosixSpawnFn = unsafe extern "C" fn( - pid: *mut libc::pid_t, - prog: *const c_char, - file_actions: *const libc::posix_spawn_file_actions_t, - attrp: *const libc::posix_spawnattr_t, - argv: *const *mut c_char, - envp: *const *mut c_char, -) -> libc::c_int; - -#[expect( - clippy::too_many_arguments, - reason = "mirrors the posix_spawn(3) signature which requires all these parameters" -)] -unsafe fn handle_posix_spawn( - config: ExecResolveConfig, - original: PosixSpawnFn, - pid: *mut libc::pid_t, - file: *const c_char, - file_actions: *const libc::posix_spawn_file_actions_t, - attrp: *const libc::posix_spawnattr_t, - argv: *const *mut c_char, - envp: *const *mut c_char, -) -> c_int { - struct AssertSend(T); - #[expect( - clippy::non_send_fields_in_send_ty, - reason = "the closure captures raw pointers that are valid for the duration of the thread::scope call, so sending them to the scoped thread is safe" - )] - // SAFETY: the raw pointers captured inside T are valid for the duration of the thread::scope call, so sending them to the scoped thread is safe - unsafe impl Send for AssertSend {} - - let client = global_client() - .expect("posix_spawn(p) unexpectedly called before client initialized in ctor"); - - // SAFETY: file, argv, and envp are valid pointers forwarded from the interposed posix_spawn(p) function - let result = unsafe { - client.handle_exec::( - config, - RawExec { prog: file, argv: argv.cast(), envp: envp.cast() }, - |raw_command, pre_exec| { - let call_original = move || { - original( - pid, - raw_command.prog, - file_actions, - attrp, - raw_command.argv.cast(), - raw_command.envp.cast(), - ) - }; - if let Some(pre_exec) = pre_exec { - thread::scope(move |s| { - let call_original = AssertSend(call_original); - s.spawn(move || { - let call_original = call_original; - pre_exec.run()?; - - nix::Result::Ok((call_original.0)()) - }) - .join() - .unwrap() - }) - } else { - Ok(call_original()) - } - }, - ) - }; - match result { - Err(errno) => errno as _, - Ok(ret) => ret, - } -} - -intercept!(posix_spawnp(64): PosixSpawnFn); -unsafe extern "C" fn posix_spawnp( - pid: *mut libc::pid_t, - file: *const c_char, - file_actions: *const libc::posix_spawn_file_actions_t, - attrp: *const libc::posix_spawnattr_t, - argv: *const *mut c_char, - envp: *const *mut c_char, -) -> libc::c_int { - // SAFETY: all arguments are valid pointers forwarded from the interposed posix_spawnp function - unsafe { - handle_posix_spawn( - ExecResolveConfig::search_path_enabled(None), - posix_spawnp::original(), - pid, - file, - file_actions, - attrp, - argv, - envp, - ) - } -} - -intercept!(posix_spawn(64): PosixSpawnFn); -unsafe extern "C" fn posix_spawn( - pid: *mut libc::pid_t, - file: *const c_char, - file_actions: *const libc::posix_spawn_file_actions_t, - attrp: *const libc::posix_spawnattr_t, - argv: *const *mut c_char, - envp: *const *mut c_char, -) -> libc::c_int { - // SAFETY: all arguments are valid pointers forwarded from the interposed posix_spawn function - unsafe { - handle_posix_spawn( - ExecResolveConfig::search_path_disabled(), - posix_spawn::original(), - pid, - file, - file_actions, - attrp, - argv, - envp, - ) - } -} diff --git a/crates/fspy_preload_unix/src/interceptions/stat.rs b/crates/fspy_preload_unix/src/interceptions/stat.rs deleted file mode 100644 index ac4af7651..000000000 --- a/crates/fspy_preload_unix/src/interceptions/stat.rs +++ /dev/null @@ -1,82 +0,0 @@ -use fspy_shared::ipc::AccessMode; -use libc::{c_char, c_int, stat as stat_struct}; - -#[cfg(target_os = "linux")] -use crate::client::convert::Fd; -use crate::{ - client::{convert::PathAt, handle_open}, - macros::intercept, -}; - -intercept!(stat(64): unsafe extern "C" fn(path: *const c_char, buf: *mut stat_struct) -> c_int); -unsafe extern "C" fn stat(path: *const c_char, buf: *mut stat_struct) -> c_int { - // SAFETY: path is a valid C string pointer provided by the caller of the interposed function - unsafe { - handle_open(path, AccessMode::READ); - } - // SAFETY: calling the original libc stat() with the same arguments forwarded from the interposed function - unsafe { stat::original()(path, buf) } -} - -intercept!(lstat(64): unsafe extern "C" fn(path: *const c_char, buf: *mut stat_struct) -> c_int); -unsafe extern "C" fn lstat(path: *const c_char, buf: *mut stat_struct) -> c_int { - // TODO: add accessmode ReadNoFollow - // SAFETY: path is a valid C string pointer provided by the caller of the interposed function - unsafe { - handle_open(path, AccessMode::READ); - } - // SAFETY: calling the original libc lstat() with the same arguments forwarded from the interposed function - unsafe { lstat::original()(path, buf) } -} - -intercept!(fstatat(64): unsafe extern "C" fn(dirfd: c_int, pathname: *const c_char, buf: *mut stat_struct, flags: c_int) -> c_int); -unsafe extern "C" fn fstatat( - dirfd: c_int, - pathname: *const c_char, - buf: *mut stat_struct, - flags: c_int, -) -> c_int { - // SAFETY: dirfd and pathname are valid arguments provided by the caller of the interposed function - unsafe { - handle_open(PathAt(dirfd, pathname), AccessMode::READ); - } - // SAFETY: calling the original libc fstatat() with the same arguments forwarded from the interposed function - unsafe { fstatat::original()(dirfd, pathname, buf, flags) } -} - -#[cfg(target_os = "linux")] -intercept!(statx: unsafe extern "C" fn( - dirfd: c_int, - pathname: *const c_char, - flags: c_int, - mask: libc::c_uint, - statxbuf: *mut libc::statx, -) -> c_int); -#[cfg(target_os = "linux")] -unsafe extern "C" fn statx( - dirfd: c_int, - pathname: *const c_char, - flags: c_int, - mask: libc::c_uint, - statxbuf: *mut libc::statx, -) -> c_int { - let Some(original) = statx::try_original() else { - // Rust's standard library interprets ENOSYS from its statx availability - // probe as unsupported and falls back to stat64. - // SAFETY: __errno_location returns the calling thread's errno storage on Linux. - unsafe { *libc::__errno_location() = libc::ENOSYS }; - return -1; - }; - - if pathname.is_null() { - if flags & libc::AT_EMPTY_PATH != 0 { - // SAFETY: dirfd is provided by the statx caller. - unsafe { handle_open(Fd(dirfd), AccessMode::READ) }; - } - } else { - // SAFETY: pathname is a non-null C string pointer provided by the statx caller. - unsafe { handle_open(PathAt(dirfd, pathname), AccessMode::READ) }; - } - // SAFETY: calling the original libc statx() with the same arguments forwarded from the interposed function - unsafe { original(dirfd, pathname, flags, mask, statxbuf) } -} diff --git a/crates/fspy_preload_unix/src/lib.rs b/crates/fspy_preload_unix/src/lib.rs deleted file mode 100644 index 42bf9e9cb..000000000 --- a/crates/fspy_preload_unix/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Compile as an empty crate on non-unix targets and on musl (where seccomp -// alone handles access tracking). Guarding the feature gate keeps rustc from -// warning about unused features on those targets. -#![cfg_attr(all(unix, not(target_env = "musl")), feature(c_variadic))] - -#[cfg(all(unix, not(target_env = "musl")))] -mod client; -#[cfg(all(unix, not(target_env = "musl")))] -mod interceptions; -#[cfg(all(unix, not(target_env = "musl")))] -mod libc; -#[cfg(all(unix, not(target_env = "musl")))] -mod macros; diff --git a/crates/fspy_preload_unix/src/libc.rs b/crates/fspy_preload_unix/src/libc.rs deleted file mode 100644 index 656779817..000000000 --- a/crates/fspy_preload_unix/src/libc.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub use libc::*; - -unsafe extern "C" { - // On macOS x86_64, directory functions use $INODE64 symbol suffix for 64-bit inode support. - // On arm64, 64-bit inodes are the only option so no suffix is needed. - // https://github.com/apple-open-source-mirror/Libc/blob/5e566be7a7047360adfb35ffc44c6a019a854bea/include/dirent.h#L198 - #[cfg_attr(all(target_os = "macos", target_arch = "x86_64"), link_name = "scandir$INODE64")] - pub unsafe fn scandir( - dirname: *const c_char, - namelist: *mut c_void, - select: *const c_void, - compar: *const c_void, - ) -> c_int; - - #[cfg(target_os = "macos")] - #[cfg_attr(target_arch = "x86_64", link_name = "scandir_b$INODE64")] - pub unsafe fn scandir_b( - dirname: *const c_char, - namelist: *mut c_void, - select: *const c_void, - compar: *const c_void, - ) -> c_int; - - pub unsafe fn getdirentries( - fd: c_int, - buf: *mut c_char, - nbytes: c_int, - basep: *mut c_long, - ) -> c_int; - - #[cfg(target_os = "macos")] - #[link_name = "open$NOCANCEL"] - pub unsafe fn open_nocancel(path: *const c_char, flags: c_int, ...) -> c_int; - - #[cfg(target_os = "macos")] - #[link_name = "openat$NOCANCEL"] - pub unsafe fn openat_nocancel(dirfd: c_int, path: *const c_char, flags: c_int, ...) -> c_int; - - #[cfg(target_os = "macos")] - pub unsafe fn __getdirentries64( - fd: c_int, - buf: *mut u8, - buf_len: usize, - basep: *mut i64, - ) -> isize; -} diff --git a/crates/fspy_preload_unix/src/macros/linux.rs b/crates/fspy_preload_unix/src/macros/linux.rs deleted file mode 100644 index 72881d62a..000000000 --- a/crates/fspy_preload_unix/src/macros/linux.rs +++ /dev/null @@ -1,117 +0,0 @@ -macro_rules! intercept { - ($name: ident (64): $fn_sig: ty) => { - $crate::macros::intercept_inner! { - $name: $fn_sig; - - #[cfg(test)] - #[test] - fn symbol_64_exists() { - ::core::assert!($crate::macros::symbol_exists(::core::stringify!($name))); - } - } - #[cfg(not(test))] // Don't interpose on the test binary - const _: () = { - #[unsafe(naked)] - #[unsafe(export_name = ::core::concat!(::core::stringify!($name), 64))] - pub unsafe extern "C" fn interpose_fn() { - #[cfg(target_arch = "aarch64")] - ::core::arch::naked_asm!("b {}", sym $name); - #[cfg(target_arch = "x86_64")] - ::core::arch::naked_asm!("jmp {}", sym $name); - } - }; - }; - ($name: ident: $fn_sig: ty) => { - $crate::macros::intercept_inner! { - $name: $fn_sig; - - #[cfg(test)] - #[test] - fn symbol_64_does_not_exist() { - ::core::assert_eq!( - $crate::macros::symbol_exists(::core::concat!(::core::stringify!($name), 64)), - false, - ); - } - } - }; -} - -pub(crate) use intercept; - -#[cfg(test)] -#[doc(hidden)] -pub fn symbol_exists(name: &str) -> bool { - use std::ffi::CString; - - let name = CString::new(name).unwrap(); - // SAFETY: dlsym with RTLD_DEFAULT searches for the symbol in the default shared object search order - !unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr().cast()) }.is_null() -} - -macro_rules! intercept_inner { - ($name: ident: $fn_sig: ty; $test_fn: item) => { - const _: $fn_sig = $name; - const _: $fn_sig = $crate::libc::$name; - - #[cfg(not(test))] // Don't interpose on the test binary - const _: () = { - #[unsafe(naked)] - #[unsafe(export_name = ::core::stringify!($name))] - pub unsafe extern "C" fn interpose_fn() { - #[cfg(target_arch = "aarch64")] - ::core::arch::naked_asm!("b {}", sym $name); - #[cfg(target_arch = "x86_64")] - ::core::arch::naked_asm!("jmp {}", sym $name); - } - }; - mod $name { - #[expect(clippy::allow_attributes, reason = "using allow because unused_imports may or may not fire depending on macro expansion")] - #[allow(unused_imports, reason = "glob import brings types into scope for macro-generated code")] - use super::*; - #[expect( - clippy::allow_attributes, - reason = "using allow because dead_code only fires for optional original symbols" - )] - #[allow( - dead_code, - reason = "not every interposer forwards to its generated original function" - )] - pub unsafe fn original() -> $fn_sig { - try_original().unwrap_or_else(|| { - panic!(::core::concat!( - "original symbol not found: ", - ::core::stringify!($name) - )) - }) - } - pub fn try_original() -> ::core::option::Option<$fn_sig> { - static LAZY: std::sync::LazyLock<::core::option::Option<$fn_sig>> = - std::sync::LazyLock::new(|| { - // SAFETY: dlsym with RTLD_NEXT returns the next symbol in the dynamic - // linking order. A non-null pointer has the signature checked by the - // macro invocation. - let symbol = unsafe { - ::libc::dlsym( - ::libc::RTLD_NEXT, - ::core::concat!(::core::stringify!($name), "\0").as_ptr().cast(), - ) - }; - if symbol.is_null() { - ::core::option::Option::None - } else { - // SAFETY: the symbol name and function signature are paired by the - // macro invocation, and null was checked above. - ::core::option::Option::Some(unsafe { - ::core::mem::transmute::<*mut ::libc::c_void, $fn_sig>(symbol) - }) - } - }); - *LAZY - } - $test_fn - } - }; -} - -pub(crate) use intercept_inner; diff --git a/crates/fspy_preload_unix/src/macros/macos.rs b/crates/fspy_preload_unix/src/macros/macos.rs deleted file mode 100644 index fd3191192..000000000 --- a/crates/fspy_preload_unix/src/macros/macos.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::os::raw::c_void; - -// $crate::macros:: -macro_rules! intercept { - ($name: ident $((64))? : $fn_sig: ty) => { - const _: () = { - const _: $fn_sig = $name; - const _: $fn_sig = $crate::libc::$name; - - #[used] - #[unsafe(link_section = "__DATA,__interpose")] - static mut _INTERPOSE_ENTRY: $crate::macros::InterposeEntry = - $crate::macros::InterposeEntry { _new: $name as _, _old: $crate::libc::$name as _ }; - }; - - mod $name { - // macro-generated: imports may or may not be used depending on expansion context - #[expect(clippy::allow_attributes, reason = "macro-generated: imports may or may not be used depending on expansion context")] - #[allow(unused_imports, reason = "macro-generated: imports may or may not be used depending on expansion context")] - use super::*; - pub fn original() -> $fn_sig { - $crate::libc::$name - } - } - }; -} - -pub(crate) use intercept; - -#[doc(hidden)] -#[repr(C)] -pub struct InterposeEntry { - pub _new: *const c_void, - pub _old: *const c_void, -} diff --git a/crates/fspy_preload_unix/src/macros/mod.rs b/crates/fspy_preload_unix/src/macros/mod.rs deleted file mode 100644 index 5b3fa805f..000000000 --- a/crates/fspy_preload_unix/src/macros/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -#[cfg(target_os = "macos")] -#[path = "./macos.rs"] -mod os_impl; - -#[cfg(target_os = "linux")] -#[path = "./linux.rs"] -mod os_impl; - -#[expect( - clippy::redundant_pub_crate, - reason = "macro_rules! macros cannot be `pub`, only `pub(crate)` at most" -)] -pub(crate) use os_impl::*; diff --git a/crates/fspy_preload_windows/.clippy.toml b/crates/fspy_preload_windows/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/fspy_preload_windows/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/fspy_preload_windows/Cargo.toml b/crates/fspy_preload_windows/Cargo.toml deleted file mode 100644 index 9dd1c50cc..000000000 --- a/crates/fspy_preload_windows/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "fspy_preload_windows" -version = "0.1.0" -edition.workspace = true -license.workspace = true -publish = false - -[lib] -crate-type = ["cdylib"] - -[target.'cfg(target_os = "windows")'.dependencies] -wincode = { workspace = true } -constcat = { workspace = true } -fspy_detours_sys = { workspace = true } -fspy_shared = { workspace = true } -ntapi = { workspace = true } -smallvec = { workspace = true } -widestring = { workspace = true } -winapi = { workspace = true, features = [ - "winerror", - "winbase", - "namedpipeapi", - "memoryapi", - "std", -] } -windows-sys = { workspace = true, features = ["Win32_Foundation", "Win32_UI_Shell"] } -winsafe = { workspace = true } - -[target.'cfg(target_os = "windows")'.dev-dependencies] -tempfile = { workspace = true } - -[lints] -workspace = true diff --git a/crates/fspy_preload_windows/README.md b/crates/fspy_preload_windows/README.md deleted file mode 100644 index 845484d70..000000000 --- a/crates/fspy_preload_windows/README.md +++ /dev/null @@ -1,5 +0,0 @@ -## fspy_preload_windows - -The shared library injected by `Detours` on Windows. - -This crates only contains code the shared library itself. The injection process is done in `fspy` crate. diff --git a/crates/fspy_preload_windows/build.rs b/crates/fspy_preload_windows/build.rs deleted file mode 100644 index 75269058f..000000000 --- a/crates/fspy_preload_windows/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - if std::env::var_os("CARGO_CFG_TARGET_OS").unwrap() == "windows" { - println!("cargo:rustc-cdylib-link-arg=/EXPORT:DetourFinishHelperProcess,@1,NONAME"); - } -} diff --git a/crates/fspy_preload_windows/src/lib.rs b/crates/fspy_preload_windows/src/lib.rs deleted file mode 100644 index 04957cd85..000000000 --- a/crates/fspy_preload_windows/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![cfg(windows)] -#![feature(sync_unsafe_cell)] - -pub mod windows; diff --git a/crates/fspy_preload_windows/src/windows/client.rs b/crates/fspy_preload_windows/src/windows/client.rs deleted file mode 100644 index 48933414e..000000000 --- a/crates/fspy_preload_windows/src/windows/client.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::{cell::SyncUnsafeCell, ffi::CStr, mem::MaybeUninit}; - -use fspy_detours_sys::DetourCopyPayloadToProcess; -use fspy_shared::{ - ipc::{PathAccess, channel::Sender}, - windows::{PAYLOAD_ID, Payload}, -}; -use winapi::{shared::minwindef::BOOL, um::winnt::HANDLE}; - -pub struct Client<'a> { - payload: Payload<'a>, - ipc_sender: Option, -} - -impl<'a> Client<'a> { - pub fn from_payload_bytes(payload_bytes: &'a [u8]) -> Self { - let payload: Payload<'a> = wincode::deserialize_exact(payload_bytes).unwrap(); - - let ipc_sender = match payload.channel_conf.sender() { - Ok(sender) => Some(sender), - Err(err) => { - // this can happen if the process is started after the root target process has exited. - // By that time the channel would have been closed in the receiver side. - // In this case we just leave a message and skip sending any path accesses. - #[expect( - clippy::print_stderr, - reason = "preload library uses stderr for debug diagnostics" - )] - { - eprintln!("fspy: failed to create ipc sender: {err}"); - } - None - } - }; - - Self { payload, ipc_sender } - } - - pub fn send(&self, access: PathAccess<'_>) { - let Some(sender) = &self.ipc_sender else { - return; - }; - sender.write_encoded(&access).expect("failed to send path access"); - } - - pub unsafe fn prepare_child_process(&self, child_handle: HANDLE) -> BOOL { - let payload_bytes = wincode::serialize(&self.payload).unwrap(); - // SAFETY: FFI call to DetourCopyPayloadToProcess with valid handle and payload buffer - unsafe { - DetourCopyPayloadToProcess( - child_handle, - &PAYLOAD_ID, - payload_bytes.as_ptr().cast(), - payload_bytes.len().try_into().unwrap(), - ) - } - } - - pub const fn ansi_dll_path(&self) -> &'a CStr { - // SAFETY: payload.ansi_dll_path_with_nul is guaranteed to be a valid null-terminated byte string - unsafe { CStr::from_bytes_with_nul_unchecked(self.payload.ansi_dll_path_with_nul) } - } -} - -static CLIENT: SyncUnsafeCell>> = - SyncUnsafeCell::new(MaybeUninit::uninit()); - -pub unsafe fn set_global_client(client: Client<'static>) { - // SAFETY: called once during DLL_PROCESS_ATTACH before any concurrent access - unsafe { *CLIENT.get() = MaybeUninit::new(client) } -} - -pub unsafe fn global_client() -> &'static Client<'static> { - // SAFETY: CLIENT is initialized via set_global_client during DLL_PROCESS_ATTACH - unsafe { (*CLIENT.get()).assume_init_ref() } -} diff --git a/crates/fspy_preload_windows/src/windows/convert.rs b/crates/fspy_preload_windows/src/windows/convert.rs deleted file mode 100644 index 72a095786..000000000 --- a/crates/fspy_preload_windows/src/windows/convert.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::fmt::Debug; - -use fspy_shared::ipc::AccessMode; -use widestring::{U16CStr, U16CString, U16Str}; -use winapi::{ - shared::ntdef::{HANDLE, POBJECT_ATTRIBUTES}, - um::winnt::ACCESS_MASK, -}; - -use crate::windows::winapi_utils::{ - access_mask_to_mode, combine_paths, get_path_name, get_u16_str, -}; - -pub trait ToAccessMode: Debug { - unsafe fn to_access_mode(self) -> AccessMode; -} - -impl ToAccessMode for AccessMode { - unsafe fn to_access_mode(self) -> AccessMode { - self - } -} - -impl ToAccessMode for ACCESS_MASK { - unsafe fn to_access_mode(self) -> AccessMode { - access_mask_to_mode(self) - } -} - -pub trait ToAbsolutePath { - unsafe fn to_absolute_path) -> winsafe::SysResult>( - self, - f: F, - ) -> winsafe::SysResult; -} - -impl ToAbsolutePath for HANDLE { - unsafe fn to_absolute_path) -> winsafe::SysResult>( - self, - f: F, - ) -> winsafe::SysResult { - // SAFETY: get_path_name performs FFI call with this HANDLE to retrieve the file path - let resolved = unsafe { get_path_name(self) }.ok(); - let resolved = resolved.as_ref().map(|p| U16Str::from_slice(p)); - f(resolved) - } -} - -impl ToAbsolutePath for POBJECT_ATTRIBUTES { - unsafe fn to_absolute_path) -> winsafe::SysResult>( - self, - f: F, - ) -> winsafe::SysResult { - // SAFETY: dereferencing POBJECT_ATTRIBUTES to read ObjectName field from Windows API struct - let fname_str = unsafe { (*self).ObjectName.as_ref() }.map_or_else( - || U16Str::from_slice(&[]), - |object_name| { - // SAFETY: reading UNICODE_STRING fields from a valid OBJECT_ATTRIBUTES - unsafe { get_u16_str(object_name) } - }, - ); - let fname_slice = fname_str.as_slice(); - let is_absolute = fname_slice.first() == Some(&b'\\'.into()) // \... - || fname_slice.get(1) == Some(&b':'.into()); // C:... - - if is_absolute { - f(Some(fname_str)) - } else { - // SAFETY: dereferencing POBJECT_ATTRIBUTES to read RootDirectory handle - let Ok(mut root_dir) = (unsafe { get_path_name((*self).RootDirectory) }) else { - return f(None); - }; - // If filename is empty, just use root_dir directly - if fname_str.is_empty() { - let root_dir_str = U16Str::from_slice(&root_dir); - return f(Some(root_dir_str)); - } - let root_dir_cstr = { - root_dir.push(0); - // SAFETY: we just pushed a null terminator, so the buffer is null-terminated - unsafe { U16CStr::from_ptr_str(root_dir.as_ptr()) } - }; - let fname_cstring = U16CString::from_ustr_truncate(fname_str); - let abs_path = combine_paths(root_dir_cstr, fname_cstring.as_ucstr()).unwrap(); - f(Some(abs_path.to_u16_str())) - } - } -} diff --git a/crates/fspy_preload_windows/src/windows/detour.rs b/crates/fspy_preload_windows/src/windows/detour.rs deleted file mode 100644 index d7a8c8a30..000000000 --- a/crates/fspy_preload_windows/src/windows/detour.rs +++ /dev/null @@ -1,125 +0,0 @@ -use std::{cell::UnsafeCell, ffi::CStr, mem::transmute_copy, os::raw::c_void, ptr::null_mut}; - -use fspy_detours_sys::{DetourAttach, DetourDetach}; -use winapi::{ - shared::minwindef::HMODULE, - um::libloaderapi::{GetProcAddress, LoadLibraryA}, -}; -use winsafe::SysResult; - -use crate::windows::winapi_utils::ck_long; - -// SAFETY: Detour is only mutated during DLL attach/detach (single-threaded DLL_PROCESS_ATTACH) -unsafe impl Sync for Detour {} -pub struct Detour { - symbol_name: &'static CStr, - target: UnsafeCell<*mut c_void>, - new: T, -} - -impl Detour { - pub const unsafe fn new(symbol_name: &'static CStr, target: T, new: T) -> Self { - // SAFETY: transmute_copy reinterprets the function pointer as *mut c_void for Detours API - Self { symbol_name, target: UnsafeCell::new(unsafe { transmute_copy(&target) }), new } - } - - pub const unsafe fn dynamic(symbol_name: &'static CStr, new: T) -> Self { - Self { symbol_name, target: UnsafeCell::new(null_mut()), new } - } - - #[must_use] - pub fn real(&self) -> &T { - // SAFETY: target is initialized during Detour construction or attach; read-only after attach - unsafe { &(*self.target.get().cast::()) } - } - - pub const fn as_any(&'static self) -> DetourAny - where - T: Copy, - { - DetourAny { - symbol_name: std::ptr::addr_of!(self.symbol_name), - target: self.target.get(), - new: (&raw const self.new).cast(), - } - } -} - -#[derive(Clone, Copy)] -pub struct DetourAny { - symbol_name: *const &'static CStr, - target: *mut *mut c_void, - new: *const *mut c_void, -} - -pub struct AttachContext { - kernelbase: HMODULE, - kernel32: HMODULE, - ntdll: HMODULE, -} - -impl AttachContext { - #[must_use] - pub fn new() -> Self { - // SAFETY: LoadLibraryA is safe to call with valid C string pointers to system DLLs - let kernelbase = unsafe { LoadLibraryA(c"kernelbase".as_ptr()) }; - // SAFETY: LoadLibraryA is safe to call with valid C string pointers to system DLLs - let kernel32 = unsafe { LoadLibraryA(c"kernel32".as_ptr()) }; - // SAFETY: LoadLibraryA is safe to call with valid C string pointers to system DLLs - let ntdll = unsafe { LoadLibraryA(c"ntdll".as_ptr()) }; - assert_ne!(kernelbase, null_mut()); - assert_ne!(kernel32, null_mut()); - assert_ne!(ntdll, null_mut()); - Self { kernelbase, kernel32, ntdll } - } -} - -// SAFETY: DetourAny is only used during DLL attach/detach (single-threaded DLL_PROCESS_ATTACH) -unsafe impl Sync for DetourAny {} -impl DetourAny { - pub unsafe fn attach(&self, ctx: &AttachContext) -> SysResult<()> { - // SAFETY: dereferencing pointer to static CStr symbol name - let symbol_name = unsafe { *self.symbol_name }.as_ptr(); - // SAFETY: GetProcAddress FFI call with valid module handle and symbol name - let symbol_in_kernelbase = unsafe { GetProcAddress(ctx.kernelbase, symbol_name) }; - if symbol_in_kernelbase.is_null() { - // SAFETY: reading target pointer to check if symbol was already resolved - if unsafe { *self.target }.is_null() { - // dynamic symbol - look up from kernel32 or ntdll - // SAFETY: GetProcAddress FFI call with valid module handle and symbol name - let symbol_in_kernel32 = unsafe { GetProcAddress(ctx.kernel32, symbol_name) }; - if symbol_in_kernel32.is_null() { - // SAFETY: GetProcAddress FFI call with valid module handle and symbol name - let symbol_in_ntdll = unsafe { GetProcAddress(ctx.ntdll, symbol_name) }; - // SAFETY: writing resolved symbol address to target pointer - unsafe { *self.target = symbol_in_ntdll.cast() }; - } else { - // SAFETY: writing resolved symbol address to target pointer - unsafe { *self.target = symbol_in_kernel32.cast() }; - } - } - } else { - // stub symbol: https://github.com/microsoft/Detours/issues/328#issuecomment-2494147615 - // SAFETY: writing resolved symbol address to target pointer for Detours API - unsafe { *self.target = symbol_in_kernelbase.cast() }; - } - // SAFETY: reading target pointer to check if symbol was resolved - if unsafe { *self.target }.is_null() { - // dynamic symbol not found, skip attaching - return Ok(()); - } - // SAFETY: DetourAttach FFI call with valid target and detour function pointers - ck_long(unsafe { DetourAttach(self.target, *self.new) })?; - Ok(()) - } - - pub unsafe fn detach(&self) -> SysResult<()> { - // SAFETY: reading target pointer to check if symbol was resolved - if unsafe { *self.target }.is_null() { - // dynamic symbol not found, skip detaching - return Ok(()); - } - // SAFETY: DetourDetach FFI call with valid target and detour function pointers - ck_long(unsafe { DetourDetach(self.target, *self.new) }) - } -} diff --git a/crates/fspy_preload_windows/src/windows/detours/create_process.rs b/crates/fspy_preload_windows/src/windows/detours/create_process.rs deleted file mode 100644 index 6dd09e77a..000000000 --- a/crates/fspy_preload_windows/src/windows/detours/create_process.rs +++ /dev/null @@ -1,289 +0,0 @@ -use fspy_detours_sys::{DetourCreateProcessWithDllExA, DetourCreateProcessWithDllExW}; -use winapi::{ - shared::{ - minwindef::{BOOL, DWORD, LPVOID}, - ntdef::{LPCSTR, LPSTR}, - }, - um::{ - minwinbase::LPSECURITY_ATTRIBUTES, - processthreadsapi::{ - CreateProcessA, CreateProcessW, LPPROCESS_INFORMATION, LPSTARTUPINFOA, LPSTARTUPINFOW, - ResumeThread, - }, - winbase::CREATE_SUSPENDED, - winnt::{LPCWSTR, LPWSTR}, - }, -}; - -use crate::windows::{ - client::global_client, - detour::{Detour, DetourAny}, -}; - -thread_local! { - static IS_HOOKING_CREATE_PROCESS: std::cell::Cell = const { std::cell::Cell::new(false) }; -} - -struct HookGuard; -impl HookGuard { - pub fn new() -> Option { - let already_hooking = IS_HOOKING_CREATE_PROCESS.with(|c| { - let v = c.get(); - c.set(true); - v - }); - if already_hooking { None } else { Some(Self) } - } -} -impl Drop for HookGuard { - fn drop(&mut self) { - IS_HOOKING_CREATE_PROCESS.with(|c| { - c.set(false); - }); - } -} - -static DETOUR_CREATE_PROCESS_W: Detour< - unsafe extern "system" fn( - LPCWSTR, - LPWSTR, - LPSECURITY_ATTRIBUTES, - LPSECURITY_ATTRIBUTES, - BOOL, - DWORD, - LPVOID, - LPCWSTR, - LPSTARTUPINFOW, - LPPROCESS_INFORMATION, - ) -> i32, -> = - // SAFETY: initializing Detour with the real CreateProcessW function pointer and our replacement - unsafe { - Detour::new(c"CreateProcessW", CreateProcessW, { - unsafe extern "system" fn new_fn( - lp_application_name: LPCWSTR, - lp_command_line: LPWSTR, - lp_process_attributes: LPSECURITY_ATTRIBUTES, - lp_thread_attributes: LPSECURITY_ATTRIBUTES, - b_inherit_handles: BOOL, - dw_creation_flags: DWORD, - lp_environment: LPVOID, - lp_current_directory: LPCWSTR, - lp_startup_info: LPSTARTUPINFOW, - lp_process_information: LPPROCESS_INFORMATION, - ) -> BOOL { - unsafe extern "system" fn create_process_with_payload_w( - lp_application_name: LPCWSTR, - lp_command_line: LPWSTR, - lp_process_attributes: LPSECURITY_ATTRIBUTES, - lp_thread_attributes: LPSECURITY_ATTRIBUTES, - b_inherit_handles: BOOL, - dw_creation_flags: DWORD, - lp_environment: LPVOID, - lp_current_directory: LPCWSTR, - lp_startup_info: LPSTARTUPINFOW, - lp_process_information: LPPROCESS_INFORMATION, - ) -> BOOL { - // SAFETY: calling original CreateProcessW with CREATE_SUSPENDED to inject DLL before resume - let ret = unsafe { - (DETOUR_CREATE_PROCESS_W.real())( - lp_application_name, - lp_command_line, - lp_process_attributes, - lp_thread_attributes, - b_inherit_handles, - dw_creation_flags | CREATE_SUSPENDED, - lp_environment, - lp_current_directory, - lp_startup_info, - lp_process_information, - ) - }; - if ret == 0 { - return 0; - } - - // SAFETY: copying payload to child process and dereferencing lp_process_information - let ret = unsafe { - global_client().prepare_child_process((*lp_process_information).hProcess) - }; - - if ret == 0 { - return 0; - } - if dw_creation_flags & CREATE_SUSPENDED == 0 { - // SAFETY: resuming the suspended child thread after DLL injection - let ret = unsafe { ResumeThread((*lp_process_information).hThread) }; - if ret == (-1i32).cast_unsigned() { - return 0; - } - } - ret - } - - let Some(_hook_guard) = HookGuard::new() else { - // Detect re-entrance and avoid double hooking - // SAFETY: calling original CreateProcessW with all original arguments - return unsafe { - (DETOUR_CREATE_PROCESS_W.real())( - lp_application_name, - lp_command_line, - lp_process_attributes, - lp_thread_attributes, - b_inherit_handles, - dw_creation_flags, - lp_environment, - lp_current_directory, - lp_startup_info, - lp_process_information, - ) - }; - }; - - // SAFETY: accessing the global client initialized during DLL_PROCESS_ATTACH - let client = unsafe { global_client() }; - - // SAFETY: calling DetourCreateProcessWithDllExW to create process with our DLL injected - unsafe { - DetourCreateProcessWithDllExW( - lp_application_name, - lp_command_line, - lp_process_attributes, - lp_thread_attributes, - b_inherit_handles, - dw_creation_flags, - lp_environment, - lp_current_directory, - lp_startup_info, - lp_process_information, - client.ansi_dll_path().as_ptr().cast(), - Some(create_process_with_payload_w), - ) - } - } - new_fn - }) - }; - -static DETOUR_CREATE_PROCESS_A: Detour< - unsafe extern "system" fn( - LPCSTR, - LPSTR, - LPSECURITY_ATTRIBUTES, - LPSECURITY_ATTRIBUTES, - BOOL, - DWORD, - LPVOID, - LPCSTR, - LPSTARTUPINFOA, - LPPROCESS_INFORMATION, - ) -> i32, -> = - // SAFETY: initializing Detour with the real CreateProcessA function pointer and our replacement - unsafe { - Detour::new(c"CreateProcessA", CreateProcessA, { - unsafe extern "system" fn new_fn( - lp_application_name: LPCSTR, - lp_command_line: LPSTR, - lp_process_attributes: LPSECURITY_ATTRIBUTES, - lp_thread_attributes: LPSECURITY_ATTRIBUTES, - b_inherit_handles: BOOL, - dw_creation_flags: DWORD, - lp_environment: LPVOID, - lp_current_directory: LPCSTR, - lp_startup_info: LPSTARTUPINFOA, - lp_process_information: LPPROCESS_INFORMATION, - ) -> BOOL { - unsafe extern "system" fn create_process_with_payload_a( - lp_application_name: LPCSTR, - lp_command_line: LPSTR, - lp_process_attributes: LPSECURITY_ATTRIBUTES, - lp_thread_attributes: LPSECURITY_ATTRIBUTES, - b_inherit_handles: BOOL, - dw_creation_flags: DWORD, - lp_environment: LPVOID, - lp_current_directory: LPCSTR, - lp_startup_info: LPSTARTUPINFOA, - lp_process_information: LPPROCESS_INFORMATION, - ) -> BOOL { - // SAFETY: calling original CreateProcessA with CREATE_SUSPENDED to inject DLL before resume - let ret = unsafe { - (DETOUR_CREATE_PROCESS_A.real())( - lp_application_name, - lp_command_line, - lp_process_attributes, - lp_thread_attributes, - b_inherit_handles, - dw_creation_flags | CREATE_SUSPENDED, - lp_environment, - lp_current_directory, - lp_startup_info, - lp_process_information, - ) - }; - if ret == 0 { - return 0; - } - - // SAFETY: copying payload to child process and dereferencing lp_process_information - let ret = unsafe { - global_client().prepare_child_process((*lp_process_information).hProcess) - }; - - if ret == 0 { - return 0; - } - if dw_creation_flags & CREATE_SUSPENDED == 0 { - // SAFETY: resuming the suspended child thread after DLL injection - let ret = unsafe { ResumeThread((*lp_process_information).hThread) }; - if ret == (-1i32).cast_unsigned() { - return 0; - } - } - ret - } - - let Some(_hook_guard) = HookGuard::new() else { - // Detect re-entrance and avoid double hooking - // SAFETY: calling original CreateProcessA with all original arguments - return unsafe { - (DETOUR_CREATE_PROCESS_A.real())( - lp_application_name, - lp_command_line, - lp_process_attributes, - lp_thread_attributes, - b_inherit_handles, - dw_creation_flags, - lp_environment, - lp_current_directory, - lp_startup_info, - lp_process_information, - ) - }; - }; - // SAFETY: accessing the global client initialized during DLL_PROCESS_ATTACH - let client = unsafe { global_client() }; - - // SAFETY: calling DetourCreateProcessWithDllExA to create process with our DLL injected - unsafe { - DetourCreateProcessWithDllExA( - lp_application_name, - lp_command_line, - lp_process_attributes, - lp_thread_attributes, - b_inherit_handles, - dw_creation_flags, - lp_environment, - lp_current_directory, - lp_startup_info, - lp_process_information, - client.ansi_dll_path().as_ptr().cast(), - Some(create_process_with_payload_a), - ) - } - } - new_fn - }) - }; -pub const DETOURS: &[DetourAny] = - &[DETOUR_CREATE_PROCESS_W.as_any(), DETOUR_CREATE_PROCESS_A.as_any()]; diff --git a/crates/fspy_preload_windows/src/windows/detours/mod.rs b/crates/fspy_preload_windows/src/windows/detours/mod.rs deleted file mode 100644 index 23c252fcc..000000000 --- a/crates/fspy_preload_windows/src/windows/detours/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod create_process; -mod nt; - -use constcat::concat_slices; - -use super::detour::DetourAny; - -pub const DETOURS: &[DetourAny] = concat_slices!([DetourAny]: - create_process::DETOURS, - nt::DETOURS, -); diff --git a/crates/fspy_preload_windows/src/windows/detours/nt.rs b/crates/fspy_preload_windows/src/windows/detours/nt.rs deleted file mode 100644 index 12fb8c050..000000000 --- a/crates/fspy_preload_windows/src/windows/detours/nt.rs +++ /dev/null @@ -1,531 +0,0 @@ -use std::mem::{offset_of, size_of}; - -use fspy_shared::ipc::{AccessMode, NativePath, PathAccess}; -use ntapi::{ - ntioapi::{ - FILE_INFORMATION_CLASS, NtQueryDirectoryFile, NtQueryFullAttributesFile, - NtQueryInformationByName, PFILE_BASIC_INFORMATION, PFILE_NETWORK_OPEN_INFORMATION, - PIO_APC_ROUTINE, PIO_STATUS_BLOCK, - }, - ntpsapi::{ - NtCreateUserProcess, PPS_ATTRIBUTE_LIST, PPS_CREATE_INFO, PS_ATTRIBUTE, - PS_ATTRIBUTE_IMAGE_NAME, PS_ATTRIBUTE_LIST, - }, -}; -use winapi::{ - shared::{ - minwindef::HFILE, - ntdef::{ - BOOLEAN, HANDLE, NTSTATUS, PHANDLE, PLARGE_INTEGER, POBJECT_ATTRIBUTES, - PUNICODE_STRING, PVOID, ULONG, - }, - }, - um::winnt::{ACCESS_MASK, GENERIC_READ}, -}; - -use crate::windows::{ - client::global_client, - convert::{ToAbsolutePath, ToAccessMode}, - detour::{Detour, DetourAny}, -}; - -// CreateProcess ultimately asks NtCreateUserProcess to open the executable image. Some Windows -// versions perform that open entirely inside the syscall, so it never reaches the NtCreateFile and -// query functions hooked below. PS_ATTRIBUTE_IMAGE_NAME is the kernel-facing path that identifies -// the image to open; RTL_USER_PROCESS_PARAMETERS.ImagePathName is only metadata for the child PEB -// and can intentionally name a different file. -// -// Record the image before forwarding the syscall, matching the attempted-access semantics of the -// other NT hooks in this module. This is important for missing executables: the failed lookup is -// still an input access even though NtCreateUserProcess returns an error. -static DETOUR_NT_CREATE_USER_PROCESS: Detour< - unsafe extern "system" fn( - process_handle: PHANDLE, - thread_handle: PHANDLE, - process_desired_access: ACCESS_MASK, - thread_desired_access: ACCESS_MASK, - process_object_attributes: POBJECT_ATTRIBUTES, - thread_object_attributes: POBJECT_ATTRIBUTES, - process_flags: ULONG, - thread_flags: ULONG, - process_parameters: PVOID, - create_info: PPS_CREATE_INFO, - attribute_list: PPS_ATTRIBUTE_LIST, - ) -> NTSTATUS, -> = - // SAFETY: initializing Detour with the real NtCreateUserProcess function pointer - unsafe { - Detour::new(c"NtCreateUserProcess", NtCreateUserProcess, { - unsafe extern "system" fn new_fn( - process_handle: PHANDLE, - thread_handle: PHANDLE, - process_desired_access: ACCESS_MASK, - thread_desired_access: ACCESS_MASK, - process_object_attributes: POBJECT_ATTRIBUTES, - thread_object_attributes: POBJECT_ATTRIBUTES, - process_flags: ULONG, - thread_flags: ULONG, - process_parameters: PVOID, - create_info: PPS_CREATE_INFO, - attribute_list: PPS_ATTRIBUTE_LIST, - ) -> NTSTATUS { - // SAFETY: observing caller memory without changing the forwarded arguments - unsafe { handle_process_image(attribute_list) }; - - // SAFETY: calling the original NtCreateUserProcess with all original arguments - unsafe { - (DETOUR_NT_CREATE_USER_PROCESS.real())( - process_handle, - thread_handle, - process_desired_access, - thread_desired_access, - process_object_attributes, - thread_object_attributes, - process_flags, - thread_flags, - process_parameters, - create_info, - attribute_list, - ) - } - } - new_fn - }) - }; - -unsafe fn handle_process_image(attribute_list: PPS_ATTRIBUTE_LIST) { - // SAFETY: NtCreateUserProcess requires its attribute list to remain valid for this call. - if let Some(image_path) = unsafe { read_process_image_attribute(attribute_list) } { - // Sender serialization completes before this call returns, so NativePath does not retain - // the borrowed PS_ATTRIBUTE_IMAGE_NAME buffer past the NtCreateUserProcess call. - // SAFETY: accessing the global client which was initialized during DLL_PROCESS_ATTACH - unsafe { global_client() } - .send(PathAccess { mode: AccessMode::READ, path: NativePath::from_wide(image_path) }); - } -} - -/// Find the kernel-facing executable name in an `NtCreateUserProcess` attribute list. -/// -/// `PS_ATTRIBUTE_LIST` is a variable-length structure: `TotalLength` covers a fixed-size header -/// followed by contiguous `PS_ATTRIBUTE` entries. Its Rust definition contains one placeholder -/// element, so the actual entry count must be derived from `TotalLength`, not from the array type. -/// -/// The returned slice borrows the caller's `PS_ATTRIBUTE_IMAGE_NAME` buffer and is valid only while -/// the intercepted `NtCreateUserProcess` call is active. -/// -/// # Safety -/// -/// `attribute_list` and the image-name buffer it references must remain valid for the duration of -/// the intercepted call, as required by `NtCreateUserProcess`. -unsafe fn read_process_image_attribute<'a>( - attribute_list: PPS_ATTRIBUTE_LIST, -) -> Option<&'a [u16]> { - // SAFETY: NtCreateUserProcess keeps a supplied attribute list valid for this call; a null - // optional pointer is parsed as None. - let attribute_list = unsafe { attribute_list.as_ref()? }; - - // Attributes is a trailing array. Subtract its byte offset to remove the header; the native API - // contract guarantees that the remaining bytes contain complete PS_ATTRIBUTE entries. - let attributes_offset = offset_of!(PS_ATTRIBUTE_LIST, Attributes); - let attribute_count = - (attribute_list.TotalLength - attributes_offset) / size_of::(); - - // The Rust field exposes the first placeholder entry as a reference; TotalLength describes how - // many contiguous entries follow it in the actual variable-length allocation. - let first_attribute = attribute_list.Attributes.first()?; - // SAFETY: TotalLength covers a contiguous variable-length tail starting at first_attribute. - let attributes: &[PS_ATTRIBUTE] = - unsafe { std::slice::from_raw_parts(std::ptr::from_ref(first_attribute), attribute_count) }; - for attribute in attributes { - if attribute.Attribute != PS_ATTRIBUTE_IMAGE_NAME { - continue; - } - - // Unlike a UNICODE_STRING, PS_ATTRIBUTE_IMAGE_NAME stores the path buffer directly in - // ValuePtr and stores its byte length in Size. It is the image path consumed by the kernel, - // so do not fall back to the separately spoofable process-parameter path. - // SAFETY: PS_ATTRIBUTE_IMAGE_NAME stores a valid UTF-16 pointer in ValuePtr for this call; - // a null pointer is parsed as None. - let image_path = unsafe { attribute.u.ValuePtr.cast::().as_ref()? }; - // SAFETY: the attribute contract guarantees a valid UTF-16 buffer of Size bytes for this - // call. Size is the counted string length, so no NUL-terminator parsing is needed. - return Some(unsafe { - std::slice::from_raw_parts( - std::ptr::from_ref(image_path), - attribute.Size / size_of::(), - ) - }); - } - - None -} - -static DETOUR_NT_CREATE_FILE: Detour< - unsafe extern "system" fn( - file_handle: PHANDLE, - desired_access: ACCESS_MASK, - object_attributes: POBJECT_ATTRIBUTES, - io_status_block: PIO_STATUS_BLOCK, - allocation_size: PLARGE_INTEGER, - file_attributes: ULONG, - share_access: ULONG, - create_disposition: ULONG, - create_options: ULONG, - ea_buffer: PVOID, - ea_length: ULONG, - ) -> HFILE, -> = - // SAFETY: initializing Detour with the real NtCreateFile function pointer and our replacement - unsafe { - Detour::new(c"NtCreateFile", ntapi::ntioapi::NtCreateFile, { - unsafe extern "system" fn new_nt_create_file( - file_handle: PHANDLE, - desired_access: ACCESS_MASK, - object_attributes: POBJECT_ATTRIBUTES, - io_status_block: PIO_STATUS_BLOCK, - allocation_size: PLARGE_INTEGER, - file_attributes: ULONG, - share_access: ULONG, - create_disposition: ULONG, - create_options: ULONG, - ea_buffer: PVOID, - ea_length: ULONG, - ) -> HFILE { - // SAFETY: intercepting file open to record access before forwarding to real function - unsafe { handle_open(desired_access, object_attributes) }; - - // SAFETY: calling the original NtCreateFile with all original arguments - unsafe { - (DETOUR_NT_CREATE_FILE.real())( - file_handle, - desired_access, - object_attributes, - io_status_block, - allocation_size, - file_attributes, - share_access, - create_disposition, - create_options, - ea_buffer, - ea_length, - ) - } - } - new_nt_create_file - }) - }; - -static DETOUR_NT_OPEN_FILE: Detour< - unsafe extern "system" fn( - file_handle: PHANDLE, - desired_access: ACCESS_MASK, - object_attributes: POBJECT_ATTRIBUTES, - io_status_block: PIO_STATUS_BLOCK, - share_access: ULONG, - open_options: ULONG, - ) -> HFILE, -> = - // SAFETY: initializing Detour with the real NtOpenFile function pointer and our replacement - unsafe { - Detour::new(c"NtOpenFile", ntapi::ntioapi::NtOpenFile, { - unsafe extern "system" fn new_nt_open_file( - file_handle: PHANDLE, - desired_access: ACCESS_MASK, - object_attributes: POBJECT_ATTRIBUTES, - io_status_block: PIO_STATUS_BLOCK, - share_access: ULONG, - open_options: ULONG, - ) -> HFILE { - // SAFETY: intercepting file open to record access before forwarding to real function - unsafe { - handle_open(desired_access, object_attributes); - } - - // SAFETY: calling the original NtOpenFile with all original arguments - unsafe { - (DETOUR_NT_OPEN_FILE.real())( - file_handle, - desired_access, - object_attributes, - io_status_block, - share_access, - open_options, - ) - } - } - new_nt_open_file - }) - }; - -static DETOUR_NT_QUERY_ATTRIBUTES_FILE: Detour< - unsafe extern "system" fn( - object_attributes: POBJECT_ATTRIBUTES, - file_information: PFILE_BASIC_INFORMATION, - ) -> HFILE, -> = - // SAFETY: initializing Detour with the real NtQueryAttributesFile function pointer and our replacement - unsafe { - Detour::new(c"NtQueryAttributesFile", ntapi::ntioapi::NtQueryAttributesFile, { - unsafe extern "system" fn new_nt_query_attrs( - object_attributes: POBJECT_ATTRIBUTES, - file_information: PFILE_BASIC_INFORMATION, - ) -> HFILE { - // SAFETY: intercepting attribute query to record read access - unsafe { handle_open(AccessMode::READ, object_attributes) }; - // SAFETY: calling the original NtQueryAttributesFile with all original arguments - unsafe { - (DETOUR_NT_QUERY_ATTRIBUTES_FILE.real())(object_attributes, file_information) - } - } - new_nt_query_attrs - }) - }; - -unsafe fn handle_open(access_mode: impl ToAccessMode, path: impl ToAbsolutePath) { - // SAFETY: accessing the global client which was initialized during DLL_PROCESS_ATTACH - let client = unsafe { global_client() }; - // SAFETY: resolving path from Windows object attributes or handle for access tracking - unsafe { - path.to_absolute_path(|path| { - let Some(path) = path else { - return Ok(()); - }; - let path = path.as_slice(); - let path_access = path.iter().rposition(|c| *c == u16::from(b'*')).map_or_else( - || { - // SAFETY: converting access mask to AccessMode via FFI-aware trait - PathAccess { - mode: access_mode.to_access_mode(), - path: NativePath::from_wide(path), - } - }, - |wildcard_pos| { - let path_before_wildcard = &path[..wildcard_pos]; - let slash_pos = path_before_wildcard - .iter() - .rposition(|c| *c == u16::from(b'\\') || *c == u16::from(b'/')) - .unwrap_or(0); - PathAccess { - mode: AccessMode::READ_DIR, - path: NativePath::from_wide(&path[..slash_pos]), - } - }, - ); - client.send(path_access); - Ok(()) - }) - } - .unwrap(); -} - -static DETOUR_NT_FULL_QUERY_ATTRIBUTES_FILE: Detour< - unsafe extern "system" fn( - object_attributes: POBJECT_ATTRIBUTES, - file_information: PFILE_NETWORK_OPEN_INFORMATION, - ) -> HFILE, -> = - // SAFETY: initializing Detour with the real NtQueryFullAttributesFile function pointer - unsafe { - Detour::new(c"NtQueryFullAttributesFile", NtQueryFullAttributesFile, { - unsafe extern "system" fn new_fn( - object_attributes: POBJECT_ATTRIBUTES, - file_information: PFILE_NETWORK_OPEN_INFORMATION, - ) -> HFILE { - // SAFETY: intercepting attribute query to record read access - unsafe { handle_open(GENERIC_READ, object_attributes) }; - // SAFETY: calling the original NtQueryFullAttributesFile - unsafe { - (DETOUR_NT_FULL_QUERY_ATTRIBUTES_FILE.real())( - object_attributes, - file_information, - ) - } - } - new_fn - }) - }; - -static DETOUR_NT_OPEN_SYMBOLIC_LINK_OBJECT: Detour< - unsafe extern "system" fn( - link_handle: PHANDLE, - desired_access: ACCESS_MASK, - object_attributes: POBJECT_ATTRIBUTES, - ) -> HFILE, -> = - // SAFETY: initializing Detour with the real NtOpenSymbolicLinkObject function pointer - unsafe { - Detour::new(c"NtOpenSymbolicLinkObject", ntapi::ntobapi::NtOpenSymbolicLinkObject, { - unsafe extern "system" fn new_fn( - link_handle: PHANDLE, - desired_access: ACCESS_MASK, - object_attributes: POBJECT_ATTRIBUTES, - ) -> HFILE { - // SAFETY: intercepting symlink open to record access - unsafe { handle_open(desired_access, object_attributes) }; - // SAFETY: calling the original NtOpenSymbolicLinkObject - unsafe { - (DETOUR_NT_OPEN_SYMBOLIC_LINK_OBJECT.real())( - link_handle, - desired_access, - object_attributes, - ) - } - } - new_fn - }) - }; - -static DETOUR_NT_QUERY_INFORMATION_BY_NAME: Detour< - unsafe extern "system" fn( - object_attributes: POBJECT_ATTRIBUTES, - io_status_block: PIO_STATUS_BLOCK, - file_information: PVOID, - length: ULONG, - file_information_class: FILE_INFORMATION_CLASS, - ) -> HFILE, -> = - // SAFETY: initializing Detour with the real NtQueryInformationByName function pointer - unsafe { - Detour::new(c"NtQueryInformationByName", NtQueryInformationByName, { - unsafe extern "system" fn new_fn( - object_attributes: POBJECT_ATTRIBUTES, - io_status_block: PIO_STATUS_BLOCK, - file_information: PVOID, - length: ULONG, - file_information_class: FILE_INFORMATION_CLASS, - ) -> HFILE { - // SAFETY: intercepting information query to record read access - unsafe { handle_open(GENERIC_READ, object_attributes) }; - // SAFETY: calling the original NtQueryInformationByName - unsafe { - (DETOUR_NT_QUERY_INFORMATION_BY_NAME.real())( - object_attributes, - io_status_block, - file_information, - length, - file_information_class, - ) - } - } - new_fn - }) - }; - -static DETOUR_NT_QUERY_DIRECTORY_FILE: Detour< - unsafe extern "system" fn( - file_handle: HANDLE, - event: HANDLE, - apc_routine: PIO_APC_ROUTINE, - apc_context: PVOID, - io_status_block: PIO_STATUS_BLOCK, - file_information: PVOID, - length: ULONG, - file_information_class: FILE_INFORMATION_CLASS, - return_single_entry: BOOLEAN, - file_name: PUNICODE_STRING, - restart_scan: BOOLEAN, - ) -> NTSTATUS, -> = - // SAFETY: initializing Detour with the real NtQueryDirectoryFile function pointer - unsafe { - Detour::new(c"NtQueryDirectoryFile", NtQueryDirectoryFile, { - unsafe extern "system" fn new_fn( - file_handle: HANDLE, - event: HANDLE, - apc_routine: PIO_APC_ROUTINE, - apc_context: PVOID, - io_status_block: PIO_STATUS_BLOCK, - file_information: PVOID, - length: ULONG, - file_information_class: FILE_INFORMATION_CLASS, - return_single_entry: BOOLEAN, - file_name: PUNICODE_STRING, - restart_scan: BOOLEAN, - ) -> NTSTATUS { - // SAFETY: intercepting directory query to record directory read access - unsafe { handle_open(AccessMode::READ_DIR, file_handle) }; - // SAFETY: calling the original NtQueryDirectoryFile - unsafe { - (DETOUR_NT_QUERY_DIRECTORY_FILE.real())( - file_handle, - event, - apc_routine, - apc_context, - io_status_block, - file_information, - length, - file_information_class, - return_single_entry, - file_name, - restart_scan, - ) - } - } - new_fn - }) - }; - -// NtQueryDirectoryFileEx is not in ntapi crate, so we define it here. -// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntquerydirectoryfileex -type NtQueryDirectoryFileExFn = unsafe extern "system" fn( - file_handle: HANDLE, - event: HANDLE, - apc_routine: PIO_APC_ROUTINE, - apc_context: PVOID, - io_status_block: PIO_STATUS_BLOCK, - file_information: PVOID, - length: ULONG, - file_information_class: FILE_INFORMATION_CLASS, - query_flags: ULONG, - file_name: PUNICODE_STRING, -) -> NTSTATUS; - -static DETOUR_NT_QUERY_DIRECTORY_FILE_EX: Detour = - // SAFETY: initializing dynamic Detour for NtQueryDirectoryFileEx (resolved at attach time) - unsafe { - Detour::dynamic(c"NtQueryDirectoryFileEx", { - unsafe extern "system" fn new_fn( - file_handle: HANDLE, - event: HANDLE, - apc_routine: PIO_APC_ROUTINE, - apc_context: PVOID, - io_status_block: PIO_STATUS_BLOCK, - file_information: PVOID, - length: ULONG, - file_information_class: FILE_INFORMATION_CLASS, - query_flags: ULONG, - file_name: PUNICODE_STRING, - ) -> NTSTATUS { - // SAFETY: intercepting directory query to record directory read access - unsafe { handle_open(AccessMode::READ_DIR, file_handle) }; - // SAFETY: calling the original NtQueryDirectoryFileEx - unsafe { - (DETOUR_NT_QUERY_DIRECTORY_FILE_EX.real())( - file_handle, - event, - apc_routine, - apc_context, - io_status_block, - file_information, - length, - file_information_class, - query_flags, - file_name, - ) - } - } - new_fn - }) - }; - -pub const DETOURS: &[DetourAny] = &[ - DETOUR_NT_CREATE_USER_PROCESS.as_any(), - DETOUR_NT_CREATE_FILE.as_any(), - DETOUR_NT_OPEN_FILE.as_any(), - DETOUR_NT_QUERY_ATTRIBUTES_FILE.as_any(), - DETOUR_NT_FULL_QUERY_ATTRIBUTES_FILE.as_any(), - DETOUR_NT_OPEN_SYMBOLIC_LINK_OBJECT.as_any(), - DETOUR_NT_QUERY_INFORMATION_BY_NAME.as_any(), - DETOUR_NT_QUERY_DIRECTORY_FILE.as_any(), - DETOUR_NT_QUERY_DIRECTORY_FILE_EX.as_any(), -]; diff --git a/crates/fspy_preload_windows/src/windows/mod.rs b/crates/fspy_preload_windows/src/windows/mod.rs deleted file mode 100644 index 0d1612a39..000000000 --- a/crates/fspy_preload_windows/src/windows/mod.rs +++ /dev/null @@ -1,95 +0,0 @@ -pub(crate) mod client; -mod convert; -pub(crate) mod detour; -mod detours; -mod winapi_utils; - -use std::slice; - -use client::{Client, set_global_client}; -use detours::DETOURS; -use fspy_detours_sys::{ - DetourFindPayloadEx, DetourIsHelperProcess, DetourRestoreAfterWith, DetourTransactionBegin, - DetourTransactionCommit, DetourUpdateThread, -}; -use fspy_shared::windows::PAYLOAD_ID; -use winapi::{ - shared::minwindef::{BOOL, DWORD, FALSE, HINSTANCE, TRUE}, - um::{ - processthreadsapi::GetCurrentThread, - winnt::{self}, - }, -}; -use winapi_utils::{ck, ck_long}; -use winsafe::SetLastError; - -use crate::windows::detour::AttachContext; - -fn dll_main(_hinstance: HINSTANCE, reason: u32) -> winsafe::SysResult<()> { - // SAFETY: FFI call to check if this is a Detours helper process - if unsafe { DetourIsHelperProcess() } == TRUE { - return Ok(()); - } - - match reason { - winnt::DLL_PROCESS_ATTACH => { - // dbg!((current_exe(), std::process::id())); - // SAFETY: FFI call to restore Detours state after DLL injection - ck(unsafe { DetourRestoreAfterWith() })?; - - let mut payload_len: DWORD = 0; - // SAFETY: FFI call to find the injected payload by GUID - let payload_ptr = - unsafe { DetourFindPayloadEx(&PAYLOAD_ID, &raw mut payload_len).cast::() }; - // SAFETY: creating a static slice from the payload pointer; lifetime is valid for process duration - let payload_bytes = unsafe { - slice::from_raw_parts::<'static, u8>(payload_ptr, payload_len.try_into().unwrap()) - }; - let client = Client::from_payload_bytes(payload_bytes); - // SAFETY: setting the global client during single-threaded DLL_PROCESS_ATTACH - unsafe { set_global_client(client) }; - - let ctx = AttachContext::new(); - - // SAFETY: FFI call to begin a Detours transaction - ck_long(unsafe { DetourTransactionBegin() })?; - // SAFETY: FFI call to update the current thread in the Detours transaction - ck_long(unsafe { DetourUpdateThread(GetCurrentThread().cast()) })?; - - for d in DETOURS { - // SAFETY: attaching each detour within the active Detours transaction - unsafe { d.attach(&ctx) }?; - } - - // SAFETY: FFI call to commit the Detours transaction - ck_long(unsafe { DetourTransactionCommit() })?; - } - winnt::DLL_PROCESS_DETACH => { - // SAFETY: FFI call to begin a Detours transaction for detaching - ck(unsafe { DetourTransactionBegin() })?; - // SAFETY: FFI call to update the current thread in the Detours transaction - ck(unsafe { DetourUpdateThread(GetCurrentThread().cast()) })?; - - for d in DETOURS { - // SAFETY: detaching each detour within the active Detours transaction - unsafe { d.detach() }?; - } - - // SAFETY: FFI call to commit the Detours transaction - ck(unsafe { DetourTransactionCommit() })?; - } - _ => {} - } - Ok(()) -} - -#[unsafe(no_mangle)] -extern "system" fn DllMain(hinstance: HINSTANCE, reason: u32, _: *mut std::ffi::c_void) -> BOOL { - match dll_main(hinstance, reason) { - Ok(()) => TRUE, - Err(err) => { - SetLastError(err); - FALSE - } - } -} diff --git a/crates/fspy_preload_windows/src/windows/winapi_utils.rs b/crates/fspy_preload_windows/src/windows/winapi_utils.rs deleted file mode 100644 index 38eba1c19..000000000 --- a/crates/fspy_preload_windows/src/windows/winapi_utils.rs +++ /dev/null @@ -1,180 +0,0 @@ -use std::slice; - -use fspy_shared::ipc::AccessMode; -use smallvec::SmallVec; -use widestring::{U16CStr, U16Str}; -use winapi::{ - ctypes::c_long, - shared::{ - minwindef::{BOOL, FALSE, MAX_PATH}, - ntdef::{HANDLE, PWSTR, UNICODE_STRING}, - winerror::{NO_ERROR, S_OK}, - }, - um::{ - fileapi::GetFinalPathNameByHandleW, - winnt::{ - ACCESS_MASK, FILE_APPEND_DATA, FILE_READ_DATA, FILE_WRITE_DATA, GENERIC_READ, - GENERIC_WRITE, - }, - }, -}; -use windows_sys::Win32::{ - Foundation::LocalFree, - UI::Shell::{PATHCCH_ALLOW_LONG_PATHS, PathAllocCombine}, -}; -use winsafe::{GetLastError, co}; - -pub fn ck(b: BOOL) -> winsafe::SysResult<()> { - if b == FALSE { Err(GetLastError()) } else { Ok(()) } -} - -pub const fn ck_long(val: c_long) -> winsafe::SysResult<()> { - if 0 == NO_ERROR { - Ok(()) - } else { - // SAFETY: creating an ERROR from the raw c_long value for the Windows error code - Err(unsafe { winsafe::co::ERROR::from_raw(val.cast_unsigned()) }) - } -} - -pub unsafe fn get_u16_str(ustring: &UNICODE_STRING) -> &U16Str { - // https://learn.microsoft.com/en-us/windows/win32/api/subauth/ns-subauth-unicode_string - // UNICODE_STRING.Length is in bytes - let u16_count = ustring.Length / 2; - let chars: &[u16] = if u16_count == 0 { - // If length is zero, we can't use slice::from_raw_parts as it requires a non-null pointer but - // Buffer may be null in that case. - &[] - } else { - // SAFETY: UNICODE_STRING.Buffer points to a valid u16 array of Length/2 elements - unsafe { slice::from_raw_parts(ustring.Buffer, usize::from(u16_count)) } - }; - U16CStr::from_slice_truncate(chars).map_or_else(|_| chars.into(), U16CStr::as_ustr) -} - -pub unsafe fn get_path_name(handle: HANDLE) -> winsafe::SysResult> { - let mut path = SmallVec::::new(); - // SAFETY: FFI call to GetFinalPathNameByHandleW to query the file path from a handle - let len = unsafe { - GetFinalPathNameByHandleW( - handle, - path.as_mut_ptr(), - path.capacity().try_into().unwrap(), - 0, /*FILE_NAME_NORMALIZED*/ - ) - }; - if len == 0 { - return Err(winsafe::GetLastError()); - } - let len = usize::try_from(len).unwrap(); - if len <= path.capacity() { - // SAFETY: GetFinalPathNameByHandleW wrote `len` u16 characters into the buffer - unsafe { path.set_len(len) }; - } else { - path.reserve_exact(len); - // SAFETY: FFI call to GetFinalPathNameByHandleW with larger buffer after first call indicated needed size - let len = unsafe { - GetFinalPathNameByHandleW( - handle, - path.as_mut_ptr(), - path.capacity().try_into().unwrap(), - 0, /*FILE_NAME_NORMALIZED*/ - ) - }; - let len = usize::try_from(len).unwrap(); - if len == 0 { - return Err(winsafe::GetLastError()); - } else if len > path.capacity() { - unreachable!() - } - // SAFETY: GetFinalPathNameByHandleW wrote `len` u16 characters into the buffer - unsafe { path.set_len(len) }; - } - Ok(path) -} - -pub const fn access_mask_to_mode(desired_access: ACCESS_MASK) -> AccessMode { - let has_write = (desired_access & (FILE_WRITE_DATA | FILE_APPEND_DATA | GENERIC_WRITE)) != 0; - let has_read = (desired_access & (FILE_READ_DATA | GENERIC_READ)) != 0; - if has_write { - if has_read { AccessMode::READ.union(AccessMode::WRITE) } else { AccessMode::WRITE } - } else { - AccessMode::READ - } -} - -pub struct HeapPath(PWSTR); -impl HeapPath { - #[must_use] - pub fn to_u16_str(&self) -> &U16Str { - // SAFETY: the PWSTR was allocated by PathAllocCombine and is a valid null-terminated wide string - unsafe { U16CStr::from_ptr_str(self.0).as_ustr() } - } -} -impl Drop for HeapPath { - fn drop(&mut self) { - // SAFETY: freeing the PWSTR allocated by PathAllocCombine via LocalFree - unsafe { LocalFree(self.0.cast()) }; - } -} - -pub fn combine_paths(path1: &U16CStr, path2: &U16CStr) -> winsafe::SysResult { - let mut out = std::ptr::null_mut(); - // SAFETY: FFI call to PathAllocCombine with valid null-terminated wide string pointers - let hr = unsafe { - PathAllocCombine( - path1.as_ptr(), - path2.as_ptr(), - PATHCCH_ALLOW_LONG_PATHS, /*PATHCOMBINE_DEFAULT*/ - &raw mut out, - ) - }; - if hr != S_OK { - // SAFETY: creating an ERROR from the HRESULT value - return Err(unsafe { co::ERROR::from_raw(hr.try_into().unwrap()) }); - } - Ok(HeapPath(out)) -} - -#[cfg(test)] -mod tests { - use std::{ - ffi::OsString, - fs::File, - os::windows::{ffi::OsStringExt, io::AsRawHandle}, - path::PathBuf, - }; - - use super::get_path_name; - - fn test_get_path_name(filename: &str) { - let tmpdir = tempfile::tempdir().unwrap(); - let path = tmpdir.path().canonicalize().unwrap().join(filename); - let file = File::create(&path).unwrap(); - // SAFETY: passing a valid raw file handle to get_path_name - let actual_path = unsafe { get_path_name(file.as_raw_handle().cast()) }.unwrap(); - let actual_path = PathBuf::from(OsString::from_wide(&actual_path)); - assert_eq!(path, actual_path); - } - - #[test] - fn test_get_path_name_short() { - test_get_path_name("foo"); - } - #[test] - fn test_get_path_name_long() { - test_get_path_name(str::repeat("a", 255).as_str()); - } - - #[test] - fn test_combine_path() { - use widestring::u16cstr; - - use super::combine_paths; - - let path1 = u16cstr!("C:\\foo"); - let path2 = u16cstr!("bar\\baz"); - let combined = combine_paths(path1, path2).unwrap(); - assert_eq!(combined.to_u16_str(), u16cstr!("C:\\foo\\bar\\baz")); - } -} diff --git a/crates/fspy_seccomp_unotify/.clippy.toml b/crates/fspy_seccomp_unotify/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/fspy_seccomp_unotify/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/fspy_seccomp_unotify/Cargo.toml b/crates/fspy_seccomp_unotify/Cargo.toml deleted file mode 100644 index 915d0867c..000000000 --- a/crates/fspy_seccomp_unotify/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "fspy_seccomp_unotify" -version = "0.1.0" -edition = "2024" -license.workspace = true -publish = false - -[target.'cfg(target_os = "linux")'.dependencies] -wincode = { workspace = true, features = ["derive"] } -libc = { workspace = true } -nix = { workspace = true, features = ["process", "fs", "poll", "socket", "uio"] } -passfd = { workspace = true, default-features = false, optional = true } -seccompiler = { workspace = true } -syscalls = { workspace = true, features = ["std"] } -tokio = { workspace = true, features = ["net", "process", "io-util", "rt", "sync"] } -tracing = { workspace = true } -tempfile = { workspace = true } -futures-util = { workspace = true } - -[target.'cfg(target_os = "linux")'.dev-dependencies] -assertables = { workspace = true } -futures-util = { workspace = true } -nix = { workspace = true, features = ["fs"] } -test-log = { workspace = true } -tokio = { workspace = true, features = ["macros", "time"] } - -[features] -supervisor = ["dep:passfd", "passfd/async"] -target = ["dep:passfd"] - -[lints] -workspace = true - -[lib] -test = false -doctest = false - -[[test]] -name = "arg_types" -required-features = ["supervisor", "target"] diff --git a/crates/fspy_seccomp_unotify/README.md b/crates/fspy_seccomp_unotify/README.md deleted file mode 100644 index c55f5ba7f..000000000 --- a/crates/fspy_seccomp_unotify/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# fspy_seccomp_unotify - -Safe bindings for `seccomp_unotify` used by `fspy` to intercept direct syscalls on Linux. - -- `src/supervisor` is gated by feature `supervisor`. It contains code that needs to run in the supervisor process(the process that uses `fspy` to track child processes). -- `src/target` is gated by feature `target`. It contains code that needs to run in target processes(child processes tracked by `fspy`). diff --git a/crates/fspy_seccomp_unotify/src/bindings/alloc.rs b/crates/fspy_seccomp_unotify/src/bindings/alloc.rs deleted file mode 100644 index 024287dea..000000000 --- a/crates/fspy_seccomp_unotify/src/bindings/alloc.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::{ - alloc::{self, Layout}, - cmp::max, - ops::Deref, - ptr::NonNull, - sync::LazyLock, -}; - -use super::get_notif_sizes; - -#[derive(Debug)] -struct BufSizes { - req_layout: Layout, - resp_layout: Layout, -} - -static BUF_SIZES: LazyLock = LazyLock::new(|| { - const MAX_ALIGN: usize = align_of::(); - - let sizes = get_notif_sizes().unwrap(); - BufSizes { - req_layout: Layout::from_size_align( - max(sizes.seccomp_notif.into(), size_of::()), - MAX_ALIGN, - ) - .unwrap(), - resp_layout: Layout::from_size_align( - max(sizes.seccomp_notif_resp.into(), size_of::()), - MAX_ALIGN, - ) - .unwrap(), - } -}); - -pub struct Alloced { - ptr: NonNull, - layout: Layout, -} - -impl Alloced { - /// Allocates a zero-initialized buffer with the given layout. - /// - /// # Safety - /// The `layout` must have a size large enough to hold a value of type `T` and - /// must have proper alignment for `T`. - pub(crate) unsafe fn alloc(layout: Layout) -> Self { - // SAFETY: layout is non-zero-sized (guaranteed by caller) and properly aligned - let ptr = unsafe { alloc::alloc_zeroed(layout) }; - - let ptr = NonNull::new(ptr).unwrap(); - Self { ptr: ptr.cast(), layout } - } - - pub(crate) const fn zeroed(&mut self) -> &mut T { - // SAFETY: `self.ptr` was allocated with `self.layout.size()` bytes, - // so writing that many zero bytes is within bounds - unsafe { self.ptr.cast::().write_bytes(0, self.layout.size()) }; - // SAFETY: the pointer is valid, properly aligned, and the buffer has just - // been zero-initialized, which is valid for the kernel structs used here - unsafe { self.ptr.as_mut() } - } -} - -impl Deref for Alloced { - type Target = T; - - fn deref(&self) -> &Self::Target { - // SAFETY: the pointer is valid and properly aligned, allocated in `alloc()` - unsafe { self.ptr.as_ref() } - } -} - -impl Drop for Alloced { - fn drop(&mut self) { - // SAFETY: `self.ptr` was allocated with `alloc::alloc_zeroed` using `self.layout`, - // so it is safe to deallocate with the same layout - unsafe { - alloc::dealloc(self.ptr.as_ptr().cast(), self.layout); - } - } -} - -// SAFETY: `Alloced` owns a heap allocation and does not use thread-local storage. -// It is safe to send across threads when `T` itself is `Send + Sync`. -unsafe impl Send for Alloced {} -// SAFETY: `Alloced` only provides shared access via `Deref`, which is safe -// when `T` is `Send + Sync`. -unsafe impl Sync for Alloced {} - -/// Allocates a zero-initialized buffer for a `seccomp_notif` struct, sized to at least -/// what the kernel requires. -#[must_use] -pub fn alloc_seccomp_notif() -> Alloced { - // SAFETY: `BUF_SIZES.req_layout` is computed from `get_notif_sizes()` and - // `size_of::()`, guaranteeing sufficient size and alignment - unsafe { Alloced::alloc(BUF_SIZES.req_layout) } -} - -/// Allocates a zero-initialized buffer for a `seccomp_notif_resp` struct, sized to at least -/// what the kernel requires. -#[must_use] -pub fn alloc_seccomp_notif_resp() -> Alloced { - // SAFETY: `BUF_SIZES.resp_layout` is computed from `get_notif_sizes()` and - // `size_of::()`, guaranteeing sufficient size and alignment - unsafe { Alloced::alloc(BUF_SIZES.resp_layout) } -} diff --git a/crates/fspy_seccomp_unotify/src/bindings/mod.rs b/crates/fspy_seccomp_unotify/src/bindings/mod.rs deleted file mode 100644 index b6461a4f4..000000000 --- a/crates/fspy_seccomp_unotify/src/bindings/mod.rs +++ /dev/null @@ -1,87 +0,0 @@ -#[cfg(feature = "supervisor")] -pub mod alloc; - -#[cfg(feature = "supervisor")] -use alloc::Alloced; -use std::os::raw::c_int; - -use libc::syscall; - -/// # Safety -/// The `args` pointer must be valid for the given `operation`, or null if the operation -/// does not require arguments. -unsafe fn seccomp( - operation: libc::c_uint, - flags: libc::c_uint, - args: *mut libc::c_void, -) -> nix::Result { - // SAFETY: caller guarantees `args` is valid for the given seccomp operation - let ret = unsafe { syscall(libc::SYS_seccomp, operation, flags, args) }; - if ret < 0 { - return Err(nix::Error::last()); - } - Ok(c_int::try_from(ret).unwrap()) -} - -#[cfg(feature = "supervisor")] -fn get_notif_sizes() -> nix::Result { - use std::mem::zeroed; - // SAFETY: `seccomp_notif_sizes` is a plain data struct safe to zero-initialize - let mut sizes = unsafe { zeroed::() }; - // SAFETY: `sizes` is a valid mutable pointer to a `seccomp_notif_sizes` struct, - // which is the expected argument for `SECCOMP_GET_NOTIF_SIZES` - unsafe { seccomp(libc::SECCOMP_GET_NOTIF_SIZES, 0, (&raw mut sizes).cast()) }?; - Ok(sizes) -} - -/// Receives a seccomp notification from the given file descriptor into the provided buffer. -/// -/// # Errors -/// Returns an error if the ioctl call fails (e.g., the fd is invalid or the kernel -/// returns an error). -#[cfg(feature = "supervisor")] -pub fn notif_recv( - fd: std::os::fd::BorrowedFd<'_>, - notif_buf: &mut Alloced, -) -> nix::Result<()> { - use std::os::fd::AsRawFd; - const SECCOMP_IOCTL_NOTIF_RECV: libc::Ioctl = 3_226_476_800u64 as libc::Ioctl; - // SAFETY: `notif_buf.zeroed()` returns a valid mutable pointer to a zeroed - // `seccomp_notif` buffer with sufficient size for the kernel's notification struct - let ret = unsafe { - libc::ioctl(fd.as_raw_fd(), SECCOMP_IOCTL_NOTIF_RECV, (&raw mut *notif_buf.zeroed())) - }; - if ret < 0 { - return Err(nix::Error::last()); - } - Ok(()) -} - -/// Installs a seccomp user notification filter and returns the notification file descriptor. -/// -/// # Errors -/// Returns an error if the seccomp syscall fails (e.g., invalid filter program or -/// insufficient privileges). -#[cfg(feature = "target")] -pub fn install_unotify_filter(prog: &[libc::sock_filter]) -> nix::Result { - use std::os::fd::FromRawFd; - let mut filter = libc::sock_fprog { - len: prog.len().try_into().unwrap(), - filter: prog.as_ptr().cast_mut().cast(), - }; - - // SAFETY: `filter` is a valid `sock_fprog` pointing to the BPF program slice, - // and `SECCOMP_FILTER_FLAG_NEW_LISTENER` requests a notification fd - #[expect(clippy::cast_possible_truncation, reason = "flag value fits in u32")] - let fd = unsafe { - seccomp( - libc::SECCOMP_SET_MODE_FILTER, - libc::SECCOMP_FILTER_FLAG_NEW_LISTENER as _, - (&raw mut filter).cast(), - ) - }?; - - // SAFETY: the seccomp syscall with `SECCOMP_FILTER_FLAG_NEW_LISTENER` returns - // a valid, owned file descriptor on success - Ok(unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) }) -} diff --git a/crates/fspy_seccomp_unotify/src/lib.rs b/crates/fspy_seccomp_unotify/src/lib.rs deleted file mode 100644 index a70b0a816..000000000 --- a/crates/fspy_seccomp_unotify/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![cfg(target_os = "linux")] - -#[cfg(any(feature = "supervisor", feature = "target"))] -mod bindings; -pub mod payload; -#[cfg(feature = "target")] -pub mod target; - -#[cfg(feature = "supervisor")] -pub mod supervisor; diff --git a/crates/fspy_seccomp_unotify/src/payload/filter.rs b/crates/fspy_seccomp_unotify/src/payload/filter.rs deleted file mode 100644 index 5852aa7dc..000000000 --- a/crates/fspy_seccomp_unotify/src/payload/filter.rs +++ /dev/null @@ -1,28 +0,0 @@ -use wincode::{SchemaRead, SchemaWrite}; - -#[derive(Debug, SchemaWrite, SchemaRead, Clone, Copy)] -pub struct CodableSockFilter { - code: u16, - jt: u8, - jf: u8, - k: u32, -} - -#[cfg(feature = "supervisor")] -impl From for CodableSockFilter { - fn from(c_filter: seccompiler::sock_filter) -> Self { - let seccompiler::sock_filter { code, jt, jf, k } = c_filter; - Self { code, jt, jf, k } - } -} - -#[cfg(feature = "target")] -impl From for libc::sock_filter { - fn from(filter: CodableSockFilter) -> Self { - let CodableSockFilter { code, jt, jf, k } = filter; - Self { code, jt, jf, k } - } -} - -#[derive(SchemaWrite, SchemaRead, Debug, Clone)] -pub struct Filter(pub(crate) Vec); diff --git a/crates/fspy_seccomp_unotify/src/payload/mod.rs b/crates/fspy_seccomp_unotify/src/payload/mod.rs deleted file mode 100644 index 6895bc55a..000000000 --- a/crates/fspy_seccomp_unotify/src/payload/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod filter; -pub use filter::Filter; -use wincode::{SchemaRead, SchemaWrite}; - -#[derive(Debug, SchemaWrite, SchemaRead, Clone)] -pub struct SeccompPayload { - pub(crate) ipc_path: Vec, - pub(crate) filter: Filter, -} diff --git a/crates/fspy_seccomp_unotify/src/supervisor/handler/arg.rs b/crates/fspy_seccomp_unotify/src/supervisor/handler/arg.rs deleted file mode 100644 index fa9dc305d..000000000 --- a/crates/fspy_seccomp_unotify/src/supervisor/handler/arg.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::{ - ffi::{OsString, c_int}, - io::{self, IoSliceMut, Read}, - marker::PhantomData, - mem::MaybeUninit, - os::{fd::RawFd, raw::c_void}, -}; - -use libc::{pid_t, seccomp_notif}; -use nix::sys::uio::{RemoteIoVec, process_vm_readv}; - -pub trait FromSyscallArg: Sized { - /// Converts a raw syscall argument into this type. - /// - /// # Errors - /// Returns an error if the argument value cannot be interpreted as this type. - fn from_syscall_arg(arg: u64) -> io::Result; -} -/// Represents the caller of a syscall. Needed to read memory from the caller's address space. -#[derive(Debug, Clone, Copy)] -pub struct Caller<'a> { - pid: pid_t, - _marker: std::marker::PhantomData<&'a ()>, -} - -impl<'a> Caller<'a> { - /// Creates a `Caller` for the given pid with a local lifetime. - #[doc(hidden)] // only exposed for `impl_handler` macro - pub fn with_pid) -> R>(pid: pid_t, f: F) -> R { - f(Self { pid, _marker: std::marker::PhantomData }) - } - - #[must_use] - pub const fn read_vm(self, starting_addr: usize) -> ProcessVmReader<'a> { - ProcessVmReader { caller: self, current_addr: starting_addr } - } -} - -pub struct ProcessVmReader<'a> { - caller: Caller<'a>, - current_addr: usize, -} - -impl io::Read for ProcessVmReader<'_> { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let buf_len = buf.len(); - let read_len = process_vm_readv( - nix::unistd::Pid::from_raw(self.caller.pid), - &mut [IoSliceMut::new(buf)], - &[RemoteIoVec { base: self.current_addr, len: buf_len }], - )?; - self.current_addr = self - .current_addr - .checked_add(read_len) - .ok_or_else(|| io::Error::other("address overflow while reading remote process"))?; - Ok(read_len) - } -} - -#[derive(Debug, Clone, Copy)] -pub struct CStrPtr { - remote_ptr: usize, -} - -impl CStrPtr { - // Reads the C string from the remote process into the provided buffer. - // Returns: - /// - `Ok(Some(n))` if a null-terminator was found at position n of the buffer, - /// - `Ok(None)` if the buffer was filled without encountering a null-terminator. - /// - `Err(UnexpectedEof)` if Eof was reached without encountering a null-terminator. - /// - `Err(other_err)` on other errors from reading the remote process memory. - /// - /// # Errors - /// Returns an error if reading from the remote process memory fails. - pub fn read(self, caller: Caller<'_>, buf: &mut [u8]) -> io::Result> { - let mut reader = caller.read_vm(self.remote_ptr); - let mut pos = 0; - while let Some((_, unfilled)) = buf.split_at_mut_checked(pos) { - if unfilled.is_empty() { - break; - } - let read_bytes = reader.read(unfilled)?; - if read_bytes == 0 { - return Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "reached EOF while reading C string from remote process", - )); - } - if let Some(null_pos) = unfilled[..read_bytes].iter().position(|&b| b == 0) { - return Ok(Some(pos + null_pos)); - } - pos += read_bytes; - } - Ok(None) - } -} - -impl FromSyscallArg for CStrPtr { - #[expect(clippy::cast_possible_truncation, reason = "syscall arg represents a pointer address")] - fn from_syscall_arg(arg: u64) -> io::Result { - Ok(Self { remote_ptr: arg as usize }) - } -} - -pub struct Ptr { - remote_ptr: *mut c_void, - _marker: PhantomData, -} -impl FromSyscallArg for Ptr { - fn from_syscall_arg(arg: u64) -> io::Result { - Ok(Self { remote_ptr: arg as *mut c_void, _marker: PhantomData }) - } -} -impl Ptr { - /// Reads the value of type T from the remote process memory. - /// - /// # Safety - /// The remote pointer must be valid and point to a value of type T in the remote process memory. - /// - /// # Errors - /// Returns an error if reading from the remote process memory fails. - pub unsafe fn read(&self, caller: Caller<'_>) -> io::Result { - let mut reader = caller.read_vm(self.remote_ptr as usize); - let mut buf = MaybeUninit::::zeroed(); - // SAFETY: `MaybeUninit` has the same layout as `T`, so casting to a - // byte slice of `size_of::()` bytes is valid for writing into - let buf_slice = unsafe { - std::slice::from_raw_parts_mut(buf.as_mut_ptr().cast::(), std::mem::size_of::()) - }; - reader.read_exact(buf_slice)?; - // SAFETY: all bytes of `buf` have been initialized by `read_exact`, - // and the caller guarantees the remote pointer points to a valid `T` - Ok(unsafe { buf.assume_init() }) - } -} - -#[derive(Debug)] -pub struct Ignored(()); -impl FromSyscallArg for Ignored { - fn from_syscall_arg(_arg: u64) -> io::Result { - Ok(Self(())) - } -} - -#[derive(Debug)] -pub struct Fd { - fd: RawFd, -} - -impl Fd { - #[must_use] - pub const fn cwd() -> Self { - Self { fd: libc::AT_FDCWD } - } -} - -impl FromSyscallArg for Fd { - fn from_syscall_arg(arg: u64) -> io::Result { - Ok(Self { fd: arg as RawFd }) - } -} - -impl Fd { - // TODO: allocate in arena - /// Returns the filesystem path associated with this file descriptor. - /// - /// # Errors - /// Returns an error if the `/proc` readlink fails (e.g., the process has exited). - pub fn get_path(self, caller: Caller<'_>) -> nix::Result { - nix::fcntl::readlink( - if self.fd == libc::AT_FDCWD { - format!("/proc/{}/cwd", caller.pid) - } else { - format!("/proc/{}/fd/{}", caller.pid, self.fd) - } - .as_str(), - ) - } -} - -impl FromSyscallArg for c_int { - #[expect(clippy::cast_possible_truncation, reason = "syscall arg represents a c_int value")] - fn from_syscall_arg(arg: u64) -> io::Result { - Ok(arg as Self) - } -} - -pub trait FromNotify: Sized { - /// Parses syscall arguments from a seccomp notification. - /// - /// # Errors - /// Returns an error if any argument cannot be parsed. - fn from_notify(notif: &seccomp_notif) -> io::Result; -} - -impl FromNotify for (T,) { - fn from_notify(notif: &seccomp_notif) -> io::Result { - Ok((T::from_syscall_arg(notif.data.args[0])?,)) - } -} - -impl FromNotify for (T1, T2) { - fn from_notify(notif: &seccomp_notif) -> io::Result { - Ok((T1::from_syscall_arg(notif.data.args[0])?, T2::from_syscall_arg(notif.data.args[1])?)) - } -} - -impl FromNotify for (T1, T2, T3) { - fn from_notify(notif: &seccomp_notif) -> io::Result { - Ok(( - T1::from_syscall_arg(notif.data.args[0])?, - T2::from_syscall_arg(notif.data.args[1])?, - T3::from_syscall_arg(notif.data.args[2])?, - )) - } -} - -impl FromNotify - for (T1, T2, T3, T4) -{ - fn from_notify(notif: &seccomp_notif) -> io::Result { - Ok(( - T1::from_syscall_arg(notif.data.args[0])?, - T2::from_syscall_arg(notif.data.args[1])?, - T3::from_syscall_arg(notif.data.args[2])?, - T4::from_syscall_arg(notif.data.args[3])?, - )) - } -} diff --git a/crates/fspy_seccomp_unotify/src/supervisor/handler/mod.rs b/crates/fspy_seccomp_unotify/src/supervisor/handler/mod.rs deleted file mode 100644 index 52151c298..000000000 --- a/crates/fspy_seccomp_unotify/src/supervisor/handler/mod.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub mod arg; - -use std::io; - -use libc::seccomp_notif; - -#[expect(clippy::module_name_repetitions, reason = "clearer as a standalone export")] -pub trait SeccompNotifyHandler { - fn syscalls() -> &'static [syscalls::Sysno]; - /// Handles a seccomp notification for an intercepted syscall. - /// - /// # Errors - /// Returns an error if the handler fails to process the notification. - fn handle_notify(&mut self, notify: &seccomp_notif) -> io::Result<()>; -} - -#[doc(hidden)] // Re-export for use in the macro -pub use syscalls::Sysno; - -#[macro_export] -macro_rules! impl_handler { - ($type:ty: $( - $(#[$attr:meta])? - $syscall:ident, - )* ) => { - - impl $crate::supervisor::handler::SeccompNotifyHandler for $type { - fn syscalls() -> &'static [$crate::supervisor::handler::Sysno] { - &[ $( - $(#[$attr])? - $crate::supervisor::handler::Sysno::$syscall - ),* ] - } - fn handle_notify(&mut self, notify: &::libc::seccomp_notif) -> ::std::io::Result<()> { - $crate::supervisor::handler::arg::Caller::with_pid(notify.pid as _, |caller| { - $( - $(#[$attr])? - if notify.data.nr == $crate::supervisor::handler::Sysno::$syscall as _ { - return self.$syscall(caller, $crate::supervisor::handler::arg::FromNotify::from_notify(notify)?) - } - )* - Ok(()) - }) - } - } - }; -} diff --git a/crates/fspy_seccomp_unotify/src/supervisor/listener.rs b/crates/fspy_seccomp_unotify/src/supervisor/listener.rs deleted file mode 100644 index 0de6f89f5..000000000 --- a/crates/fspy_seccomp_unotify/src/supervisor/listener.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::{ - io, - os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}, -}; - -use libc::{seccomp_notif, seccomp_notif_resp}; -use tokio::io::unix::AsyncFd; -use tracing::trace; - -use crate::bindings::{ - alloc::{Alloced, alloc_seccomp_notif}, - notif_recv, -}; - -pub struct NotifyListener { - async_fd: AsyncFd, - notif_buf: Alloced, -} - -impl TryFrom for NotifyListener { - type Error = io::Error; - - fn try_from(value: OwnedFd) -> Result { - Ok(Self { async_fd: AsyncFd::new(value)?, notif_buf: alloc_seccomp_notif() }) - } -} -impl AsFd for NotifyListener { - fn as_fd(&self) -> BorrowedFd<'_> { - self.async_fd.as_fd() - } -} - -const SECCOMP_IOCTL_NOTIF_SEND: libc::Ioctl = 3_222_806_785u64 as libc::Ioctl; - -impl NotifyListener { - /// Sends a `SECCOMP_USER_NOTIF_FLAG_CONTINUE` response for the given request ID. - /// - /// # Errors - /// Returns an error if the ioctl call fails, except for `ENOENT` which is - /// silently ignored (indicates the target process's syscall was interrupted). - pub fn send_continue( - &self, - req_id: u64, - buf: &mut Alloced, - ) -> io::Result<()> { - let resp = buf.zeroed(); - resp.id = req_id; - #[expect(clippy::cast_possible_truncation, reason = "flag constant fits in u32")] - { - resp.flags = libc::SECCOMP_USER_NOTIF_FLAG_CONTINUE as u32; - } - - // SAFETY: `resp` is a valid mutable pointer to a zeroed and populated - // `seccomp_notif_resp` buffer, and the fd is a valid seccomp notify fd - let ret = unsafe { - libc::ioctl(self.async_fd.as_raw_fd(), SECCOMP_IOCTL_NOTIF_SEND, &raw mut *resp) - }; - if ret < 0 { - let err = nix::Error::last(); - // ignore error if target process's syscall was interrupted - if err == nix::Error::ENOENT { - return Ok(()); - } - return Err(err.into()); - } - Ok(()) - } - - /// Waits for and returns the next seccomp notification, or `None` if the fd is closed. - /// - /// # Errors - /// Returns an error if waiting on or reading from the notification fd fails. - pub async fn next(&mut self) -> io::Result> { - loop { - let mut ready_guard = self.async_fd.readable().await?; - let ready = ready_guard.ready(); - trace!("notify fd readable: {:?}", ready); - if ready.is_read_closed() || ready.is_write_closed() { - return Ok(None); - } - - if !ready.is_readable() { - continue; - } - // TODO: check why this call solves the issue that `is_read_closed || is_write_closed` is never true. - ready_guard.clear_ready(); - - match notif_recv(ready_guard.get_inner().as_fd(), &mut self.notif_buf) { - Ok(()) => return Ok(Some(&self.notif_buf)), - Err(nix::Error::EINTR | nix::Error::EWOULDBLOCK | nix::Error::ENOENT) => {} - Err(other_error) => return Err(other_error.into()), - } - } - } -} diff --git a/crates/fspy_seccomp_unotify/src/supervisor/mod.rs b/crates/fspy_seccomp_unotify/src/supervisor/mod.rs deleted file mode 100644 index b1aa0eb62..000000000 --- a/crates/fspy_seccomp_unotify/src/supervisor/mod.rs +++ /dev/null @@ -1,129 +0,0 @@ -pub mod handler; -mod listener; - -use std::{ - convert::Infallible, - io::{self}, - os::{ - fd::{FromRawFd, OwnedFd}, - unix::ffi::OsStrExt, - }, -}; - -use futures_util::{ - future::{Either, select}, - pin_mut, -}; -pub use handler::SeccompNotifyHandler; -use listener::NotifyListener; -use passfd::tokio::FdPassingExt; -use seccompiler::{BpfProgram, SeccompAction, SeccompFilter}; -use tokio::{ - net::UnixListener, - sync::oneshot, - task::{JoinHandle, JoinSet}, -}; -use tracing::{Level, span}; - -use crate::{ - bindings::alloc::alloc_seccomp_notif_resp, - payload::{Filter, SeccompPayload}, -}; - -pub struct Supervisor { - payload: SeccompPayload, - cancel_tx: oneshot::Sender, - handling_loop_task: JoinHandle>>, -} - -impl Supervisor { - #[must_use] - pub const fn payload(&self) -> &SeccompPayload { - &self.payload - } - - /// Stops the supervisor and returns all handler instances. - /// - /// # Panics - /// Panics if the handling loop task has panicked. - /// - /// # Errors - /// Returns an error if any of the spawned handler tasks failed with an I/O error. - pub async fn stop(self) -> io::Result> { - drop(self.cancel_tx); - self.handling_loop_task.await.expect("handling loop task panicked") - } -} - -/// Creates a new supervisor that listens for seccomp user notifications. -/// -/// # Panics -/// Panics if the seccomp filter cannot be compiled or the target architecture is unsupported. -/// -/// # Errors -/// Returns an error if the temporary IPC socket cannot be created. -pub fn supervise() -> io::Result> -{ - let notify_listener = tempfile::Builder::new() - .prefix("fspy_seccomp_notify") - .make(|path| UnixListener::bind(path))?; - - let seccomp_filter = SeccompFilter::new( - H::syscalls().iter().map(|sysno| (sysno.id().into(), vec![])).collect(), - SeccompAction::Allow, - SeccompAction::UserNotif, - std::env::consts::ARCH.try_into().unwrap(), - ) - .unwrap(); - - let bpf_filter = - Filter(BpfProgram::try_from(seccomp_filter).unwrap().into_iter().map(Into::into).collect()); - - let payload = SeccompPayload { - ipc_path: notify_listener.path().as_os_str().as_bytes().to_vec(), - filter: bpf_filter, - }; - - // The oneshot channel is used to cancel the accept loop. - // The sender doesn't need to actually send anything. Drop is enough. - let (cancel_tx, mut cancel_rx) = oneshot::channel::(); - - let handling_loop = async move { - let mut join_set: JoinSet> = JoinSet::new(); - - loop { - let accept_future = notify_listener.as_file().accept(); - pin_mut!(accept_future); - let (incoming_stream, _) = match select(&mut cancel_rx, accept_future).await { - Either::Left((Err(_), _)) => break, - Either::Right((incoming, _)) => incoming?, - }; - let notify_fd = incoming_stream.recv_fd().await?; - // SAFETY: `recv_fd` returns a valid file descriptor received via - // Unix domain socket fd passing - let notify_fd = unsafe { OwnedFd::from_raw_fd(notify_fd) }; - let mut listener = NotifyListener::try_from(notify_fd)?; - - let mut handler = H::default(); - let mut resp_buf = alloc_seccomp_notif_resp(); - - join_set.spawn(async move { - while let Some(notify) = listener.next().await? { - let _span = span!(Level::TRACE, "notify loop tick"); - // Errors on the supervisor side could be caused by a target process aborting. - // It shouldn't break the syscall handling loop as there might be target processes. - let _handle_result = handler.handle_notify(notify); - let req_id = notify.id; - listener.send_continue(req_id, &mut resp_buf)?; - } - io::Result::Ok(handler) - }); - } - let mut handlers = Vec::::new(); - while let Some(handler) = join_set.join_next().await.transpose()? { - handlers.push(handler?); - } - Ok(handlers) - }; - Ok(Supervisor { payload, cancel_tx, handling_loop_task: tokio::spawn(handling_loop) }) -} diff --git a/crates/fspy_seccomp_unotify/src/target.rs b/crates/fspy_seccomp_unotify/src/target.rs deleted file mode 100644 index 5406f7961..000000000 --- a/crates/fspy_seccomp_unotify/src/target.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::{ - ffi::OsStr, - os::{ - fd::AsRawFd, - unix::{ffi::OsStrExt, net::UnixStream}, - }, -}; - -use libc::sock_filter; -use nix::sys::prctl::set_no_new_privs; -use passfd::FdPassingExt; - -use crate::{bindings::install_unotify_filter, payload::SeccompPayload}; - -/// Installs the seccomp user notification filter and sends the notification fd -/// to the supervisor via the IPC socket. -/// -/// # Errors -/// Returns an error if setting no-new-privs fails, the filter cannot be installed, -/// or the IPC socket communication fails. -pub fn install_target(payload: &SeccompPayload) -> nix::Result<()> { - set_no_new_privs()?; - let sock_filters = - payload.filter.0.iter().copied().map(sock_filter::from).collect::>(); - let notify_fd = install_unotify_filter(&sock_filters)?; - let ipc_path = OsStr::from_bytes(&payload.ipc_path); - let ipc_unix_stream = UnixStream::connect(ipc_path) - .map_err(|err| nix::Error::try_from(err).unwrap_or(nix::Error::UnknownErrno))?; - ipc_unix_stream - .send_fd(notify_fd.as_raw_fd()) - .map_err(|err| nix::Error::try_from(err).unwrap_or(nix::Error::UnknownErrno))?; - Ok(()) -} diff --git a/crates/fspy_seccomp_unotify/tests/arg_types.rs b/crates/fspy_seccomp_unotify/tests/arg_types.rs deleted file mode 100644 index 93c1c9740..000000000 --- a/crates/fspy_seccomp_unotify/tests/arg_types.rs +++ /dev/null @@ -1,144 +0,0 @@ -#![cfg(target_os = "linux")] -use std::{ - env::{current_dir, set_current_dir}, - error::Error, - ffi::{CString, OsString}, - io, - os::unix::ffi::OsStringExt, - time::Duration, -}; - -use assertables::assert_contains; -use fspy_seccomp_unotify::{ - impl_handler, - supervisor::{ - handler::arg::{CStrPtr, Caller, Fd}, - supervise, - }, - target::install_target, -}; -use nix::{ - fcntl::{AT_FDCWD, OFlag, openat}, - sys::stat::Mode, -}; -use test_log::test; -use tokio::{process::Command, task::spawn_blocking, time::timeout}; -use tracing::{Level, span, trace}; - -#[derive(Debug, PartialEq, Eq, Clone)] -enum Syscall { - Openat { at_dir: OsString, path: Option }, -} - -#[derive(Default, Clone, Debug)] -struct SyscallRecorder(Vec); -impl SyscallRecorder { - fn openat(&mut self, caller: Caller<'_>, (fd, path): (Fd, CStrPtr)) -> io::Result<()> { - let at_dir = fd.get_path(caller)?; - let mut buf = vec![0u8; 40000]; - let path = path - .read(caller, &mut buf)? - .map(|null_pos| OsString::from_vec(buf[..null_pos].to_vec())); - self.0.push(Syscall::Openat { at_dir, path }); - Ok(()) - } -} - -impl_handler!(SyscallRecorder: openat,); - -async fn run_in_pre_exec( - mut f: impl FnMut() -> io::Result<()> + Send + Sync + 'static, -) -> Result, Box> { - Ok(timeout(Duration::from_secs(5), async move { - let mut cmd = Command::new("/bin/echo"); - let supervisor = supervise::()?; - - let payload = supervisor.payload().clone(); - - // SAFETY: `pre_exec` closure runs in the forked child process before exec. - // It installs the seccomp filter and runs the user-provided closure, both of - // which are safe in a pre-exec context (no async, no locks held). - unsafe { - cmd.pre_exec(move || { - install_target(&payload)?; - f()?; - Ok(()) - }); - } - let child_fut = spawn_blocking(move || { - let _span = span!(Level::TRACE, "spawn test child process"); - cmd.spawn() - }); - - let exit_status = child_fut.await.unwrap()?.wait().await?; - trace!("test child process exited with status: {:?}", exit_status); - - trace!("waiting for handler to finish and test child process to exit"); - - assert!(exit_status.success()); - - let recorders = supervisor.stop().await?; - trace!("{} recorders awaited", recorders.len()); - - let syscalls = recorders.into_iter().flat_map(|recorder| recorder.0); - io::Result::Ok(syscalls.collect()) - }) - .await??) -} - -#[test(tokio::test)] -async fn fd_and_path() -> Result<(), Box> { - let syscalls = run_in_pre_exec(|| { - set_current_dir("/")?; - let home_fd = openat(AT_FDCWD, c"/home", OFlag::O_PATH, Mode::empty())?; - let _ = openat(home_fd, c"open_at_home", OFlag::O_RDONLY, Mode::empty()); - let _ = openat(AT_FDCWD, c"openat_cwd", OFlag::O_RDONLY, Mode::empty()); - Ok(()) - }) - .await?; - assert_contains!(syscalls, &Syscall::Openat { at_dir: "/".into(), path: Some("/home".into()) }); - assert_contains!( - syscalls, - &Syscall::Openat { at_dir: "/home".into(), path: Some("open_at_home".into()) } - ); - assert_contains!( - syscalls, - &Syscall::Openat { at_dir: "/".into(), path: Some("openat_cwd".into()) } - ); - Ok(()) -} - -#[tokio::test] -async fn path_long() -> Result<(), Box> { - let long_path = b"a".repeat(30000); - let long_path_cstr = CString::new(long_path.as_slice()).unwrap(); - let syscalls = run_in_pre_exec(move || { - let _ = openat(AT_FDCWD, long_path_cstr.as_c_str(), OFlag::O_RDONLY, Mode::empty()); - Ok(()) - }) - .await?; - assert_contains!( - syscalls, - &Syscall::Openat { - at_dir: current_dir().unwrap().into(), - path: Some(OsString::from_vec(long_path)), - } - ); - Ok(()) -} - -#[tokio::test] -async fn path_overflow() -> Result<(), Box> { - let long_path = b"a".repeat(40000); - let long_path_cstr = CString::new(long_path.as_slice()).unwrap(); - let syscalls = run_in_pre_exec(move || { - let _ = openat(AT_FDCWD, long_path_cstr.as_c_str(), OFlag::O_RDONLY, Mode::empty()); - Ok(()) - }) - .await?; - assert_contains!( - syscalls, - &Syscall::Openat { at_dir: current_dir().unwrap().into(), path: None } - ); - Ok(()) -} diff --git a/crates/fspy_shared/.clippy.toml b/crates/fspy_shared/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/fspy_shared/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/fspy_shared/Cargo.toml b/crates/fspy_shared/Cargo.toml deleted file mode 100644 index c854d0239..000000000 --- a/crates/fspy_shared/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "fspy_shared" -version = "0.0.0" -edition = "2024" -license.workspace = true -publish = false - -[dependencies] -wincode = { workspace = true, features = ["derive"] } -bitflags = { workspace = true } -bumpalo = { workspace = true } -bstr = { workspace = true } -bytemuck = { workspace = true, features = ["must_cast", "derive"] } -fspy_shm = { workspace = true } -native_str = { workspace = true } -thiserror = { workspace = true } -tracing = { workspace = true } -uuid = { workspace = true, features = ["v4"] } -vite_path = { workspace = true } - -[target.'cfg(target_os = "windows")'.dependencies] -bytemuck = { workspace = true } -winapi = { workspace = true, features = ["std"] } - -[dev-dependencies] -assert2 = { workspace = true } -ctor = { workspace = true } -rustc-hash = { workspace = true } -subprocess_test = { workspace = true } -tokio = { workspace = true, features = ["macros", "net", "rt-multi-thread", "time"] } - -[lints] -workspace = true - -[lib] -doctest = false - -[package.metadata.cargo-shear] -ignored = ["ctor"] diff --git a/crates/fspy_shared/README.md b/crates/fspy_shared/README.md deleted file mode 100644 index e240aa9f7..000000000 --- a/crates/fspy_shared/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# fspy_shared - -Common code shared by `fspy` (run in the supervisor process) and `fspy_preload_*` (run in target processes). diff --git a/crates/fspy_shared/src/ipc/channel/mod.rs b/crates/fspy_shared/src/ipc/channel/mod.rs deleted file mode 100644 index ae34c8ca6..000000000 --- a/crates/fspy_shared/src/ipc/channel/mod.rs +++ /dev/null @@ -1,283 +0,0 @@ -//! Fast mpsc IPC channel implementation based on shared memory. - -mod shm_io; - -use std::{env::temp_dir, fs::File, io, mem::MaybeUninit, ops::Deref, path::PathBuf, sync::Arc}; - -use fspy_shm::Shm; -pub use shm_io::FrameMut; -use shm_io::{ShmReader, ShmWriter}; -use tracing::debug; -use uuid::Uuid; -use wincode::{ - SchemaRead, SchemaWrite, - config::Config, - error::{ReadResult, WriteResult}, - io::{Reader, Writer}, -}; - -use super::NativeStr; - -/// wincode schema adapter for `Arc`, which is a foreign type with unsized inner. -pub(crate) struct ArcStrSchema; - -// SAFETY: Delegates to `str`'s SchemaWrite impl, preserving its size/write invariants. -unsafe impl SchemaWrite for ArcStrSchema { - type Src = Arc; - - fn size_of(src: &Self::Src) -> WriteResult { - >::size_of(src) - } - - fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { - >::write(writer, src) - } -} - -// SAFETY: Delegates to `&str`'s SchemaRead impl; dst is initialized on Ok. -unsafe impl<'de, C: Config> SchemaRead<'de, C> for ArcStrSchema { - type Dst = Arc; - - fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { - let s: &str = <&str as SchemaRead<'de, C>>::get(&mut reader)?; - dst.write(Arc::from(s)); - Ok(()) - } -} - -/// Serializable configuration to create channel senders. -#[derive(SchemaWrite, SchemaRead, Clone, Debug)] -pub struct ChannelConf { - lock_file_path: Box, - #[wincode(with = "ArcStrSchema")] - shm_id: Arc, -} - -/// Creates a mpsc IPC channel with one receiver and a `ChannelConf` that can be passed around processes and used to create multiple senders -#[expect( - clippy::missing_errors_doc, - reason = "non-vite crate: cannot use vite_str/vite_path types" -)] -pub fn channel(capacity: usize) -> io::Result<(ChannelConf, Receiver)> { - // Initialize the lock file with a unique name. - let lock_file_path = temp_dir().join(format!("fspy_ipc_{}.lock", Uuid::new_v4())); - - let shm = fspy_shm::create(capacity)?; - - let conf = - ChannelConf { lock_file_path: lock_file_path.as_os_str().into(), shm_id: shm.id().into() }; - - let receiver = Receiver::new(lock_file_path, shm)?; - Ok((conf, receiver)) -} - -impl ChannelConf { - /// Creates a sender. - /// - /// This doesn't block on the file lock. Instead it returns immediately with error if the receiver is locked or dropped. - #[expect( - clippy::missing_errors_doc, - reason = "error conditions are self-evident from return type" - )] - pub fn sender(&self) -> io::Result { - let lock_file = File::open(self.lock_file_path.to_cow_os_str())?; - lock_file.try_lock_shared()?; - - let shm = fspy_shm::open(&self.shm_id)?; - // SAFETY: `shm` is a freshly opened shared memory region with valid pointer and size. - // Exclusive write access is ensured by the shared file lock held by this sender. - let writer = unsafe { ShmWriter::new(shm) }; - Ok(Sender { writer, lock_file, lock_file_path: self.lock_file_path.clone() }) - } -} - -pub struct Sender { - writer: ShmWriter, - lock_file_path: Box, - lock_file: File, -} - -impl Drop for Sender { - fn drop(&mut self) { - if let Err(err) = self.lock_file.unlock() { - debug!("Failed to unlock the shared IPC lock {:?}: {}", self.lock_file_path, err); - } - } -} - -impl Deref for Sender { - type Target = ShmWriter; - - fn deref(&self) -> &Self::Target { - &self.writer - } -} - -#[cfg_attr( - target_os = "windows", - expect( - clippy::non_send_fields_in_send_ty, - reason = "`Sender` holds a shared file lock that ensures there's no reader, so `shm` can be safely written to" - ) -)] -/// SAFETY: `Sender` holds a shared file lock that ensures there's no reader, so `shm` can be safely written to. -unsafe impl Send for Sender {} - -/// SAFETY: `Sender` holds a shared file lock that ensures there's no reader, so `shm` can be safely written to. -unsafe impl Sync for Sender {} - -/// The unique receiver side of an IPC channel. -/// Owns the lock file and removes it on drop. -pub struct Receiver { - lock_file_path: PathBuf, - lock_file: File, - shm: Shm, -} - -#[cfg_attr( - target_os = "windows", - expect( - clippy::non_send_fields_in_send_ty, - reason = "Receiver doesn't read or write `shm`. It only passes it to `ReceiverLockGuard` under the lock" - ) -)] -/// SAFETY: `Receiver` doesn't read or write `shm`. It only passes it to `ReceiverLockGuard` under the lock. -unsafe impl Send for Receiver {} - -/// SAFETY: `Receiver` doesn't read or write `shm`. It only passes it to `ReceiverLockGuard` under the lock. -unsafe impl Sync for Receiver {} - -impl Drop for Receiver { - fn drop(&mut self) { - if let Err(err) = std::fs::remove_file(&self.lock_file_path) { - debug!("Failed to remove IPC lock file {:?}: {}", self.lock_file_path, err); - } - } -} - -impl Receiver { - fn new(lock_file_path: PathBuf, shm: Shm) -> io::Result { - let lock_file = File::create(&lock_file_path)?; - Ok(Self { lock_file_path, lock_file, shm }) - } - - /// Lock the shared memory for unique read access. - /// Blocks until all the senders have dropped (or processes owning them have all exited) so the shared memory can be safely read. - /// During the lifetime of returned `ReceiverReadGuard`, no new senders can be created (`ChannelConf::sender` would fail). - #[expect( - clippy::missing_errors_doc, - reason = "error conditions are self-evident from return type" - )] - pub fn lock(&self) -> io::Result> { - self.lock_file.lock()?; - // SAFETY: The exclusive file lock is held, so no writers can access the shared memory. - // The lock ensures all prior writes are visible to this thread. - let reader = ShmReader::new(unsafe { self.shm.as_slice() }); - Ok(ReceiverLockGuard { reader, lock_file: &self.lock_file }) - } -} - -pub struct ReceiverLockGuard<'a> { - reader: ShmReader<&'a [u8]>, - lock_file: &'a File, -} - -impl Drop for ReceiverLockGuard<'_> { - fn drop(&mut self) { - if let Err(err) = self.lock_file.unlock() { - debug!("Failed to unlock IPC lock file: {}", err); - } - } -} -impl<'a> Deref for ReceiverLockGuard<'a> { - type Target = ShmReader<&'a [u8]>; - - fn deref(&self) -> &Self::Target { - &self.reader - } -} - -#[cfg(test)] -mod tests { - use std::{num::NonZeroUsize, str::from_utf8}; - - use bstr::B; - use subprocess_test::command_for_fn; - - use super::*; - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn smoke() { - let (conf, receiver) = channel(100).unwrap(); - let cmd = command_for_fn!(conf, |conf: ChannelConf| { - let sender = conf.sender().unwrap(); - let frame_size = NonZeroUsize::new(2).unwrap(); - let mut frame = sender.claim_frame(frame_size).unwrap(); - frame.copy_from_slice(&[4, 2]); - }); - assert!(std::process::Command::from(cmd).status().unwrap().success()); - - let lock = receiver.lock().unwrap(); - let mut frames = lock.iter_frames(); - - let received_frame = frames.next().unwrap(); - assert_eq!(received_frame, &[4, 2]); - - assert!(frames.next().is_none()); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - #[expect(clippy::print_stdout, reason = "test diagnostics")] - async fn forbid_new_senders_after_locked() { - let (conf, receiver) = channel(42).unwrap(); - let _lock = receiver.lock().unwrap(); - - let cmd = command_for_fn!(conf, |conf: ChannelConf| { - print!("{}", conf.sender().is_ok()); - }); - let output = std::process::Command::from(cmd).output().unwrap(); - assert_eq!(B(&output.stdout), B("false")); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - #[expect(clippy::print_stdout, reason = "test diagnostics")] - async fn forbid_new_senders_after_receiver_dropped() { - let (conf, receiver) = channel(42).unwrap(); - drop(receiver); - - let cmd = command_for_fn!(conf, |conf: ChannelConf| { - print!("{}", conf.sender().is_ok()); - }); - let output = std::process::Command::from(cmd).output().unwrap(); - assert_eq!(B(&output.stdout), B("false")); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn concurrent_senders() { - let (conf, receiver) = channel(8192).unwrap(); - for i in 0u16..200 { - let cmd = command_for_fn!((conf.clone(), i), |(conf, i): (ChannelConf, u16)| { - let sender = conf.sender().unwrap(); - let data_to_send = i.to_string(); - sender - .claim_frame(NonZeroUsize::new(data_to_send.len()).unwrap()) - .unwrap() - .copy_from_slice(data_to_send.as_bytes()); - }); - let output = std::process::Command::from(cmd).output().unwrap(); - assert!( - output.status.success(), - "Failed to send in iteration {}: {:?}", - i, - B(&output.stderr) - ); - } - let lock = receiver.lock().unwrap(); - let mut received_values: Vec = lock - .iter_frames() - .map(|frame| from_utf8(frame).unwrap().parse::().unwrap()) - .collect(); - received_values.sort_unstable(); - assert_eq!(received_values, (0u16..200).collect::>()); - } -} diff --git a/crates/fspy_shared/src/ipc/channel/shm_io.rs b/crates/fspy_shared/src/ipc/channel/shm_io.rs deleted file mode 100644 index 62d927cd5..000000000 --- a/crates/fspy_shared/src/ipc/channel/shm_io.rs +++ /dev/null @@ -1,728 +0,0 @@ -//! Provides lock-free concurrent writing and reading of frames in a shared memory region. - -use core::iter::from_fn; -use std::{ - num::NonZeroUsize, - ops::{Deref, DerefMut}, - ptr::slice_from_raw_parts_mut, - sync::atomic::{AtomicI32, AtomicUsize, Ordering, fence}, -}; - -use bytemuck::must_cast; -use fspy_shm::Shm; -use wincode::{SchemaWrite, Serialize as _, config::DefaultConfig}; - -// `ShmWriter` writes headers using atomic operations to prevent partial writes due to crashes, -// while `ShmReader` reads headers by simple pointer dereferences. -// This is safe because `ShmReader` is only used after all writing is done and visible to the calling thread (see docs of `ShmReader::new`). -// To ensure that the layouts of atomic types and their non-atomic counterparts are the same: -const _: () = { - assert!(size_of::() == size_of::()); - assert!(align_of::() == align_of::()); - assert!(size_of::() == size_of::()); - assert!(align_of::() == align_of::()); -}; - -/// A trait to borrow a raw memory region. -pub trait AsRawSlice { - fn as_raw_slice(&self) -> *mut [u8]; -} - -impl AsRawSlice for Shm { - fn as_raw_slice(&self) -> *mut [u8] { - slice_from_raw_parts_mut(self.as_ptr(), self.len()) - } -} - -/// A concurrent shared memory writer. -/// -/// It's lock-free and safe to use across multiple threads/processes at the same time. -/// Internally it uses atomic operations to ensure that multiple writers can write to the shared memory without -/// overwriting each other's data. -pub struct ShmWriter { - /* - Layout of the whole shared memory: - | total byte size of frames(AtomicUsize) | frame 1 | frame 2 | ..... | - - Possible layout states of each frame: - - | 0(AtomicI32) | 0000...... | all zero. This happens when the thread/process crashed right after the frame is claimed. - - | byte size of the frame (AtomicI32) | partially written data | extra 0s to align to next frame header | This happens when the thread/process crashed during writing. - - | negative byte size of the frame (AtomicI32) | fully written data | extra 0s to align to next frame header | This is the normal case (negative size indicates completion). - */ - mem: M, - - #[cfg(test)] - fail_on_claim: bool, -} - -// unsafe impl Send for ShmWriter {} -// unsafe impl Sync for ShmWriter {} - -#[track_caller] -fn assert_alignment(ptr: *const u8) { - // Assert that the header of the shm is aligned to usize - assert_eq!(ptr as usize % align_of::(), 0); - // Assert that the content after whole shm header is aligned to i32 - assert_eq!((ptr as usize + size_of::()) % align_of::(), 0); -} - -const fn roundup_to_align_frame_header(mut size: usize) -> usize { - // round up new_end so that the next frame header is aligned - const FRAME_HEADER_ALIGN: usize = align_of::(); - if !size.is_multiple_of(FRAME_HEADER_ALIGN) { - size += FRAME_HEADER_ALIGN - (size % FRAME_HEADER_ALIGN); - } - size -} - -pub struct FrameMut<'a> { - header: &'a AtomicI32, - content: &'a mut [u8], -} -impl Deref for FrameMut<'_> { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.content - } -} -impl DerefMut for FrameMut<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.content - } -} - -impl Drop for FrameMut<'_> { - fn drop(&mut self) { - // Prevents compiler from ordering memory operations. Ensure the data is visible before marking as fully written - fence(Ordering::Release); - - // Mark as fully written (negative size indicates completion) - let frame_size_i32 = - i32::try_from(self.content.len()).expect("frame size checked in `append_frame`"); - self.header.store(-frame_size_i32, Ordering::Relaxed); - } -} - -#[derive(thiserror::Error, Debug)] -pub enum WriteEncodedError { - #[error("Failed to encode value into shared memory")] - EncodeError(#[from] wincode::error::WriteError), - #[error("Tried to write a frame of zero size into shared memory")] - ZeroSizedFrame, - #[error("Not enough space in shared memory to write the encoded frame")] - InsufficientSpace, -} - -impl ShmWriter { - /// Create a new `ShmWriter` backed by a shared memory region. - /// - /// # Safety - /// - `mem.as_raw_slice()` must return a stable valid pointer to a memory region of `total` bytes, - /// - the memory region must only be accessed via `ShmWriter` across all the processes. - /// - The unused region of the shared memory must be initialized to zero. - pub unsafe fn new(mem: M) -> Self { - assert_alignment(mem.as_raw_slice() as *const u8); - Self { - mem, - #[cfg(test)] - fail_on_claim: false, - } - } - - // Unwrap `self` and return the underlying memory. - #[cfg(test)] - pub fn into_memory(self) -> M { - self.mem - } - - #[cfg(test)] - const fn set_fail_on_claim(&mut self, fail_on_claim: bool) { - self.fail_on_claim = fail_on_claim; - } - - /// Claim a frame of size `frame_size`. - /// - /// Returns `None` if there is no sufficient remaining space (or simulated crash in tests) - /// `frame_size` must be non-zero because frame header being 0 would be ambiguous. - pub fn claim_frame(&self, frame_size: NonZeroUsize) -> Option> { - let shm_slice: *mut [u8] = self.mem.as_raw_slice(); - let shm_ptr = shm_slice.cast::(); - let shm_len = self.mem.as_raw_slice().len(); - - let frame_size = frame_size.get(); - let Ok(frame_size_i32) = i32::try_from(frame_size) else { - // The frame header uses a signed 32-bit integer (i32) to store the frame size. - // Negative values are reserved to indicate completion, so only positive values are valid. - // Therefore, the maximum allowed frame size is i32::MAX (2^31-1), approximately 2GB. - // Attempting to claim a frame larger than this will fail. - return None; - }; - - // Get the atomic value of the end position (first 8 bytes of shared memory) - // SAFETY: `shm_ptr` points to the start of the shared memory region, which is properly - // aligned to `usize` (verified by `assert_alignment` in `new`), and the allocation is - // large enough to contain at least a `usize` header. - let atomic_header = unsafe { AtomicUsize::from_ptr(shm_ptr.cast()) }; - - let frame_with_header_size = size_of::() + frame_size; - - // Try to atomically claim the space - // Different writers only share the header, not each other's content. so relaxed ordering is sufficient. - let current_end = - atomic_header.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current_end| { - let new_end = roundup_to_align_frame_header(current_end + frame_with_header_size); - - // Check if we have enough space - if size_of::() + new_end > shm_len { - return None; - } - - Some(new_end) - }); - - let Ok(current_end) = current_end else { - return None; // Not enough space - }; - - #[cfg(test)] - if self.fail_on_claim { - // Simulate crash right after claiming the space - return None; - } - - // Successfully claimed the space, now write the data - - // SAFETY: The atomic fetch_update above guaranteed that `size_of::() + current_end` - // is within the shared memory bounds, so this pointer arithmetic stays within the allocation. - let frame_start = unsafe { - shm_ptr.add(/* shm header */ size_of::() + current_end) - }; - - // SAFETY: `frame_start` is properly aligned to `i32` (ensured by `roundup_to_align_frame_header`) - // and points within the shared memory allocation (bounds checked by the atomic fetch_update). - let frame_header = unsafe { AtomicI32::from_ptr(frame_start.cast()) }; - - // Mark as partially written with positive size - // Atomic operations on the frame header is only for preventing partial writes of the frame header itself (possibly due to crashes), - // not for synchronization of frame contents, so relaxed ordering is sufficient - frame_header.store(frame_size_i32, Ordering::Relaxed); - - // Prevents compiler from re-ordering memory operations. Ensure the size is visible before writing the data - fence(Ordering::Release); - - // SAFETY: `frame_start` is within bounds and adding `size_of::()` skips the frame - // header to reach the content area, which is still within the claimed space. - let frame_content_ptr = unsafe { frame_start.add(size_of::()) }; // skip the frame header - Some(FrameMut { - header: frame_header, - // SAFETY: `frame_content_ptr` is valid for `frame_size` bytes (guaranteed by the - // atomic space claim), properly aligned for `u8`, and no other writer will access - // this region because each writer atomically claims a unique range. - content: unsafe { std::slice::from_raw_parts_mut(frame_content_ptr, frame_size) }, - }) - } - - /// Append an encoded value into the shared memory. - pub fn write_encoded>( - &self, - value: &T, - ) -> Result<(), WriteEncodedError> { - let serialized_size = - usize::try_from(T::serialized_size(value)?).expect("serialized size exceeds usize"); - - let Some(frame_size) = NonZeroUsize::new(serialized_size) else { - return Err(WriteEncodedError::ZeroSizedFrame); - }; - let Some(mut frame) = self.claim_frame(frame_size) else { - return Err(WriteEncodedError::InsufficientSpace); - }; - - let mut writer: &mut [u8] = &mut frame; - T::serialize_into(&mut writer, value)?; - assert_eq!(writer.len(), 0); - - Ok(()) - } - - #[cfg(test)] - pub fn try_write_frame(&self, frame: &[u8]) -> bool { - let Some(frame_size) = NonZeroUsize::new(frame.len()) else { - return false; - }; - let Some(mut frame_mut) = self.claim_frame(frame_size) else { - return false; - }; - frame_mut.copy_from_slice(frame); - true - } -} - -/// Reader of frames in shared memory created by `ShmWriter`. -pub struct ShmReader> { - mem: M, -} - -impl> ShmReader { - /// The content of `mem` should be created by `ShmWriter`. - /// Failing to do so may result in panics (mostly out-of-bounds), but won't trigger undefined behavior. - /// - /// The `ShmReader` must be created after all writing to the shared memory is done and visible to the calling thread. - /// This is guaranteed by `M: AsRef<[u8]>`, which means the memory region is immutable during the lifetime of `ShmReader`, - /// so no need to mark `ShmReader::new` as unsafe, but care must be taken to create a safe `M` from the shared memory. - pub fn new(mem: M) -> Self { - assert_alignment(mem.as_ref().as_ptr()); - Self { mem } - } - - /// Iterate over all the frames in the shared memory. - pub fn iter_frames(&self) -> impl Iterator { - let mem = self.mem.as_ref(); - let (header, content) = mem - .split_first_chunk::<{ size_of::() }>() - .expect("mem too small to contain header"); - let content_size: usize = must_cast(*header); - let mut remaining_content = &content[..content_size]; - - from_fn(move || { - let frame_size = loop { - // looking for the next valid frame - let (frame_header, next_remaining_content) = - remaining_content.split_first_chunk::<{ size_of::() }>()?; - remaining_content = next_remaining_content; - let frame_header: i32 = must_cast(*frame_header); - match frame_header { - 0 => { - // frame was claimed but never written (crashed process) - // Keep reading until we find a non-zero header - } - 1.. => { - // Partially written frame - skip it and continue - let size = usize::try_from(frame_header).unwrap(); - remaining_content = - &remaining_content[roundup_to_align_frame_header(size)..]; - } - ..0 => { - // Fully written frame (negative size indicates completion) - break usize::try_from(-frame_header).unwrap(); - } - } - }; - - let (frame_with_padding, next_remaining_content) = - remaining_content.split_at(roundup_to_align_frame_header(frame_size)); - remaining_content = next_remaining_content; - - Some(&frame_with_padding[..frame_size]) - }) - } -} - -#[cfg(test)] -mod tests { - use std::{ - process::{Child, Command}, - sync::Arc, - thread, - }; - - use assert2::assert; - use bstr::BStr; - use rustc_hash::FxHashSet; - - use super::*; - - /// A mocked shared memory region for testing. - /// - /// To be testable for miri, the shared memory is allocated using `Arc` instead of real shared memory APIs. - #[derive(Clone)] - struct MockedShm { - // Why usize: to ensure alignment - // - // Why not Arc<[usize]>: - // According to miri, from the perspective of data racing, incrementing ref count of Arc<[T]> - // is considered the same as reading the content of [T], which conflicts with writing to [T] by `ShmWriter`. - // This problem is unrelated to real shared memory. - mem: Arc>, - /// The actual requested byte length. - /// - /// over-allocation might happen to ensure alignment of `usize`, so `mem.len()` might be inaccurate. - len: usize, - } - // SAFETY: `MockedShm` uses `Arc>` for its backing memory, which is safe to send - // across threads. The raw pointer access through `AsRawSlice` is synchronized by `ShmWriter`'s - // atomic operations. - unsafe impl Send for MockedShm {} - // SAFETY: Concurrent access to the shared memory is synchronized by `ShmWriter`'s atomic - // operations. The `Arc` wrapper ensures the allocation remains valid. - unsafe impl Sync for MockedShm {} - impl MockedShm { - fn alloc(len: usize) -> Self { - // allocates this many of usize to fit the requested byte size - let size_in_usize = len / size_of::() + 1; - - let mem: Vec = std::iter::repeat_n(0usize, size_in_usize).collect(); - - Self { mem: Arc::new(mem), len } - } - } - impl AsRef<[u8]> for MockedShm { - fn as_ref(&self) -> &[u8] { - // SAFETY: `Vec::as_ptr` returns a valid pointer to the vec's buffer. The vec is - // allocated with enough `usize` elements to cover `self.len` bytes, and the pointer - // is valid for reads of `self.len` bytes. The `Arc` ensures the allocation is alive. - unsafe { std::slice::from_raw_parts(Vec::as_ptr(&self.mem).cast(), self.len) } - } - } - - impl AsRawSlice for MockedShm { - fn as_raw_slice(&self) -> *mut [u8] { - slice_from_raw_parts_mut(Vec::as_ptr(&self.mem).cast::().cast_mut(), self.len) - } - } - - #[test] - fn single_thread_basic() { - // SAFETY: `MockedShm::alloc` provides a valid, properly-sized, zero-initialized allocation. - let writer = unsafe { ShmWriter::new(MockedShm::alloc(1024)) }; - assert!(writer.try_write_frame(b"hello")); - assert!(writer.try_write_frame(b"world")); - assert!(writer.try_write_frame(b"this is a test")); - assert!(!writer.try_write_frame(&vec![0u8; 2048])); // too large - - let reader = ShmReader::new(writer.into_memory()); - let mut frames = reader.iter_frames(); - assert_eq!(frames.next().unwrap(), b"hello"); - assert_eq!(frames.next().unwrap(), b"world"); - assert_eq!(frames.next().unwrap(), b"this is a test"); - assert_eq!(frames.next(), None); - } - #[test] - fn single_thread_empty() { - // SAFETY: `MockedShm::alloc` provides a valid, properly-sized, zero-initialized allocation. - let writer = unsafe { ShmWriter::new(MockedShm::alloc(1024)) }; - assert!(writer.try_write_frame(b"hello")); - assert!(!writer.try_write_frame(b"")); - assert!(writer.try_write_frame(b"this is a test")); - - let reader = ShmReader::new(writer.into_memory()); - let mut frames = reader.iter_frames(); - assert_eq!(frames.next().unwrap(), b"hello"); - assert_eq!(frames.next().unwrap(), b"this is a test"); - assert_eq!(frames.next(), None); - } - - #[test] - fn single_thread_crash_after_claim() { - // SAFETY: `MockedShm::alloc` provides a valid, properly-sized, zero-initialized allocation. - let mut writer = unsafe { ShmWriter::new(MockedShm::alloc(1024)) }; - assert!(writer.try_write_frame(b"foo")); - - // Simulate crash during writing - writer.set_fail_on_claim(true); - assert!(!writer.try_write_frame(b"hello")); - - writer.set_fail_on_claim(false); - assert!(writer.try_write_frame(b"bar")); - - let reader = ShmReader::new(writer.into_memory()); - let mut frames = reader.iter_frames(); - assert_eq!(frames.next().unwrap(), b"foo"); - assert_eq!(frames.next().unwrap(), b"bar"); - assert_eq!(frames.next(), None); - } - - #[test] - fn single_thread_crash_partial_write() { - // SAFETY: `MockedShm::alloc` provides a valid, properly-sized, zero-initialized allocation. - let writer = unsafe { ShmWriter::new(MockedShm::alloc(1024)) }; - assert!(writer.try_write_frame(b"foo")); - - // Simulate crash during writing - let mut frame = writer.claim_frame(5.try_into().unwrap()).unwrap(); - frame[..3].copy_from_slice(b"wor"); - std::mem::forget(frame); - - assert!(writer.try_write_frame(b"bar")); - - let reader = ShmReader::new(writer.into_memory()); - let mut frames = reader.iter_frames(); - assert_eq!(frames.next().unwrap(), b"foo"); - assert_eq!(frames.next().unwrap(), b"bar"); - assert_eq!(frames.next(), None); - } - - #[test] - fn single_thread_two_crashes_after_claim_and_partial_write() { - // This test verifies that ShmReader::iter correctly handles MULTIPLE consecutive - // invalid frames by continuing the loop. It's crucial for testing - // that the reader doesn't stop at the first invalid frame but keeps processing - // through multiple crash scenarios to find valid frames beyond them. - - // SAFETY: `MockedShm::alloc` provides a valid, properly-sized, zero-initialized allocation. - let mut writer = unsafe { ShmWriter::new(MockedShm::alloc(1024)) }; - - assert!(writer.try_write_frame(b"foo")); - - // First crash: AfterClaim (leaves frame header as 0) - writer.set_fail_on_claim(true); - assert!(!writer.try_write_frame(b"world")); - writer.set_fail_on_claim(false); - - // Second crash: PartialWrite (leaves positive frame header) - let mut frame = writer.claim_frame(5.try_into().unwrap()).unwrap(); - frame[..3].copy_from_slice(b"wor"); - std::mem::forget(frame); - - assert!(writer.try_write_frame(b"bar")); - - // ShmReader must skip BOTH invalid frames (0 header + partial header) - // and find the valid frame beyond them - this tests the loop continuation - - let reader = ShmReader::new(writer.into_memory()); - let mut frames = reader.iter_frames(); - assert_eq!(frames.next().unwrap(), b"foo"); - assert_eq!(frames.next().unwrap(), b"bar"); - assert_eq!(frames.next(), None); - } - - #[test] - fn single_thread_two_crashes_partial_write_and_after_claim() { - // SAFETY: `MockedShm::alloc` provides a valid, properly-sized, zero-initialized allocation. - let mut writer = unsafe { ShmWriter::new(MockedShm::alloc(1024)) }; - // This test verifies the same loop continuation behavior but with crashes - // in reverse order. This ensures the loop correctly handles different - // sequences of invalid frame types (partial write -> after claim). - - assert!(writer.try_write_frame(b"foo")); - - // First crash: PartialWrite (leaves positive frame header) - let mut frame = writer.claim_frame(5.try_into().unwrap()).unwrap(); - frame[..3].copy_from_slice(b"wor"); - std::mem::forget(frame); - - // Second crash: AfterClaim (leaves frame header as 0) - writer.set_fail_on_claim(true); - assert!(!writer.try_write_frame(b"world")); - writer.set_fail_on_claim(false); - - assert!(writer.try_write_frame(b"bar")); - - let reader = ShmReader::new(writer.into_memory()); - // ShmReader must skip BOTH invalid frames in this order and continue - // processing to find valid frames - tests loop robustness - let mut frames = reader.iter_frames(); - assert_eq!(frames.next().unwrap(), b"foo"); - assert_eq!(frames.next().unwrap(), b"bar"); - assert_eq!(frames.next(), None); - } - - #[test] - fn concurrent() { - let shm = MockedShm::alloc(1024 * 4); - - thread::scope(|s| { - for _ in 0..4 { - s.spawn(|| { - // SAFETY: `MockedShm::alloc` provides a valid, properly-sized, zero-initialized - // allocation. The clone shares the same backing memory, which is safe because - // `ShmWriter` uses atomic operations for concurrent access. - let writer = unsafe { ShmWriter::new(shm.clone()) }; - for _ in 0..10 { - assert!(writer.try_write_frame(b"hello")); - assert!(writer.try_write_frame(b"foo")); - assert!(writer.try_write_frame(b"this is a test")); - } - }); - } - }); - let mut count = 0; - let reader = ShmReader::new(shm); - for frame in reader.iter_frames() { - count += 1; - let frame = BStr::new(frame); - assert!(frame == b"hello" || frame == b"foo" || frame == b"this is a test"); - } - assert_eq!(count, 120); - } - - #[test] - fn concurrent_exceeded_size() { - // SAFETY: `MockedShm::alloc` provides a valid, properly-sized, zero-initialized allocation. - let writer = unsafe { ShmWriter::new(MockedShm::alloc(1024)) }; - thread::scope(|s| { - for _ in 0..4 { - s.spawn(|| { - for _ in 0..10 { - writer.try_write_frame(b"hello"); - writer.try_write_frame(b"foo"); - writer.try_write_frame(b"this is a test"); - } - }); - } - }); - let mut count = 0; - let reader = ShmReader::new(writer.into_memory()); - for frame in reader.iter_frames() { - count += 1; - let frame = BStr::new(frame); - assert!(frame == b"hello" || frame == b"foo" || frame == b"this is a test"); - } - assert!(count > 50); - } - - #[test] - fn test_integer_overflow_space_calculation() { - // Test case for potential integer overflow in space calculation - - // SAFETY: `MockedShm::alloc` provides a valid, properly-sized, zero-initialized allocation. - let writer = unsafe { ShmWriter::new(MockedShm::alloc(1024)) }; - - // Try to trigger integer overflow by using maximum values - let large_frame = vec![0u8; (i32::MAX as usize) - 100]; - - // This should fail safely, not cause overflow - assert!(!writer.try_write_frame(&large_frame)); - - // Small frame should still work - assert!(writer.try_write_frame(b"test")); - - let reader = ShmReader::new(writer.into_memory()); - let mut frames = reader.iter_frames(); - assert_eq!(frames.next().unwrap(), b"test"); - assert_eq!(frames.next(), None); - } - - #[test] - fn test_space_calculation_race_condition() { - // Test for race condition in space calculation where multiple threads - // might calculate overlapping space requirements - - // SAFETY: `MockedShm::alloc` provides a valid, properly-sized, zero-initialized allocation. - let writer = unsafe { ShmWriter::new(MockedShm::alloc(200)) }; - - // Very small buffer - thread::scope(|s| { - for _ in 0..10 { - s.spawn(|| { - // Many threads trying to write large-ish frames - writer - .try_write_frame(b"this_is_a_moderately_long_frame_that_might_cause_races"); - }); - } - }); - - // The exact count doesn't matter, but the reader should not panic - // and should handle any race conditions gracefully - - let reader = ShmReader::new(writer.into_memory()); - let mut count = 0; - for _frame in reader.iter_frames() { - count += 1; - } - // At least some but not all writes should succeed - assert!(count > 0); - assert!(count < 10); - } - - #[test] - fn test_alignment_violation_detection() { - struct Misaligned(MockedShm); - impl AsRawSlice for Misaligned { - fn as_raw_slice(&self) -> *mut [u8] { - let raw_slice = self.0.as_raw_slice(); - slice_from_raw_parts_mut( - // SAFETY: Adding 1 byte to create a deliberately misaligned pointer for testing. - // The original allocation is large enough that adding 1 byte stays within bounds. - unsafe { raw_slice.cast::().add(1) }, - raw_slice.len() - 1, - ) - } - } - // Test that alignment violations are properly detected - - // Allocate memory with proper alignment first - let shm = MockedShm::alloc(64); - - // Create a deliberately misaligned pointer by adding 1 byte - // This ensures the pointer is NOT aligned to usize boundary - let misaligned_shm = Misaligned(shm); - - // Verify the pointer is actually misaligned - assert_ne!(misaligned_shm.as_raw_slice().cast::() as usize % align_of::(), 0); - - // This should panic due to alignment assertion - let result = std::panic::catch_unwind(|| { - // SAFETY: Intentionally passing a misaligned pointer to test that the alignment - // assertion in `ShmWriter::new` correctly panics. This is expected to panic. - unsafe { ShmWriter::new(misaligned_shm) }; - }); - - // Verify that the alignment check properly caught the violation - assert!(result.is_err(), "Should panic on misaligned pointer"); - } - - #[test] - #[cfg(not(miri))] - fn real_shm_across_processes() { - use subprocess_test::command_for_fn; - - const CHILD_COUNT: usize = 12; - const FRAME_COUNT_EACH_CHILD: usize = 100; - - const SHM_SIZE: usize = 1024 * 1024; - - // On Linux, `fspy_shm::create` spawns the mapping's broker onto the - // ambient tokio runtime, which serves the child processes' opens. - #[cfg(target_os = "linux")] - let runtime = tokio::runtime::Builder::new_multi_thread() - .worker_threads(1) - .enable_io() - .enable_time() - .build() - .unwrap(); - #[cfg(target_os = "linux")] - let _guard = runtime.enter(); - - let shm = fspy_shm::create(SHM_SIZE).unwrap(); - let shm_name = shm.id().to_owned(); - - let children: Vec = (0..CHILD_COUNT) - .map(|child_index| { - let cmd = command_for_fn!( - (shm_name.clone(), child_index), - |(shm_name, child_index): (String, usize)| { - let shm = fspy_shm::open(&shm_name).unwrap(); - // SAFETY: `shm` is a freshly opened shared memory region with a valid - // pointer and size. Concurrent write access is safe because `ShmWriter` - // uses atomic operations. - let writer = unsafe { ShmWriter::new(shm) }; - for i in 0..FRAME_COUNT_EACH_CHILD { - let frame_data = std::format!("{child_index} {i}"); - assert!(writer.try_write_frame(frame_data.as_bytes())); - } - } - ); - Command::from(cmd).spawn().unwrap() - }) - .collect(); - - for mut c in children { - let status = c.wait().unwrap(); - assert!(status.success()); - } - - // SAFETY: All child processes have exited (waited above), so no concurrent writers exist. - // The shared memory is valid and fully written. - let shm = unsafe { shm.as_slice() }; - let reader = ShmReader::new(shm); - let frames = reader.iter_frames().map(BStr::new).collect::>(); - assert_eq!(frames.len(), CHILD_COUNT * FRAME_COUNT_EACH_CHILD); - for child_index in 0..CHILD_COUNT { - for i in 0..FRAME_COUNT_EACH_CHILD { - let frame_data = format!("{child_index} {i}"); - assert!(frames.contains(&BStr::new(frame_data.as_bytes()))); - } - } - } -} diff --git a/crates/fspy_shared/src/ipc/mod.rs b/crates/fspy_shared/src/ipc/mod.rs deleted file mode 100644 index c7236e5d6..000000000 --- a/crates/fspy_shared/src/ipc/mod.rs +++ /dev/null @@ -1,49 +0,0 @@ -#[cfg(not(target_env = "musl"))] -pub mod channel; -mod native_path; -use std::fmt::Debug; - -use bitflags::bitflags; -pub use native_path::NativePath; -pub use native_str::NativeStr; -use wincode::{SchemaRead, SchemaWrite}; - -#[derive(SchemaWrite, SchemaRead, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub struct AccessMode(u8); - -bitflags! { - impl AccessMode: u8 { - const READ = 1; - const WRITE = 1 << 1; - const READ_DIR = 1 << 2; - } -} - -impl Debug for AccessMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - struct InternalAccessMode(AccessMode); - impl Debug for InternalAccessMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - bitflags::parser::to_writer(&self.0, f) - } - } - f.debug_tuple("AccessMode").field(&InternalAccessMode(*self)).finish() - } -} - -#[derive(SchemaWrite, SchemaRead, Debug, Clone, Copy, PartialEq, Eq)] -pub struct PathAccess<'a> { - pub mode: AccessMode, - pub path: &'a NativePath, - // TODO: add follow_symlinks (O_NOFOLLOW) -} - -impl<'a> PathAccess<'a> { - pub fn read(path: impl Into<&'a NativePath>) -> Self { - Self { mode: AccessMode::READ, path: path.into() } - } - - pub fn read_dir(path: impl Into<&'a NativePath>) -> Self { - Self { mode: AccessMode::READ_DIR, path: path.into() } - } -} diff --git a/crates/fspy_shared/src/ipc/native_path.rs b/crates/fspy_shared/src/ipc/native_path.rs deleted file mode 100644 index 3149c979f..000000000 --- a/crates/fspy_shared/src/ipc/native_path.rs +++ /dev/null @@ -1,90 +0,0 @@ -#[cfg(unix)] -use std::ffi::OsStr; -#[cfg(unix)] -use std::os::unix::ffi::OsStrExt as _; -use std::{ - fmt::Debug, - mem::MaybeUninit, - path::{Path, StripPrefixError}, -}; - -use bumpalo::Bump; -use bytemuck::TransparentWrapper; -use native_str::NativeStr; -use wincode::{ - SchemaRead, SchemaWrite, - config::Config, - error::{ReadResult, WriteResult}, - io::{Reader, Writer}, -}; - -/// An opaque path type used in [`super::PathAccess`]. -/// -/// On Windows, tracked paths are NT Object Manager paths (`\??` prefix), -/// whose raw data is not meaningful for direct consumption. The only way -/// to use the path is through [`strip_path_prefix`](NativePath::strip_path_prefix), -/// which normalizes platform differences and extracts a workspace-relative path. -#[derive(TransparentWrapper, PartialEq, Eq)] -#[repr(transparent)] -pub struct NativePath { - inner: NativeStr, -} - -// Manual impl: wincode derive requires Sized, but NativePath wraps unsized NativeStr. -// SAFETY: Delegates to `NativeStr`'s SchemaWrite impl, preserving its invariants. -unsafe impl SchemaWrite for NativePath { - type Src = Self; - - fn size_of(src: &Self::Src) -> WriteResult { - >::size_of(&src.inner) - } - - fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { - >::write(writer, &src.inner) - } -} - -// SAFETY: Delegates to `&NativeStr`'s SchemaRead impl; dst is initialized on Ok. -unsafe impl<'de, C: Config> SchemaRead<'de, C> for &'de NativePath { - type Dst = &'de NativePath; - - fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { - let inner: &'de NativeStr = <&NativeStr as SchemaRead<'de, C>>::get(&mut reader)?; - dst.write(NativePath::wrap_ref(inner)); - Ok(()) - } -} - -impl NativePath { - #[cfg(windows)] - #[must_use] - pub fn from_wide(wide: &[u16]) -> &Self { - Self::wrap_ref(NativeStr::from_wide(wide)) - } - - pub fn clone_in<'bump>(&self, bump: &'bump Bump) -> &'bump Self { - Self::wrap_ref(self.inner.clone_in(bump)) - } - - pub fn strip_path_prefix, R, F: FnOnce(Result<&Path, StripPrefixError>) -> R>( - &self, - base: P, - f: F, - ) -> R { - let me = self.inner.to_cow_os_str(); - f(vite_path::strip_path_prefix(&me, base.as_ref().as_os_str())) - } -} - -impl Debug for NativePath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(&self.inner, f) - } -} - -#[cfg(unix)] -impl<'a, S: AsRef + ?Sized> From<&'a S> for &'a NativePath { - fn from(value: &'a S) -> Self { - NativePath::wrap_ref(NativeStr::from_bytes(value.as_ref().as_bytes())) - } -} diff --git a/crates/fspy_shared/src/lib.rs b/crates/fspy_shared/src/lib.rs deleted file mode 100644 index e44d2b7ea..000000000 --- a/crates/fspy_shared/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod ipc; - -#[cfg(windows)] -pub mod windows; diff --git a/crates/fspy_shared/src/windows/mod.rs b/crates/fspy_shared/src/windows/mod.rs deleted file mode 100644 index cf7c536be..000000000 --- a/crates/fspy_shared/src/windows/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -use winapi::DEFINE_GUID; -use wincode::{SchemaRead, SchemaWrite}; - -use crate::ipc::channel::ChannelConf; - -// Generated by guidgen.exe -// {FC4845F1-3A8B-4F05-A3D3-A5E9E102AF33} -DEFINE_GUID!( - PAYLOAD_ID, - 0xfc48_45f1, - 0x3a8b, - 0x4f05, - 0xa3, - 0xd3, - 0xa5, - 0xe9, - 0xe1, - 0x02, - 0xaf, - 0x33 -); - -#[derive(SchemaWrite, SchemaRead, Debug, Clone)] -pub struct Payload<'a> { - pub channel_conf: ChannelConf, - pub ansi_dll_path_with_nul: &'a [u8], -} diff --git a/crates/fspy_shared_unix/.clippy.toml b/crates/fspy_shared_unix/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/fspy_shared_unix/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/fspy_shared_unix/Cargo.toml b/crates/fspy_shared_unix/Cargo.toml deleted file mode 100644 index 38b9301bb..000000000 --- a/crates/fspy_shared_unix/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "fspy_shared_unix" -version = "0.0.0" -edition.workspace = true -license.workspace = true -publish = false - -[target.'cfg(unix)'.dependencies] -anyhow = { workspace = true } -base64 = { workspace = true } -wincode = { workspace = true, features = ["derive"] } -bstr = { workspace = true } -fspy_shared = { workspace = true } -nix = { workspace = true, features = ["fs"] } -stackalloc = { workspace = true } - -[dev-dependencies] - -[target.'cfg(target_os = "linux")'.dependencies] -elf = { workspace = true } -fspy_seccomp_unotify = { workspace = true, features = ["target"] } -memmap2 = { workspace = true } - -[target.'cfg(target_os = "macos")'.dependencies] -phf = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/fspy_shared_unix/README.md b/crates/fspy_shared_unix/README.md deleted file mode 100644 index 241514631..000000000 --- a/crates/fspy_shared_unix/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# fspy_shared_unix - -Common code shared by `fspy` (run in the supervisor process) and `fspy_preload_unix` (run in target processes, macOS/Linux only). diff --git a/crates/fspy_shared_unix/src/elf.rs b/crates/fspy_shared_unix/src/elf.rs deleted file mode 100644 index 7dde8637c..000000000 --- a/crates/fspy_shared_unix/src/elf.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::{ - ffi::{CStr, OsStr}, - os::unix::ffi::OsStrExt as _, - path::Path, -}; - -use bstr::BStr; -use elf::{ElfBytes, abi::PT_INTERP, endian::AnyEndian}; - -/// Checks whether the given ELF executable is dynamically linked to libc. -/// -/// # Errors -/// -/// Returns `ENOEXEC` if the binary cannot be parsed as a valid ELF file. -pub fn is_dynamically_linked_to_libc(executable: impl AsRef<[u8]>) -> nix::Result { - let executable = executable.as_ref(); - let Some(interp) = get_interp(executable)? else { - return Ok(false); - }; - let Some(interp_filename) = Path::new(OsStr::from_bytes(interp)).file_name() else { - return Ok(false); - }; - let interp_filename = interp_filename.as_bytes(); - Ok(interp_filename.starts_with(b"ld-") || interp_filename.starts_with(b"ld.")) -} - -fn get_interp(executable: &[u8]) -> nix::Result> { - let elf = - ElfBytes::<'_, AnyEndian>::minimal_parse(executable).map_err(|_| nix::Error::ENOEXEC)?; - let Some(headers) = elf.segments() else { - return Ok(None); - }; - - let Some(interp_header) = headers.into_iter().find(|header| header.p_type == PT_INTERP) else { - return Ok(None); - }; - let Ok(interp) = elf.segment_data(&interp_header) else { - return Err(nix::Error::ENOEXEC); - }; - - let interp = CStr::from_bytes_until_nul(interp).map_or(interp, CStr::to_bytes); - Ok(Some(BStr::new(interp))) -} - -#[cfg(test)] -mod tests { - use std::fs::read; - - use super::*; - #[test] - fn dynamic_executable() { - assert!(is_dynamically_linked_to_libc(read("/bin/sh").unwrap()).unwrap()); - } - #[test] - fn static_executable() { - let cat = read("/bin/cat").unwrap(); - let ld_so_path = get_interp(&cat).unwrap().unwrap(); - - assert!( - !is_dynamically_linked_to_libc(read(OsStr::from_bytes(ld_so_path)).unwrap()).unwrap() - ); - } -} diff --git a/crates/fspy_shared_unix/src/exec/mod.rs b/crates/fspy_shared_unix/src/exec/mod.rs deleted file mode 100644 index 094cd6967..000000000 --- a/crates/fspy_shared_unix/src/exec/mod.rs +++ /dev/null @@ -1,296 +0,0 @@ -mod shebang; -mod which; - -use std::{ - ffi::{CStr, OsStr}, - iter::once, - mem::replace, - os::unix::ffi::OsStrExt, - path::Path, -}; - -use bstr::{BStr, BString, ByteSlice}; -use fspy_shared::ipc::AccessMode; -use nix::unistd::{AccessFlags, access}; -use shebang::{ParseShebangOptions, parse_shebang}; - -use crate::open_exec::open_executable; - -#[derive(Debug, Clone)] -pub struct SearchPath<'a> { - /// Custom search path to use (like execvP), overrides PATH if Some - pub custom_path: Option<&'a BStr>, -} - -/// Configuration for exec resolution behavior -#[derive(Debug, Clone)] -pub struct ExecResolveConfig<'a> { - /// If Some and the program doesn't contains `/`, - /// search the program in PATH (like execvp, execvpe, execlp) instead of finding it in current directory - pub search_path: Option>, - /// Options for parsing shebangs (all exec variants handle shebangs) - pub shebang_options: ParseShebangOptions, -} - -impl<'a> ExecResolveConfig<'a> { - /// Configuration for execve - no PATH search, direct execution - #[must_use] - pub fn search_path_disabled() -> Self { - Self { search_path: None, shebang_options: ParseShebangOptions::default() } - } - - /// execlp/execvp/execvP/execvpe - /// `custom_path` allows a customized path to be searched like in execvP (macOS extension) - #[must_use] - pub fn search_path_enabled(custom_path: Option<&'a BStr>) -> Self { - Self { - search_path: Some(SearchPath { custom_path }), - shebang_options: ParseShebangOptions::default(), - } - } -} - -#[derive(Debug)] -pub struct Exec { - pub program: BString, - pub args: Vec, - /// vec of (name, value). value is None when the entry in environ doesn't contain a `=` character. - pub envs: Vec<(BString, Option)>, -} - -fn getenv(name: &CStr) -> Option<&'static CStr> { - // SAFETY: `getenv` is a C standard library function, called with a valid pointer from `CStr::as_ptr`. - let value = unsafe { nix::libc::getenv(name.as_ptr().cast()) }; - if value.is_null() { - None - } else { - // SAFETY: `value` is non-null (checked above) and points to a null-terminated string owned - // by the environment, as guaranteed by the C `getenv` contract. - Some(unsafe { CStr::from_ptr(value) }) - } -} - -fn peek_executable(path: &Path, buf: &mut [u8]) -> nix::Result { - let fd = open_executable(path)?; - let mut total_read_size = 0; - loop { - let read_size = nix::unistd::read(&fd, &mut buf[total_read_size..])?; - if read_size == 0 { - break; - } - total_read_size += read_size; - } - Ok(total_read_size) -} - -impl Exec { - /// Resolve the program path according to exec family semantics - /// - /// This method replicates the behavior of execve/execvp/execvP/execvpe for program resolution, - /// including PATH searching and shebang handling. - /// - /// # Returns - /// - /// * `Ok(())` if resolution succeeds and `self` is updated with resolved paths - /// * `Err(nix::Error)` with appropriate errno, like the exec function would return - /// - /// # Errors - /// - /// Returns an error if: - /// - The program is not found in PATH (`ENOENT`) - /// - The program file cannot be accessed or read (`EACCES`, `EISDIR`, `EIO`) - /// - Shebang parsing fails due to I/O errors (`EIO`) - pub fn resolve( - &mut self, - mut on_path_access: impl FnMut(AccessMode, &Path), - config: ExecResolveConfig, - ) -> nix::Result<()> { - if let Some(search_path) = config.search_path { - let path = search_path.custom_path.unwrap_or_else(|| { - getenv(c"PATH").map_or_else( - || { - // https://github.com/kraj/musl/blob/1b06420abdf46f7d06ab4067e7c51b8b63731852/src/process/execvp.c#L21 - b"/usr/local/bin:/bin:/usr/bin".as_bstr() - }, - |path| path.to_bytes().as_bstr(), - ) - }); - let program = which::which( - self.program.as_ref(), - path, - |path| { - on_path_access(AccessMode::READ, Path::new(OsStr::from_bytes(path))); - access(OsStr::from_bytes(path), AccessFlags::X_OK) - }, - |program| Ok(program.to_owned()), - )?; - self.program = program; - } - - self.parse_shebang(on_path_access, config.shebang_options)?; - - Ok(()) - } - - fn parse_shebang( - &mut self, - mut on_path_access: impl FnMut(AccessMode, &Path), - options: ParseShebangOptions, - ) -> nix::Result<()> { - if let Some(shebang) = parse_shebang( - |path, buf| { - on_path_access(AccessMode::READ, path); - peek_executable(path, buf) - }, - Path::new(OsStr::from_bytes(&self.program)), - options, - )? { - self.args[0] = shebang.interpreter.clone(); - let old_program = replace(&mut self.program, shebang.interpreter); - self.args.splice(1..1, shebang.arguments.into_iter().chain(once(old_program))); - } - Ok(()) - } -} - -/// Ensures an environment variable is set to the specified value -/// -/// If the variable doesn't exist, it is added. If it exists with the same value, -/// no change is made. If it exists with a different value, an error is returned. -/// -/// # Errors -/// -/// Returns `Err(nix::Error::EINVAL)` if the environment variable already exists with a different value. -pub fn ensure_env( - envs: &mut Vec<(BString, Option)>, - name: impl AsRef, - value: impl AsRef, -) -> nix::Result<()> { - let name = name.as_ref(); - let value = value.as_ref(); - let existing_value = envs.iter().find_map(|(n, v)| if n == name { v.as_ref() } else { None }); - if let Some(existing_value) = existing_value { - return if existing_value == value { Ok(()) } else { Err(nix::Error::EINVAL) }; - } - envs.push((name.to_owned(), Some(value.to_owned()))); - Ok(()) -} - -/// Ensures `value` is the trailing colon-separated entry of env var `name`. -/// -/// Used for `LD_PRELOAD` / `DYLD_INSERT_LIBRARIES`, which the dynamic loader -/// treats as colon-separated lists. Appending (rather than overwriting) -/// preserves any user-provided preload, and appending to the *end* keeps -/// fspy's shim as the last interposer so a user preload that short-circuits -/// a call (returning without forwarding to libc) stays invisible to fspy — -/// mirroring what the OS actually did. -/// -/// - Absent: inserts `(name, value)`. -/// - Present with `value` already as the last colon-separated entry: no -/// change (idempotent across nested execs within the preloaded shim). -/// - Present otherwise: rewrites to `{existing}:{value}`. If `existing` is -/// empty, sets to `value` alone to avoid a leading `:` (which glibc's -/// `ld.so` interprets as the current directory). -pub fn append_path_env( - envs: &mut Vec<(BString, Option)>, - name: impl AsRef, - value: impl AsRef, -) { - let name = name.as_ref(); - let value = value.as_ref(); - if let Some(entry) = envs.iter_mut().find(|(n, _)| n == name) { - let existing: &[u8] = entry.1.as_deref().map_or(&[][..], |v| v.as_ref()); - let value_bytes: &[u8] = value.as_ref(); - let already_last = existing == value_bytes - || (existing.len() > value_bytes.len() - && existing.ends_with(value_bytes) - && existing[existing.len() - value_bytes.len() - 1] == b':'); - if already_last { - return; - } - let mut new_value = Vec::with_capacity(existing.len() + 1 + value_bytes.len()); - if !existing.is_empty() { - new_value.extend_from_slice(existing); - new_value.push(b':'); - } - new_value.extend_from_slice(value_bytes); - entry.1 = Some(BString::from(new_value)); - } else { - envs.push((name.to_owned(), Some(value.to_owned()))); - } -} - -#[cfg(test)] -mod tests { - use bstr::BString; - - use super::append_path_env; - - fn env(envs: &[(BString, Option)], name: &[u8]) -> Option> { - envs.iter() - .find(|(n, _)| AsRef::<[u8]>::as_ref(n) == name) - .and_then(|(_, v)| v.as_ref().map(|v| AsRef::<[u8]>::as_ref(v).to_vec())) - } - - #[test] - fn inserts_when_absent() { - let mut envs: Vec<(BString, Option)> = vec![]; - append_path_env(&mut envs, "LD_PRELOAD", "/a.so"); - assert_eq!(env(&envs, b"LD_PRELOAD"), Some(b"/a.so".to_vec())); - } - - #[test] - fn noop_when_equal() { - let mut envs = vec![(BString::from("LD_PRELOAD"), Some(BString::from("/a.so")))]; - append_path_env(&mut envs, "LD_PRELOAD", "/a.so"); - assert_eq!(env(&envs, b"LD_PRELOAD"), Some(b"/a.so".to_vec())); - } - - #[test] - fn noop_when_value_is_last_entry() { - let mut envs = vec![(BString::from("LD_PRELOAD"), Some(BString::from("/user.so:/a.so")))]; - append_path_env(&mut envs, "LD_PRELOAD", "/a.so"); - assert_eq!(env(&envs, b"LD_PRELOAD"), Some(b"/user.so:/a.so".to_vec())); - } - - #[test] - fn appends_with_colon_when_present_and_different() { - let mut envs = vec![(BString::from("LD_PRELOAD"), Some(BString::from("/user.so")))]; - append_path_env(&mut envs, "LD_PRELOAD", "/a.so"); - assert_eq!(env(&envs, b"LD_PRELOAD"), Some(b"/user.so:/a.so".to_vec())); - } - - #[test] - fn sets_without_leading_colon_when_existing_is_empty() { - let mut envs = vec![(BString::from("LD_PRELOAD"), Some(BString::from("")))]; - append_path_env(&mut envs, "LD_PRELOAD", "/a.so"); - assert_eq!(env(&envs, b"LD_PRELOAD"), Some(b"/a.so".to_vec())); - } - - #[test] - fn idempotent_on_repeat() { - let mut envs: Vec<(BString, Option)> = vec![]; - append_path_env(&mut envs, "LD_PRELOAD", "/a.so"); - append_path_env(&mut envs, "LD_PRELOAD", "/a.so"); - append_path_env(&mut envs, "LD_PRELOAD", "/a.so"); - assert_eq!(env(&envs, b"LD_PRELOAD"), Some(b"/a.so".to_vec())); - } - - #[test] - fn does_not_false_match_prefix_without_preceding_colon() { - // `lib/a.so` ends with `/a.so` as bytes, but the preceding byte is - // `b` not `:`, so it must NOT be treated as already-present. - let mut envs = vec![(BString::from("LD_PRELOAD"), Some(BString::from("/lib/a.so")))]; - append_path_env(&mut envs, "LD_PRELOAD", "a.so"); - assert_eq!(env(&envs, b"LD_PRELOAD"), Some(b"/lib/a.so:a.so".to_vec())); - } - - #[test] - fn inserts_when_present_with_none_value() { - // An env var present in the list but with `None` value (name without - // `=`) should be rewritten to `Some(value)`. - let mut envs = vec![(BString::from("LD_PRELOAD"), None)]; - append_path_env(&mut envs, "LD_PRELOAD", "/a.so"); - assert_eq!(env(&envs, b"LD_PRELOAD"), Some(b"/a.so".to_vec())); - } -} diff --git a/crates/fspy_shared_unix/src/exec/shebang.rs b/crates/fspy_shared_unix/src/exec/shebang.rs deleted file mode 100644 index 245b19b46..000000000 --- a/crates/fspy_shared_unix/src/exec/shebang.rs +++ /dev/null @@ -1,230 +0,0 @@ -use std::path::Path; - -use bstr::{BString, ByteSlice}; - -#[derive(Debug, Clone)] -pub struct Shebang { - pub interpreter: BString, - pub arguments: Vec, -} - -const fn is_whitespace(c: u8) -> bool { - c == b' ' || c == b'\t' -} - -#[derive(Clone, Copy, Debug)] -pub struct ParseShebangOptions { - pub split_arguments: bool, // TODO: recursive -} - -#[cfg_attr( - not(target_vendor = "apple"), - expect( - clippy::derivable_impls, - reason = "on macOS split_arguments defaults to true via cfg!, which is not derivable" - ) -)] -impl Default for ParseShebangOptions { - fn default() -> Self { - Self { split_arguments: cfg!(target_vendor = "apple") } - } -} - -pub fn parse_shebang( - mut peek_executable: impl FnMut(&Path, &mut [u8]) -> nix::Result, - path: &Path, - options: ParseShebangOptions, -) -> Result, nix::Error> { - // https://lwn.net/Articles/779997/ - // > The array used to hold the shebang line is defined to be 128 bytes in length - // TODO: check linux/macOS' kernel source - const PEEK_SIZE: usize = 128; - - let mut buf = [0u8; PEEK_SIZE]; - - let total_read_size = peek_executable(path, &mut buf)?; - - let Some(buf) = buf[..total_read_size].strip_prefix(b"#!") else { - return Ok(None); - }; - - let Some(buf) = buf.split(|ch| matches!(*ch, b'\n')).next() else { - // https://github.com/torvalds/linux/blob/5723cc3450bccf7f98f227b9723b5c9f6b3af1c5/fs/binfmt_script.c#L59-L80 - return Err(nix::Error::ENOEXEC); - }; - let buf = buf.trim_ascii(); - let Some(interpreter) = buf.split(|ch| is_whitespace(*ch)).next() else { - return Ok(None); - }; - let arguments_buf = buf[interpreter.len()..].trim_ascii_start().as_bstr(); - - let arguments: Vec = if options.split_arguments { - arguments_buf - .split(|ch| is_whitespace(*ch)) - .filter_map(|arg| { - let arg = arg.trim_ascii(); - if arg.is_empty() { None } else { Some(arg.as_bstr().to_owned()) } - }) - .collect() - } else if arguments_buf.is_empty() { - vec![] - } else { - vec![arguments_buf.to_owned()] - }; - - Ok(Some(Shebang { interpreter: interpreter.as_bstr().to_owned(), arguments })) -} - -// #[derive(Debug)] -// pub struct RecursiveParseOpts { -// pub recursion_limit: usize, -// pub split_arguments: bool, -// } - -// impl Default for RecursiveParseOpts { -// fn default() -> Self { -// Self { -// recursion_limit: 4, // BINPRM_MAX_RECURSION -// split_arguments: false, -// } -// } -// } - -// fn parse_shebang_recursive_impl( -// buf: &mut [u8], -// reader: R, -// mut get_reader: impl FnMut(&OsStr) -> io::Result, -// mut on_shebang: impl FnMut(shebang<'_>) -> io::Result<()>, -// ) -> io::Result<()> { -// let Some(mut shebang) = parse_shebang(buf, reader)? else { -// return Ok(()); -// }; -// on_shebang(shebang)?; -// loop { -// let reader = get_reader(&shebang.interpreter)?; -// let Some(cur_shebang) = parse_shebang(buf, reader)? else { -// break Ok(()); -// }; -// on_shebang(cur_shebang)?; -// shebang = cur_shebang; -// } -// } - -// pub fn parse_shebang_recursive< -// const PEEK_CAP: usize, -// R: Read, -// O: FnMut(&OsStr) -> io::Result, -// C: FnMut(&OsStr) -> io::Result<()>, -// >( -// opts: RecursiveParseOpts, -// reader: R, -// open: O, -// mut on_arg_reverse: C, -// ) -> io::Result<()> { -// let mut peek_buf = [0u8; PEEK_CAP]; -// let mut recursive_count = 0; -// parse_shebang_recursive_impl(&mut peek_buf, reader, open, |shebang| { -// if recursive_count > opts.recursion_limit { -// return Err(io::Error::from_raw_os_error(libc::ELOOP)); -// } -// if opts.split_arguments { -// for arg in shebang.arguments.split().rev() { -// on_arg_reverse(arg)?; -// } -// } else { -// on_arg_reverse(shebang.arguments.as_one())?; -// } -// on_arg_reverse(shebang.interpreter)?; -// recursive_count += 1; -// Ok(()) -// })?; -// Ok(()) -// } - -// #[cfg(test)] -// mod tests { -// use std::os::unix::ffi::OsStrExt; - -// use super::*; - -// #[test] -// fn shebang_basic() { -// let mut buf = [0u8; PEEK_SIZE]; -// let shebang = parse_shebang(&mut buf, "#!/bin/sh a b\n".as_bytes()) -// .unwrap() -// .unwrap(); -// assert_eq!(shebang.interpreter.as_bytes(), b"/bin/sh"); -// assert_eq!(shebang.arguments.as_one().as_bytes(), b"a b"); -// assert_eq!( -// shebang -// .arguments -// .split() -// .map(OsStrExt::as_bytes) -// .collect::>(), -// vec![b"a", b"b"] -// ); -// } - -// #[test] -// fn shebang_trimming_spaces() { -// let mut buf = [0u8; PEEK_SIZE]; -// let shebang = parse_shebang(&mut buf, "#! /bin/sh a \n".as_bytes()) -// .unwrap() -// .unwrap(); -// assert_eq!(shebang.interpreter, "/bin/sh"); -// assert_eq!(shebang.arguments.as_one().as_bytes(), b"a"); -// assert_eq!( -// shebang -// .arguments -// .split() -// .map(OsStrExt::as_bytes) -// .collect::>(), -// vec![b"a"] -// ); -// } - -// #[test] -// fn shebang_split_arguments() { -// let mut buf = [0u8; PEEK_SIZE]; -// let shebang = parse_shebang(&mut buf, "#! /bin/sh a b\tc \n".as_bytes()) -// .unwrap() -// .unwrap(); -// assert_eq!(shebang.interpreter, "/bin/sh"); -// assert_eq!( -// shebang -// .arguments -// .split() -// .map(OsStrExt::as_bytes) -// .collect::>(), -// &[b"a", b"b", b"c"] -// ); -// } -// #[test] -// fn shebang_recursive_basic() { -// let mut args = Vec::::new(); -// parse_shebang_recursive::( -// RecursiveParseOpts { -// split_arguments: true, -// ..RecursiveParseOpts::default() -// }, -// "#!/bin/B bparam".as_bytes(), -// |path| { -// Ok(match path.as_bytes() { -// b"/bin/B" => "#! /bin/A aparam1 aparam2".as_bytes(), -// b"/bin/A" => "not a shebang script".as_bytes(), -// _ => unreachable!("Unexpected path: {}", path.display()), -// }) -// }, -// |arg| { -// args.push(str::from_utf8(arg.as_bytes()).unwrap().to_owned()); -// Ok(()) -// }, -// ) -// .unwrap(); -// args.reverse(); -// assert_eq!( -// args, -// vec!["/bin/A", "aparam1", "aparam2", "/bin/B", "bparam"] -// ); -// } -// } diff --git a/crates/fspy_shared_unix/src/exec/which.rs b/crates/fspy_shared_unix/src/exec/which.rs deleted file mode 100644 index ed6e290bf..000000000 --- a/crates/fspy_shared_unix/src/exec/which.rs +++ /dev/null @@ -1,207 +0,0 @@ -use bstr::{BStr, ByteSlice}; -use stackalloc::alloca; - -fn concat(s: &[&BStr], callback: impl FnOnce(&BStr) -> R) -> R { - let size = s.iter().map(|s| s.len()).sum(); - alloca(size, |buf| { - debug_assert_eq!(buf.len(), size); - let mut pos = 0usize; - for s in s { - let next_pos = pos + s.len(); - let bytes: &[u8] = s.as_ref(); - let src_ptr = bytes.as_ptr(); - let dst_ptr = buf[pos..next_pos].as_mut_ptr().cast::(); - // SAFETY: `src_ptr` and `dst_ptr` are derived from valid slices of known lengths, - // they do not overlap (src is from the input slice, dst is from the stack-allocated buffer), - // and `s.len()` bytes are within bounds for both. - unsafe { - std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, s.len()); - } - pos = next_pos; - } - debug_assert_eq!(pos, buf.len()); - // SAFETY: `buf.as_ptr()` points to a valid allocation of `buf.len()` bytes that was - // fully initialized by the copy loop above (verified by the debug_assert). - let bytes = unsafe { std::slice::from_raw_parts(buf.as_ptr().cast::(), buf.len()) }; - callback(bytes.as_bstr()) - }) -} - -const NAME_MAX: usize = 255; - -/// Search the executable in PATH. -/// -/// Referenced musl Implementation: -/// -/// Difference from musl: -/// - Instead of actually calling execve, use `access_executable` to check if the file is executable, and call `callback` with the found executable. -/// - The path limit (`PATH_MAX`) is not checked. -/// - PATH is passed as parameter instead of using the real environment variable. -pub fn which( - file: &BStr, - path: &BStr, - mut access_executable: impl FnMut(&BStr) -> nix::Result<()>, - callback: impl FnOnce(&BStr) -> nix::Result, -) -> nix::Result { - use nix::Error; - // 1. If file is empty, return ENOENT - if file.is_empty() { - return Err(Error::ENOENT); - } - // 2. If file contains '/', call callback directly - if file.contains(&b'/') { - return callback(file); - } - // 3. If file is too long, return ENAMETOOLONG - if file.len() > NAME_MAX { - return Err(Error::ENAMETOOLONG); - } - // 4. Search PATH - let mut seen_eacces = false; - let mut last_err = Error::ENOENT; - let mut callback = Some(callback); - for p in path.split(|ch| *ch == b':') { - let p = p.as_bstr(); - let result_to_return = concat( - // join with '/' if path is not empty - &[p, (if p.is_empty() { "" } else { "/" }).into(), file], - |path| match access_executable(path) { - Ok(()) => Some((callback.take().unwrap())(path)), - Err(err @ (Error::EACCES | Error::ENOENT | Error::ENOTDIR)) => { - if err == Error::EACCES { - seen_eacces = true; - } - last_err = err; - None - } - Err(other_err) => Some(Err(other_err)), - }, - ); - if let Some(result) = result_to_return { - return result; - } - } - Err(if seen_eacces { Error::EACCES } else { last_err }) -} - -#[cfg(test)] -mod tests { - use std::cell::RefCell; - - use bstr::{B, BStr}; - - use super::*; - - #[test] - fn test_concat() { - let s = concat(&["a".into(), "bc".into(), "".into(), "e".into()], BStr::to_owned); - assert_eq!(s, "abce"); - } - - fn mock_access<'a>( - allowed: &'a [&'a str], - fail_eacces: &'a [&'a str], - fail_enotdir: &'a [&'a str], - ) -> impl FnMut(&BStr) -> nix::Result<()> + 'a { - move |path| { - let s = std::str::from_utf8(path).unwrap(); - if allowed.contains(&s) { - Ok(()) - } else if fail_eacces.contains(&s) { - Err(nix::Error::EACCES) - } else if fail_enotdir.contains(&s) { - Err(nix::Error::ENOTDIR) - } else { - Err(nix::Error::ENOENT) - } - } - } - - #[test] - fn test_which_found() { - let called = RefCell::new(None); - let file: &BStr = B("foo").as_bstr(); - let path: &BStr = B("/bin:/usr/bin").as_bstr(); - let access = mock_access(&["/bin/foo"], &[], &[]); - let res = which(file, path, access, |found| { - *called.borrow_mut() = Some(found.to_owned()); - Ok(()) - }); - assert!(res.is_ok()); - assert_eq!(called.borrow().as_ref().unwrap(), b"/bin/foo".as_bstr()); - } - - #[test] - fn test_which_not_found() { - let file: &BStr = B("foo").as_bstr(); - let path: &BStr = B("/bin:/usr/bin").as_bstr(); - let access = mock_access(&[], &[], &[]); - let res = which(file, path, access, |_| Ok(())); - assert_eq!(res.unwrap_err(), nix::Error::ENOENT); - } - - #[test] - fn test_which_eacces() { - let file: &BStr = B("foo").as_bstr(); - let path: &BStr = B("/bin:/usr/bin").as_bstr(); - let access = mock_access(&[], &["/bin/foo", "/usr/bin/foo"], &[]); - let res = which(file, path, access, |_| Ok(())); - assert_eq!(res.unwrap_err(), nix::Error::EACCES); - } - - #[test] - fn test_which_enotdir() { - let file: &BStr = B("foo").as_bstr(); - let path: &BStr = B("/usr/bin:/bin").as_bstr(); - let access = mock_access(&[], &[], &["/bin/foo"]); - let res = which(file, path, access, |_| Ok(())); - assert_eq!(res.unwrap_err(), nix::Error::ENOTDIR); - } - - #[test] - fn test_which_slash_in_file() { - let called = RefCell::new(None); - let file: &BStr = B("/usr/bin/foo").as_bstr(); - let path: &BStr = B("").as_bstr(); - let access = mock_access(&["/usr/bin/foo"], &[], &[]); - let res = which(file, path, access, |found| { - *called.borrow_mut() = Some(found.to_owned()); - Ok(()) - }); - assert!(res.is_ok()); - assert_eq!(called.borrow().as_ref().unwrap(), b"/usr/bin/foo".as_bstr()); - } - - #[test] - fn test_which_empty_file() { - let file: &BStr = B("").as_bstr(); - let path: &BStr = B("/bin:/usr/bin").as_bstr(); - let access = mock_access(&[], &[], &[]); - let res = which(file, path, access, |_| Ok(())); - assert_eq!(res.unwrap_err(), nix::Error::ENOENT); - } - - #[test] - fn test_which_file_too_long() { - let long_name = vec![b'a'; NAME_MAX + 1]; - let file: &BStr = B(&long_name).as_bstr(); - let path: &BStr = B("/bin:/usr/bin").as_bstr(); - let access = mock_access(&[], &[], &[]); - let res = which(file, path, access, |_| Ok(())); - assert_eq!(res.unwrap_err(), nix::Error::ENAMETOOLONG); - } - - #[test] - fn test_which_empty_path_entry() { - let called = RefCell::new(None); - let file: &BStr = B("foo").as_bstr(); - let path: &BStr = B(":/bin").as_bstr(); - let access = mock_access(&["foo"], &[], &[]); - let res = which(file, path, access, |found| { - *called.borrow_mut() = Some(found.to_owned()); - Ok(()) - }); - assert!(res.is_ok()); - assert_eq!(called.borrow().as_ref().unwrap(), b"foo".as_bstr()); - } -} diff --git a/crates/fspy_shared_unix/src/lib.rs b/crates/fspy_shared_unix/src/lib.rs deleted file mode 100644 index 5437a3efc..000000000 --- a/crates/fspy_shared_unix/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![cfg(unix)] - -pub mod exec; -pub(crate) mod open_exec; -pub mod payload; -pub mod spawn; - -#[cfg(target_os = "linux")] -mod elf; - -#[cfg(target_os = "linux")] // exposed for verifying static executables in fspy tests -pub use elf::is_dynamically_linked_to_libc; diff --git a/crates/fspy_shared_unix/src/open_exec.rs b/crates/fspy_shared_unix/src/open_exec.rs deleted file mode 100644 index 24c5e6770..000000000 --- a/crates/fspy_shared_unix/src/open_exec.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::{os::fd::OwnedFd, path::Path}; - -use nix::{ - fcntl::{OFlag, open}, - sys::stat::Mode, - unistd::{AccessFlags, access}, -}; - -pub fn open_executable(path: impl AsRef) -> nix::Result { - let path = path.as_ref(); - access(path, AccessFlags::X_OK)?; - open(path, OFlag::O_RDONLY | OFlag::O_CLOEXEC, Mode::empty()) -} diff --git a/crates/fspy_shared_unix/src/payload.rs b/crates/fspy_shared_unix/src/payload.rs deleted file mode 100644 index 5267bd42e..000000000 --- a/crates/fspy_shared_unix/src/payload.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::os::unix::ffi::OsStringExt; - -use base64::{Engine as _, prelude::BASE64_STANDARD_NO_PAD}; -use bstr::BString; -#[cfg(not(target_env = "musl"))] -use fspy_shared::ipc::NativeStr; -#[cfg(not(target_env = "musl"))] -use fspy_shared::ipc::channel::ChannelConf; -use wincode::{SchemaRead, SchemaWrite}; - -#[derive(Debug, SchemaWrite, SchemaRead)] -pub struct Payload { - #[cfg(not(target_env = "musl"))] - pub ipc_channel_conf: ChannelConf, - - #[cfg(not(target_env = "musl"))] - pub preload_path: Box, - - #[cfg(target_os = "macos")] - pub artifacts: Artifacts, - - #[cfg(target_os = "linux")] - #[cfg_attr( - not(target_env = "musl"), - expect(clippy::struct_field_names, reason = "descriptive field name for clarity") - )] - pub seccomp_payload: fspy_seccomp_unotify::payload::SeccompPayload, -} - -#[cfg(target_os = "macos")] -#[derive(Debug, SchemaWrite, SchemaRead, Clone)] -pub struct Artifacts { - pub bash_path: Box, - pub coreutils_path: Box, -} - -pub(crate) const PAYLOAD_ENV_NAME: &str = "FSPY_PAYLOAD"; - -pub struct EncodedPayload { - pub payload: Payload, - pub encoded_string: BString, -} - -/// Encodes the fspy payload into a base64 string for transmission via environment variable -/// -/// # Panics -/// -/// Panics if serialization fails, which should never happen for valid `Payload` structs. -#[must_use] -pub fn encode_payload(payload: Payload) -> EncodedPayload { - let bytes = wincode::serialize(&payload).unwrap(); - let encoded_string = BASE64_STANDARD_NO_PAD.encode(&bytes); - EncodedPayload { payload, encoded_string: encoded_string.into() } -} - -/// Decodes the fspy payload from the environment variable -/// -/// # Errors -/// -/// Returns an error if: -/// - The environment variable is not found -/// - The base64 decoding fails -/// - The deserialization fails -pub fn decode_payload_from_env() -> anyhow::Result { - let Some(encoded_string) = std::env::var_os(PAYLOAD_ENV_NAME) else { - anyhow::bail!("Environment variable '{PAYLOAD_ENV_NAME}' not found"); - }; - decode_payload(encoded_string.into_vec().into()) -} - -fn decode_payload(encoded_string: BString) -> anyhow::Result { - let bytes = BASE64_STANDARD_NO_PAD.decode(&encoded_string)?; - let payload: Payload = wincode::deserialize_exact(&bytes)?; - Ok(EncodedPayload { payload, encoded_string }) -} diff --git a/crates/fspy_shared_unix/src/spawn/linux/mod.rs b/crates/fspy_shared_unix/src/spawn/linux/mod.rs deleted file mode 100644 index d3197da00..000000000 --- a/crates/fspy_shared_unix/src/spawn/linux/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -#[cfg(not(target_env = "musl"))] -use std::{ffi::OsStr, os::unix::ffi::OsStrExt as _, path::Path}; - -use fspy_seccomp_unotify::{payload::SeccompPayload, target::install_target}; -#[cfg(not(target_env = "musl"))] -use memmap2::Mmap; - -#[cfg(not(target_env = "musl"))] -use crate::{ - elf, - exec::{append_path_env, ensure_env}, - open_exec::open_executable, -}; -use crate::{ - exec::Exec, - payload::{EncodedPayload, PAYLOAD_ENV_NAME}, -}; - -const LD_PRELOAD: &str = "LD_PRELOAD"; - -pub struct PreExec(SeccompPayload); -impl PreExec { - /// Installs the seccomp unotify filter for the current process. - /// - /// # Errors - /// - /// Returns an error if the seccomp filter installation fails. - pub fn run(&self) -> nix::Result<()> { - install_target(&self.0) - } -} - -pub fn handle_exec( - command: &mut Exec, - encoded_payload: &EncodedPayload, -) -> nix::Result> { - // On musl targets, LD_PRELOAD is not available (cdylib not supported). - // Always use seccomp-based tracking instead. - #[cfg(not(target_env = "musl"))] - { - let executable_fd = open_executable(Path::new(OsStr::from_bytes(&command.program)))?; - // SAFETY: The file descriptor is valid and we only read from the mapping. - let executable_mmap = unsafe { Mmap::map(&executable_fd) }.map_err(|io_error| { - nix::Error::try_from(io_error).unwrap_or(nix::Error::UnknownErrno) - })?; - if elf::is_dynamically_linked_to_libc(executable_mmap)? { - // Append (don't overwrite) so a user-provided LD_PRELOAD keeps - // working. fspy's shim goes last so user preloads that - // short-circuit a libc call stay invisible to fspy — what the - // OS actually executed is what we want to record. - append_path_env( - &mut command.envs, - LD_PRELOAD, - encoded_payload.payload.preload_path.as_os_str().as_bytes(), - ); - ensure_env(&mut command.envs, PAYLOAD_ENV_NAME, &encoded_payload.encoded_string)?; - return Ok(None); - } - } - - command.envs.retain(|(name, _)| name != LD_PRELOAD && name != PAYLOAD_ENV_NAME); - Ok(Some(PreExec(encoded_payload.payload.seccomp_payload.clone()))) -} diff --git a/crates/fspy_shared_unix/src/spawn/macos.rs b/crates/fspy_shared_unix/src/spawn/macos.rs deleted file mode 100644 index 88bd7d0a4..000000000 --- a/crates/fspy_shared_unix/src/spawn/macos.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::{ - convert::Infallible, - ffi::OsStr, - os::unix::ffi::{OsStrExt, OsStringExt}, - path::{Path, absolute}, -}; - -use phf::{Set, phf_set}; - -use crate::{ - exec::{Exec, append_path_env, ensure_env}, - payload::{EncodedPayload, PAYLOAD_ENV_NAME}, -}; - -pub struct PreExec(Infallible); -impl PreExec { - /// Runs pre-exec operations - /// - /// # Errors - /// - /// This function never returns an error as the type is `Infallible` - pub const fn run(&self) -> nix::Result<()> { - match self.0 {} - } -} - -pub fn handle_exec( - command: &mut Exec, - encoded_payload: &EncodedPayload, -) -> nix::Result> { - const DYLD_INSERT_LIBRARIES: &[u8] = b"DYLD_INSERT_LIBRARIES"; - - if command.program.first() != Some(&b'/') { - let program = - absolute(OsStr::from_bytes(&command.program)).expect("Failed to get absolute path"); - command.program = program.into_os_string().into_vec().into(); - } - - let program_path = Path::new(OsStr::from_bytes(&command.program)); - - let injectable = if let (Some(parent), Some(file_name)) = - (program_path.parent(), program_path.file_name()) - { - if matches!(parent.as_os_str().as_bytes(), b"/bin" | b"/usr/bin") { - let artifacts = &encoded_payload.payload.artifacts; - if matches!(file_name.as_bytes(), b"sh" | b"bash") { - command.program = artifacts.bash_path.as_os_str().as_bytes().into(); - true - } else if COREUTILS_FUNCTIONS.contains(file_name.as_bytes()) { - command.program = artifacts.coreutils_path.as_os_str().as_bytes().into(); - true - } else { - false - } - } else { - true - } - } else { - true - }; - - if injectable { - // Append (don't overwrite) so a user-provided DYLD_INSERT_LIBRARIES - // keeps working. fspy's shim goes last so user preloads that - // short-circuit a libc call stay invisible to fspy — what the OS - // actually executed is what we want to record. - append_path_env( - &mut command.envs, - DYLD_INSERT_LIBRARIES, - encoded_payload.payload.preload_path.as_os_str().as_bytes(), - ); - ensure_env(&mut command.envs, PAYLOAD_ENV_NAME, &encoded_payload.encoded_string)?; - } else { - command.envs.retain(|(name, _)| { - name != DYLD_INSERT_LIBRARIES && name != PAYLOAD_ENV_NAME.as_bytes() - }); - } - Ok(None) -} - -pub static COREUTILS_FUNCTIONS: Set<&'static [u8]> = phf_set! { - b"[", b"arch", b"b2sum", b"base32", b"base64", b"basename", b"basenc", b"cat", - b"chgrp", b"chmod", b"chown", b"chroot", b"cksum", b"comm", b"cp", b"csplit", - b"cut", b"date", b"dd", b"df", b"dir", b"dircolors", b"dirname", b"du", b"echo", - b"env", b"expand", b"expr", b"factor", b"false", b"fmt", b"fold", b"groups", - b"hashsum", b"head", b"hostid", b"hostname", b"id", b"install", b"join", b"kill", - b"link", b"ln", b"logname", b"ls", b"md5sum", b"mkdir", b"mkfifo", b"mknod", - b"mktemp", b"more", b"mv", b"nice", b"nl", b"nohup", b"nproc", b"numfmt", b"od", - b"paste", b"pathchk", b"pinky", b"pr", b"printenv", b"printf", b"ptx", b"pwd", - b"readlink", b"realpath", b"rm", b"rmdir", b"seq", b"sha1sum", b"sha224sum", - b"sha256sum", b"sha384sum", b"sha512sum", b"shred", b"shuf", b"sleep", b"sort", - b"split", b"stat", b"stdbuf", b"stty", b"sum", b"sync", b"tac", b"tail", b"tee", - b"test", b"timeout", b"touch", b"tr", b"true", b"truncate", b"tsort", b"tty", - b"uname", b"unexpand", b"uniq", b"unlink", b"uptime", b"users", b"vdir", b"wc", - b"who", b"whoami", b"yes" -}; diff --git a/crates/fspy_shared_unix/src/spawn/mod.rs b/crates/fspy_shared_unix/src/spawn/mod.rs deleted file mode 100644 index e48125772..000000000 --- a/crates/fspy_shared_unix/src/spawn/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -#[cfg(target_os = "linux")] -#[path = "./linux/mod.rs"] -mod os_specific; - -#[cfg(target_os = "macos")] -#[path = "./macos.rs"] -mod os_specific; - -use std::{ffi::OsStr, os::unix::ffi::OsStrExt, path::Path}; - -use fspy_shared::ipc::AccessMode; -#[doc(hidden)] -#[cfg(target_os = "macos")] -pub use os_specific::COREUTILS_FUNCTIONS as COREUTILS_FUNCTIONS_FOR_TEST; -pub use os_specific::PreExec; - -use crate::{ - exec::{Exec, ExecResolveConfig}, - payload::EncodedPayload, -}; - -/// Handles exec command resolution and injection -/// -/// Resolves the program path and prepares the command for execution with -/// appropriate environment variables and hooks. -/// -/// # Errors -/// -/// Returns an error if: -/// - Program resolution fails (see [`Exec::resolve`] error variants, such as `ENOENT` (file not found) or `EACCES` (permission denied)) -/// - Environment variable operations fail (e.g., `ensure_env` may return `EINVAL` if an existing value conflicts) -/// - Platform-specific errors from `os_specific::handle_exec` -/// -/// # Panics -/// -/// Panics if the current working directory cannot be determined when converting a relative path to absolute. -pub fn handle_exec( - command: &mut Exec, - config: ExecResolveConfig, - encoded_payload: &EncodedPayload, - mut on_path_access: impl FnMut(AccessMode, &Path), -) -> nix::Result> { - let mut on_path_access = |mode: AccessMode, path: &Path| { - if path.is_absolute() { - on_path_access(mode, path); - } else { - let path = std::path::absolute(path).expect("Failed to get cwd"); - on_path_access(mode, &path); - } - }; - - command.resolve(&mut on_path_access, config)?; - on_path_access(AccessMode::READ, Path::new(OsStr::from_bytes(&command.program))); - - os_specific::handle_exec(command, encoded_payload) -} diff --git a/crates/fspy_shm/.clippy.toml b/crates/fspy_shm/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/fspy_shm/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/fspy_shm/Cargo.toml b/crates/fspy_shm/Cargo.toml deleted file mode 100644 index 82f571b8d..000000000 --- a/crates/fspy_shm/Cargo.toml +++ /dev/null @@ -1,49 +0,0 @@ -[package] -name = "fspy_shm" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[target.'cfg(target_os = "linux")'.dependencies] -memmap2 = { workspace = true } -memfd = { workspace = true } -passfd = { workspace = true, features = ["async"] } -nix = { workspace = true, features = ["fs", "socket", "user"] } -tokio = { workspace = true, features = ["macros", "net", "rt", "time"] } -tokio-util = { workspace = true } -tracing = { workspace = true } -uuid = { workspace = true, features = ["v4"] } - -[target.'cfg(target_os = "macos")'.dependencies] -base64 = { workspace = true } -memmap2 = { workspace = true } -nix = { workspace = true, features = ["fs", "mman"] } -uuid = { workspace = true, features = ["v4"] } - -[target.'cfg(target_os = "windows")'.dependencies] -uuid = { workspace = true, features = ["v4"] } -windows-sys = { workspace = true, features = [ - "Win32_Foundation", - "Win32_Security", - "Win32_Storage_FileSystem", - "Win32_System_IO", - "Win32_System_Ioctl", - "Win32_System_Memory", -] } - -[dev-dependencies] -ctor = { workspace = true } -subprocess_test = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } - -[lints] -workspace = true - -[lib] -doctest = false - -[package.metadata.cargo-shear] -ignored = ["ctor"] diff --git a/crates/fspy_shm/README.md b/crates/fspy_shm/README.md deleted file mode 100644 index 773be4357..000000000 --- a/crates/fspy_shm/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# `fspy_shm` - -`fspy_shm` is the private shared-memory layer used by fspy IPC channels. It gives the channel one API for creating a mapping, passing its identifier to another process, and opening additional views of the same bytes. - -`fspy_shm` exposes only the operations used by fspy. Callers must treat an identifier as a string and must not depend on a platform's naming scheme. - -## API - -The public API is defined in [`src/lib.rs`](src/lib.rs). - -| API | Contract | -| ----------------- | ---------------------------------------------------------------------------------- | -| `create(size)` | Creates a non-empty, zero-initialized mapping and returns its unique owner. | -| `open(id)` | Opens another view of the mapping identified by `id`. | -| `Shm::id()` | Returns the identifier to send to another process. | -| `Shm::len()` | Returns the mapped size. | -| `Shm::as_ptr()` | Returns a mutable raw pointer to the first byte. | -| `Shm::as_slice()` | Returns a shared slice. The caller must prevent mutation for the slice's lifetime. | - -`Shm` does not synchronize memory access. The fspy channel combines it with atomic frame headers and a lock file. Senders hold a shared file lock while writing. The receiver takes the exclusive lock before reading, which waits for existing senders and rejects new ones. - -Every byte in a mapping returned by `create` is initially zero. `open` exposes the mapping's current contents and does not reinitialize them. - -## Ownership semantics - -`create` returns the only owner. `open` returns non-owning views. - -- While the owner is alive, a process that knows the identifier can open the mapping. -- An opened view remains usable after the owner is dropped. Its operating system mapping keeps the underlying bytes alive. -- After the owner is dropped, new opens behave differently by platform. POSIX removes the name. Windows can continue accepting opens by section name until the final handle or view is closed. - -The channel hides that difference with its lock file. [`ChannelConf::sender`](../fspy_shared/src/ipc/channel/mod.rs) opens and locks the receiver's exact lock-file path before it calls `fspy_shm::open`. The receiver removes that path before dropping the owner, so a sender that starts later fails before opening shared memory. - -## Platform designs - -Each platform keeps its implementation rationale beside its source: - -- [Linux: `memfd` with a descriptor broker](src/linux/README.md) -- [macOS: named POSIX shared memory](src/macos/README.md) -- [Windows: sparse file-backed named mapping](src/windows/README.md) - -All implementations provide the API above. Their identifiers and operating system objects differ. Each platform README explains the chosen API and why the previous `shared_memory` backend did not meet its requirements. diff --git a/crates/fspy_shm/src/lib.rs b/crates/fspy_shm/src/lib.rs deleted file mode 100644 index 4798b8b83..000000000 --- a/crates/fspy_shm/src/lib.rs +++ /dev/null @@ -1,113 +0,0 @@ -#![doc = include_str!("../README.md")] - -#[cfg(target_os = "linux")] -#[path = "linux/mod.rs"] -mod os_impl; -#[cfg(target_os = "macos")] -#[path = "macos/mod.rs"] -mod os_impl; -#[cfg(target_os = "windows")] -#[path = "windows/mod.rs"] -mod os_impl; - -pub use os_impl::{Shm, create, open}; - -#[cfg(test)] -mod tests { - use std::{mem::align_of, process::Command}; - - use subprocess_test::command_for_fn; - - use super::{Shm, create, open}; - - // Page-aligned on all supported targets. - const SIZE: usize = 64 * 1024; - // Use one byte more than 64 KiB to test multiple pages and a partial last page. - const ZERO_INITIALIZED_SIZE: usize = SIZE + 1; - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn new_mapping_is_zero_initialized_in_all_views() { - let owner = create(ZERO_INITIALIZED_SIZE).unwrap(); - let opened = open(owner.id()).unwrap(); - - assert_zero_initialized(&owner); - assert_zero_initialized(&opened); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn create_and_open_are_shared() { - let owner = create(SIZE).unwrap(); - assert_eq!(owner.len(), SIZE); - assert_eq!(owner.as_ptr() as usize % align_of::(), 0); - - let opened = open(owner.id()).unwrap(); - assert_eq!(opened.id(), owner.id()); - assert_eq!(opened.len(), SIZE); - - write_byte(&owner, 0, 17); - assert_eq!(read_byte(&opened, 0), 17); - write_byte(&opened, SIZE - 1, 29); - assert_eq!(read_byte(&owner, SIZE - 1), 29); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn mapping_is_visible_across_processes() { - let owner = create(SIZE).unwrap(); - write_byte(&owner, 0, 17); - - let command = command_for_fn!(owner.id().to_owned(), |id: String| { - let opened = open(&id).unwrap(); - assert_eq!(read_byte(&opened, 0), 17); - write_byte(&opened, SIZE - 1, 29); - }); - let success = - tokio::task::spawn_blocking(move || Command::from(command).status().unwrap().success()) - .await - .unwrap(); - assert!(success); - assert_eq!(read_byte(&owner, SIZE - 1), 29); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn owner_drop_prevents_new_opens() { - let owner = create(SIZE).unwrap(); - let id = owner.id().to_owned(); - drop(owner); - - assert!(open(&id).is_err()); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn opened_mapping_survives_owner_drop() { - let owner = create(SIZE).unwrap(); - let id = owner.id().to_owned(); - let opened = open(&id).unwrap(); - write_byte(&owner, 0, 17); - drop(owner); - - // Windows keeps the named object alive while an opened view exists. - #[cfg(not(target_os = "windows"))] - assert!(open(&id).is_err()); - assert_eq!(read_byte(&opened, 0), 17); - write_byte(&opened, SIZE - 1, 29); - assert_eq!(read_byte(&opened, SIZE - 1), 29); - } - - fn read_byte(shm: &Shm, index: usize) -> u8 { - assert!(index < shm.len()); - // SAFETY: The index is in bounds and tests synchronize all accesses. - unsafe { shm.as_ptr().add(index).read() } - } - - fn assert_zero_initialized(shm: &Shm) { - assert!(shm.len() >= ZERO_INITIALIZED_SIZE); - // SAFETY: No writes occur while this slice is borrowed. - assert!(unsafe { shm.as_slice() }.iter().all(|byte| *byte == 0)); - } - - fn write_byte(shm: &Shm, index: usize, value: u8) { - assert!(index < shm.len()); - // SAFETY: The index is in bounds and tests synchronize all accesses. - unsafe { shm.as_ptr().add(index).write(value) }; - } -} diff --git a/crates/fspy_shm/src/linux/README.md b/crates/fspy_shm/src/linux/README.md deleted file mode 100644 index 5dee2565c..000000000 --- a/crates/fspy_shm/src/linux/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Linux backend - -The Linux backend stores data in a sealed `memfd`. A process opens the mapping by connecting to an abstract Unix-domain socket and receiving the descriptor from a broker. It then accesses the mapped memory directly. - -The backend must avoid `/dev/shm` quotas, expose a large mapping without allocating every page up front, support synchronous opens from preload code, and stop new opens when the owner is dropped without invalidating existing views. - -## Options considered - -| Option | Decision | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | -| POSIX shared memory | Rejected because Linux stores it in `/dev/shm`, so it shares that mount's size limit. | -| System V shared memory | Rejected because IPC namespace limits affect availability and the owner must explicitly remove the segment. | -| Sparse temporary file | Rejected because dirty pages may reach disk, and sharing the path and deleting the file require additional handling. | -| `memfd` with descriptor broker | Selected. It avoids `/dev/shm` and System V limits. The kernel keeps it alive while a descriptor or mapping refers to it. | - -The broker accepts and serves clients with Tokio. Opening is synchronous because it can run before `main`, so creating an owner must occur inside a Tokio runtime. - -## Why not `shared_memory` - -`shared_memory` uses POSIX `shm_open` on Linux and cannot construct a mapping from a `memfd`, so it retains the `/dev/shm` dependency. - -## Lifetime semantics - -Dropping the owner stops the broker. Existing views remain valid; later opens fail. diff --git a/crates/fspy_shm/src/linux/broker.rs b/crates/fspy_shm/src/linux/broker.rs deleted file mode 100644 index 484f3e236..000000000 --- a/crates/fspy_shm/src/linux/broker.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::{ - ffi::OsStr, - future::Future, - io, - os::{ - fd::{AsRawFd, FromRawFd, OwnedFd}, - unix::ffi::OsStrExt, - }, - sync::Arc, -}; - -use nix::{ - sys::socket::{ - AddressFamily, SockFlag, SockType, UnixAddr, connect, getsockopt, socket, - sockopt::PeerCredentials, - }, - unistd::{Uid, geteuid}, -}; -use passfd::{FdPassingExt as SyncFdPassingExt, tokio::FdPassingExt as AsyncFdPassingExt}; -use tokio::{net::UnixListener, task::JoinSet}; -use tokio_util::sync::{CancellationToken, DropGuard}; -use tracing::{debug, warn}; -use uuid::Uuid; - -/// Creates the broker listener for `memfd` and returns the mapping id, the -/// broker task, and the guard whose drop stops the task. -/// -/// The listener is bound eagerly, so the id is usable as soon as the task is -/// spawned; connections arriving earlier wait in the listen backlog. -pub(super) fn new( - memfd: OwnedFd, -) -> io::Result<(String, impl Future + Send + 'static, DropGuard)> { - let id = Uuid::new_v4().simple().to_string(); - // Tokio treats a NUL-prefixed Unix socket path as a Linux abstract address. - // This keeps the broker out of the filesystem and makes the id the complete address. - let mut address = Vec::with_capacity(id.len() + 1); - address.push(0); - address.extend_from_slice(id.as_bytes()); - let listener = UnixListener::bind(OsStr::from_bytes(&address))?; - - let stop = CancellationToken::new(); - let service = run_broker(listener, memfd, geteuid(), stop.clone()); - Ok((id, service, stop.drop_guard())) -} - -async fn run_broker( - listener: UnixListener, - memfd: OwnedFd, - owner_uid: Uid, - stop: CancellationToken, -) { - let memfd = Arc::new(memfd); - let mut sends = JoinSet::new(); - loop { - tokio::select! { - biased; - () = stop.cancelled() => return, - _result = sends.join_next(), if !sends.is_empty() => {} - client = listener.accept() => match client { - Ok((client, _address)) => { - // Abstract sockets have no filesystem permissions, so authenticate the - // connecting process with the kernel-provided SO_PEERCRED credentials: - // https://man7.org/linux/man-pages/man7/unix.7.html - // D-Bus prefers the same mechanism because it requires no peer cooperation: - // https://gitlab.freedesktop.org/dbus/dbus/-/blob/958bf9db2100553bcd2fe2a854e1ebb42e886054/dbus/dbus-sysdeps-unix.c#L2296-2303 - let credentials = match getsockopt(&client, PeerCredentials) { - Ok(credentials) => credentials, - Err(error) => { - debug!("shared-memory broker failed to read peer credentials: {error}"); - continue; - } - }; - if credentials.uid() != owner_uid.as_raw() { - debug!("shared-memory broker rejected a client owned by another user"); - continue; - } - let memfd = Arc::clone(&memfd); - sends.spawn(async move { - if let Err(error) = - AsyncFdPassingExt::send_fd(&client, memfd.as_raw_fd()).await - { - debug!("shared-memory broker failed to send a descriptor: {error}"); - } - }); - } - Err(error) => { - warn!("shared-memory broker failed to accept a connection: {error}"); - return; - } - }, - } - } -} - -pub(super) fn request_memfd(id: &str) -> io::Result { - // Prevent the broker connection from leaking into later execs. - let socket = socket(AddressFamily::Unix, SockType::Stream, SockFlag::SOCK_CLOEXEC, None)?; - let address = UnixAddr::new_abstract(id.as_bytes())?; - connect(socket.as_raw_fd(), &address)?; - // `SCM_RIGHTS` does not preserve descriptor flags; `passfd` sets - // `FD_CLOEXEC` on the received descriptor before returning it. - let descriptor = SyncFdPassingExt::recv_fd(&socket.as_raw_fd())?; - // SAFETY: passfd returns a newly received descriptor owned by the caller. - Ok(unsafe { OwnedFd::from_raw_fd(descriptor) }) -} - -#[cfg(test)] -mod tests { - use std::{io, process::Command}; - - use memfd::MemfdOptions; - use nix::fcntl::{FcntlArg, FdFlag, SealFlag, fcntl}; - use subprocess_test::command_for_fn; - - use super::*; - - #[test] - fn broker_construction_is_independent_of_long_tmpdir() { - let command = command_for_fn!((), |(): ()| { - let runtime = tokio::runtime::Builder::new_multi_thread() - .worker_threads(1) - .enable_io() - .enable_time() - .build() - .unwrap(); - let _guard = runtime.enter(); - let _owner = crate::create(4096).unwrap(); - }); - let status = std::thread::spawn(move || { - Command::from(command) - .env("TMPDIR", format!("/tmp/{}", "x".repeat(4096))) - .status() - .unwrap() - }) - .join() - .unwrap(); - assert!(status.success()); - } - - #[test] - fn create_without_runtime_fails() { - let error = match crate::create(4096) { - Ok(_owner) => panic!("create without a runtime should fail"), - Err(error) => error, - }; - assert!(error.to_string().contains("tokio runtime")); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn broker_stops_when_guard_drops() { - let memfd: OwnedFd = MemfdOptions::new() - .close_on_exec(true) - .create("shared-memory-test") - .unwrap() - .into_file() - .into(); - let (_id, service, guard) = new(memfd).unwrap(); - let broker = tokio::spawn(service); - tokio::task::yield_now().await; - assert!(!broker.is_finished()); - - drop(guard); - tokio::time::timeout(std::time::Duration::from_secs(10), broker) - .await - .expect("broker should stop when the guard drops") - .unwrap(); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn received_descriptor_is_close_on_exec() { - let owner = crate::create(4096).unwrap(); - let id = owner.id().to_owned(); - let descriptor = request_memfd_blocking(id).await.unwrap(); - assert!( - FdFlag::from_bits_retain(fcntl(&descriptor, FcntlArg::F_GETFD).unwrap()) - .contains(FdFlag::FD_CLOEXEC) - ); - assert_eq!( - SealFlag::from_bits_retain(fcntl(&descriptor, FcntlArg::F_GET_SEALS).unwrap()), - SealFlag::F_SEAL_GROW | SealFlag::F_SEAL_SHRINK | SealFlag::F_SEAL_SEAL - ); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn broker_serves_concurrent_opens() { - let owner = crate::create(4096).unwrap(); - let id = owner.id().to_owned(); - let clients = (0..12) - .map(|_| { - let id = id.clone(); - tokio::task::spawn_blocking(move || crate::open(&id)) - }) - .collect::>(); - - for client in clients { - assert_eq!(client.await.unwrap().unwrap().len(), 4096); - } - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn brokers_are_isolated_by_abstract_name() { - let first = crate::create(4096).unwrap(); - let second = crate::create(4096).unwrap(); - let first_id = first.id().to_owned(); - let second_id = second.id().to_owned(); - - let first_opened = open_blocking(first_id).await.unwrap(); - let second_opened = open_blocking(second_id).await.unwrap(); - // SAFETY: Both mappings are live and the accesses are in bounds and synchronized. - unsafe { - first.as_ptr().write(17); - second.as_ptr().write(29); - assert_eq!(first_opened.as_ptr().read(), 17); - assert_eq!(second_opened.as_ptr().read(), 29); - } - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn unavailable_ids_are_rejected() { - let owner = crate::create(4096).unwrap(); - let id = owner.id().to_owned(); - - assert!(open_blocking("not-a-broker-id".to_owned()).await.is_err()); - assert!(open_blocking("x".repeat(108)).await.is_err()); - assert!(open_blocking(id).await.is_ok()); - } - - async fn request_memfd_blocking(id: String) -> io::Result { - tokio::task::spawn_blocking(move || request_memfd(&id)).await.unwrap() - } - - async fn open_blocking(id: String) -> io::Result { - tokio::task::spawn_blocking(move || crate::open(&id)).await.unwrap() - } -} diff --git a/crates/fspy_shm/src/linux/mod.rs b/crates/fspy_shm/src/linux/mod.rs deleted file mode 100644 index f0abf020c..000000000 --- a/crates/fspy_shm/src/linux/mod.rs +++ /dev/null @@ -1,130 +0,0 @@ -#![doc = include_str!("README.md")] - -mod broker; - -use std::{io, os::fd::OwnedFd, slice}; - -use memfd::{FileSeal, MemfdOptions}; -use memmap2::{MmapOptions, MmapRaw}; -use nix::sys::stat::fstat; -use tokio_util::sync::DropGuard; - -/// An owned Linux shared-memory mapping. -pub struct Shm { - id: String, - mapping: MmapRaw, - /// Stops the owner's broker on drop. `None` for opened views. - _service: Option, -} - -/// Creates a zero-initialized sealed memfd mapping of `size` bytes and returns -/// its owner. -/// -/// The memfd is handed out to other processes by a broker task spawned onto -/// the ambient tokio runtime. The broker stops on its own when the owner is -/// dropped, after which new [`open`] calls fail while already-open views stay -/// usable (see the [ownership semantics](crate)). -/// -/// # Errors -/// -/// Returns an error if no tokio runtime is active or the memfd, mapping, or -/// broker listener cannot be created. -pub fn create(size: usize) -> io::Result { - let runtime = tokio::runtime::Handle::try_current() - .map_err(|_| io::Error::other("creating Linux shared memory requires a tokio runtime"))?; - let size_u64 = valid_size(size)?; - // Prevent the descriptor from leaking across exec while permitting the - // size and seal set to be locked after initialization. - let memfd = MemfdOptions::new() - .allow_sealing(true) - .close_on_exec(true) - .create("vite-task-shared-memory") - .map_err(memfd_error)?; - memfd.as_file().set_len(size_u64)?; - // Keep the initialized size fixed and prevent removal of these seals; - // writes through the shared mapping remain allowed. - memfd - .add_seals(&[FileSeal::SealGrow, FileSeal::SealShrink, FileSeal::SealSeal]) - .map_err(memfd_error)?; - let mapping = MmapOptions::new().len(size).map_raw(memfd.as_file())?; - let memfd: OwnedFd = memfd.into_file().into(); - let (id, service, guard) = broker::new(memfd)?; - runtime.spawn(service); - - Ok(Shm { id, mapping, _service: Some(guard) }) -} - -/// Opens a view of the memfd mapping identified by `id` through its broker. -/// -/// Guaranteed to succeed only while the mapping's owner is alive; the -/// returned view stays usable independently of the owner afterwards (see the -/// [ownership semantics](crate)). -/// -/// # Errors -/// -/// Returns an error if the identifier is invalid, the broker is gone, or the -/// received memfd has an invalid size. -pub fn open(id: &str) -> io::Result { - let memfd = broker::request_memfd(id)?; - let stat = fstat(&memfd)?; - let size = usize::try_from(stat.st_size) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid shared-memory size"))?; - if size == 0 { - return Err(io::Error::new(io::ErrorKind::InvalidData, "shared-memory size is zero")); - } - let mapping = MmapOptions::new().len(size).map_raw(&memfd)?; - Ok(Shm { id: id.to_owned(), mapping, _service: None }) -} - -fn valid_size(size: usize) -> io::Result { - if size == 0 { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "shared-memory size must be nonzero", - )); - } - u64::try_from(size) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "shared-memory size exceeds u64")) -} - -fn memfd_error(error: memfd::Error) -> io::Error { - match error { - memfd::Error::Create(error) - | memfd::Error::AddSeals(error) - | memfd::Error::GetSeals(error) => error, - } -} - -#[expect(clippy::len_without_is_empty, reason = "shared-memory mappings are always non-empty")] -impl Shm { - /// Returns this mapping's opaque broker identifier. - #[must_use] - pub fn id(&self) -> &str { - &self.id - } - - /// Returns the mapped length in bytes. - #[must_use] - pub fn len(&self) -> usize { - self.mapping.len() - } - - /// Returns a raw pointer to the first mapped byte. - #[must_use] - pub fn as_ptr(&self) -> *mut u8 { - self.mapping.as_mut_ptr() - } - - /// Returns the mapped bytes as a shared slice. - /// - /// # Safety - /// - /// The caller must ensure that no process or thread mutates the mapping for - /// the lifetime of the returned slice. - #[must_use] - pub unsafe fn as_slice(&self) -> &[u8] { - // SAFETY: The mapping is valid for its full length, and the caller - // guarantees that it is not mutated while the slice is borrowed. - unsafe { slice::from_raw_parts(self.mapping.as_ptr(), self.mapping.len()) } - } -} diff --git a/crates/fspy_shm/src/macos/README.md b/crates/fspy_shm/src/macos/README.md deleted file mode 100644 index 058db0ef8..000000000 --- a/crates/fspy_shm/src/macos/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# macOS backend - -The macOS backend uses named POSIX shared memory. Another process opens the same object by name. Pages are allocated as they are accessed, and dropping the owner removes the name. Fspy does not need a separate service to pass file descriptors between processes. - -## Options considered - -| Option | Decision | -| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| System V shared memory | Rejected because kernel IPC limits affect availability and the owner must explicitly remove the segment. | -| Sparse temporary file | Rejected because dirty pages may reach disk. Another process needs the path, and the owner must delete the file. | -| Mach memory entry with port transfer | Rejected because another process can receive the memory entry only through a Mach port. Fspy would need a separate service to transfer that port. | -| POSIX shared memory | Selected. Another process can open it by name, pages are allocated as accessed, and `shm_unlink` removes the name. | - -Unlike Linux, macOS does not route POSIX shared memory through a container's `/dev/shm` mount. - -## Why not `shared_memory` - -`shared_memory` uses the same POSIX shared-memory mechanism, but it also supports opening mappings through files and changing which process deletes them. Fspy needs neither feature. It only needs to create, open, map, and unlink shared memory, so the backend calls the POSIX APIs directly. - -## Lifetime semantics - -Dropping the owner unlinks the POSIX name. Existing views remain valid; later opens fail. diff --git a/crates/fspy_shm/src/macos/mod.rs b/crates/fspy_shm/src/macos/mod.rs deleted file mode 100644 index ea646e1f8..000000000 --- a/crates/fspy_shm/src/macos/mod.rs +++ /dev/null @@ -1,174 +0,0 @@ -#![doc = include_str!("README.md")] - -use std::{io, os::fd::AsFd, slice}; - -use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; -use memmap2::{MmapOptions, MmapRaw}; -use nix::{ - errno::Errno, - fcntl::{FcntlArg, FdFlag, OFlag, fcntl}, - sys::{ - mman::{shm_open, shm_unlink}, - stat::{Mode, fstat}, - }, - unistd::ftruncate, -}; -use uuid::Uuid; - -const NAME_PREFIX: &str = "/fspy_"; - -/// An owned macOS shared-memory mapping. -pub struct Shm { - id: String, - mapping: MmapRaw, - owner: bool, -} - -/// Creates a zero-initialized POSIX shared-memory mapping of `size` bytes and -/// returns its owner. -/// -/// # Errors -/// -/// Returns an error if the object cannot be created, sized, or mapped. -pub fn create(size: usize) -> io::Result { - if size == 0 { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "shared-memory size must be nonzero", - )); - } - let size_i64 = i64::try_from(size).map_err(|_| { - io::Error::new(io::ErrorKind::InvalidInput, "shared-memory size exceeds supported range") - })?; - - loop { - let id = new_id(); - let fd = match shm_open( - id.as_str(), - OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_RDWR, - Mode::S_IRUSR | Mode::S_IWUSR, - ) { - Ok(fd) => fd, - Err(Errno::EEXIST) => continue, - Err(error) => return Err(error.into()), - }; - - if let Err(error) = ensure_cloexec(&fd) { - let _ = shm_unlink(id.as_str()); - return Err(error.into()); - } - if let Err(error) = ftruncate(&fd, size_i64) { - let _ = shm_unlink(id.as_str()); - return Err(error.into()); - } - let mapping = match MmapOptions::new().len(size).map_raw(&fd) { - Ok(mapping) => mapping, - Err(error) => { - let _ = shm_unlink(id.as_str()); - return Err(error); - } - }; - - return Ok(Shm { id, mapping, owner: true }); - } -} - -/// Opens the POSIX shared-memory mapping identified by `id`. -/// -/// # Errors -/// -/// Returns an error if the mapping is unavailable. -pub fn open(id: &str) -> io::Result { - let fd = shm_open(id, OFlag::O_RDWR, Mode::empty())?; - ensure_cloexec(&fd)?; - // If another process shrinks the object before `mmap`, `mmap` returns an - // error. If it resizes the object after `mmap`, `open` does not access the - // mapped pages. A concurrent resize cannot make `open` access invalid memory. - let stat = fstat(&fd)?; - let size = usize::try_from(stat.st_size) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid shared-memory size"))?; - if size == 0 { - return Err(io::Error::new(io::ErrorKind::InvalidData, "shared-memory size is zero")); - } - let mapping = MmapOptions::new().len(size).map_raw(&fd)?; - - Ok(Shm { id: id.to_owned(), mapping, owner: false }) -} - -fn new_id() -> String { - format!("{NAME_PREFIX}{}", URL_SAFE_NO_PAD.encode(Uuid::new_v4().as_bytes())) -} - -// macOS rejects O_CLOEXEC for shm_open, so preserve the descriptor guarantee via fcntl. -fn ensure_cloexec(fd: &Fd) -> nix::Result<()> { - let flags = FdFlag::from_bits_retain(fcntl(fd, FcntlArg::F_GETFD)?); - if !flags.contains(FdFlag::FD_CLOEXEC) { - fcntl(fd, FcntlArg::F_SETFD(flags | FdFlag::FD_CLOEXEC))?; - } - Ok(()) -} - -impl Drop for Shm { - fn drop(&mut self) { - if self.owner { - let _ = shm_unlink(self.id.as_str()); - } - } -} - -#[expect(clippy::len_without_is_empty, reason = "shared-memory mappings are always non-empty")] -impl Shm { - /// Returns this mapping's opaque macOS identifier. - #[must_use] - pub fn id(&self) -> &str { - &self.id - } - - /// Returns the mapped length in bytes. - #[must_use] - pub fn len(&self) -> usize { - self.mapping.len() - } - - /// Returns a raw pointer to the first mapped byte. - #[must_use] - pub fn as_ptr(&self) -> *mut u8 { - self.mapping.as_mut_ptr() - } - - /// Returns the mapped bytes as a shared slice. - /// - /// # Safety - /// - /// The caller must ensure that no process or thread mutates the mapping for - /// the lifetime of the returned slice. - #[must_use] - pub unsafe fn as_slice(&self) -> &[u8] { - // SAFETY: The mapping is valid for its full length, and the caller - // guarantees that it is not mutated while the slice is borrowed. - unsafe { slice::from_raw_parts(self.mapping.as_ptr(), self.mapping.len()) } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(target_pointer_width = "64")] - #[test] - fn four_gib_mapping_supports_endpoint_access() { - const PRODUCTION_SIZE: usize = 4 * 1024 * 1024 * 1024; - - let owner = create(PRODUCTION_SIZE).unwrap(); - let opened = open(owner.id()).unwrap(); - - // SAFETY: Both endpoint indexes are within the exact mapped length and - // accesses are synchronized within this test. - unsafe { - owner.as_ptr().write(17); - owner.as_ptr().add(PRODUCTION_SIZE - 1).write(29); - assert_eq!(opened.as_ptr().read(), 17); - assert_eq!(opened.as_ptr().add(PRODUCTION_SIZE - 1).read(), 29); - } - } -} diff --git a/crates/fspy_shm/src/windows/README.md b/crates/fspy_shm/src/windows/README.md deleted file mode 100644 index fac5350ae..000000000 --- a/crates/fspy_shm/src/windows/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Windows backend - -The Windows backend maps a sparse temporary file and gives the mapping a Windows section name. Another process opens the same section using only that name. Creating the mapping does not reserve its full size in system commit or disk space. - -## Options considered - -| Option | Decision | -| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | -| Paging-file-backed section | Rejected because the section's full size is charged against system commit. | -| Reserved section with incremental `VirtualAlloc(MEM_COMMIT)` | Rejected because writers would need to coordinate when committing more pages. | -| Sparse temporary file with a named section | Selected. Disk blocks are allocated only for written ranges, and another process can open the mapping using the section name. | - -`FILE_ATTRIBUTE_TEMPORARY` tells Windows to keep file data in memory when possible, but Windows may still write dirty pages to disk under memory pressure. Creation fails if the temporary volume does not support sparse files. - -## Why not `shared_memory` - -`shared_memory` creates and extends a regular file before fspy can mark it sparse. It therefore cannot ensure that untouched ranges use no disk blocks. - -`shared_memory` also uses the file path to identify the mapping. Fspy uses the section name, so another process does not need the creator's temporary-file path. - -## Lifetime semantics - -Dropping the owner unmaps its view and closes the delete-on-close backing file. Existing views keep the section alive. The section name may remain openable during that period. `ChannelConf::sender` checks the receiver's lock file first to prevent new senders after the receiver has shut down. diff --git a/crates/fspy_shm/src/windows/mod.rs b/crates/fspy_shm/src/windows/mod.rs deleted file mode 100644 index ec0c8cad9..000000000 --- a/crates/fspy_shm/src/windows/mod.rs +++ /dev/null @@ -1,198 +0,0 @@ -#![doc = include_str!("README.md")] - -mod sys; - -use std::{ - env::temp_dir, - fs::{self, File, OpenOptions}, - io, - os::windows::fs::OpenOptionsExt, - slice, -}; - -use sys::MappedView; -use uuid::Uuid; - -const MAPPING_NAME_PREFIX: &str = r"Local\vite-task-fspy-"; -const BACKING_DIR: &str = "vite-task-fspy"; - -/// An owned Windows shared-memory mapping. -pub struct Shm { - id: String, - view: MappedView, - #[cfg_attr(not(test), expect(dead_code, reason = "retained for owner cleanup"))] - backing_file: Option, -} - -/// Creates a zero-initialized, sparse, temporary file-backed named mapping of -/// `size` bytes and returns its owner. -/// -/// # Errors -/// -/// Returns an error if the backing file or mapping cannot be created. -pub fn create(size: usize) -> io::Result { - if size == 0 { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "shared-memory size must be nonzero", - )); - } - let size_u64 = u64::try_from(size).map_err(|_| { - io::Error::new(io::ErrorKind::InvalidInput, "shared-memory size exceeds u64") - })?; - let id = Uuid::new_v4().simple().to_string(); - let backing_dir = temp_dir().join(BACKING_DIR); - fs::create_dir_all(&backing_dir)?; - let backing_file = OpenOptions::new() - .read(true) - .write(true) - .create_new(true) - .share_mode(sys::SHARE_ALL) - .attributes(sys::TEMPORARY) - .custom_flags(sys::DELETE_ON_CLOSE) - .open(backing_dir.join(format!("{id}.shm")))?; - sys::set_sparse(&backing_file)?; - backing_file.set_len(size_u64)?; - let mapping = sys::create_file_mapping(&backing_file, &mapping_name(&id))?; - let view = MappedView::new(mapping)?; - - Ok(Shm { id, view, backing_file: Some(backing_file) }) -} - -/// Opens the named mapping identified by `id`. -/// -/// # Errors -/// -/// Returns an error if the mapping is unavailable. -pub fn open(id: &str) -> io::Result { - let mapping = sys::open_file_mapping(&mapping_name(id))?; - let view = MappedView::new(mapping)?; - - Ok(Shm { id: id.to_owned(), view, backing_file: None }) -} - -fn mapping_name(id: &str) -> String { - format!("{MAPPING_NAME_PREFIX}{id}") -} - -#[expect(clippy::len_without_is_empty, reason = "shared-memory mappings are always non-empty")] -impl Shm { - /// Returns this mapping's opaque Windows identifier. - #[must_use] - pub fn id(&self) -> &str { - &self.id - } - - /// Returns the mapped length in bytes. - #[must_use] - pub const fn len(&self) -> usize { - self.view.len() - } - - /// Returns a raw pointer to the first mapped byte. - #[must_use] - pub const fn as_ptr(&self) -> *mut u8 { - self.view.as_ptr() - } - - /// Returns the mapped bytes as a shared slice. - /// - /// # Safety - /// - /// The caller must ensure that no process or thread mutates the mapping for - /// the lifetime of the returned slice. - #[must_use] - pub const unsafe fn as_slice(&self) -> &[u8] { - // SAFETY: The view is valid for its exact length, and the caller - // guarantees that it is not mutated while the slice is borrowed. - unsafe { slice::from_raw_parts(self.view.as_ptr(), self.view.len()) } - } -} - -#[cfg(test)] -mod tests { - use std::{ffi::OsString, fs, process::Command}; - - use subprocess_test::command_for_fn; - - use super::*; - - const SIZE: usize = 64 * 1024; - - #[test] - fn subprocess_open_ignores_changed_temp_and_working_directory() { - let owner = create(SIZE).unwrap(); - let changed_cwd = - temp_dir().join(BACKING_DIR).join(format!("changed-cwd-{}", Uuid::new_v4())); - fs::create_dir(&changed_cwd).unwrap(); - // SAFETY: The child does not access the mapping until this write completes. - unsafe { owner.as_ptr().write(17) }; - - let mut command = command_for_fn!(owner.id().to_owned(), |id: String| { - let opened = open(&id).unwrap(); - // SAFETY: The parent waits for this child and does not access the - // mapping concurrently. - unsafe { - assert_eq!(opened.as_ptr().read(), 17); - opened.as_ptr().add(SIZE - 1).write(29); - } - }); - command.cwd = changed_cwd.clone(); - command.envs.insert(OsString::from("TMP"), OsString::from("changed-relative-tmp")); - command.envs.insert(OsString::from("TEMP"), OsString::from("changed-relative-temp")); - let succeeded = Command::from(command).status().unwrap().success(); - fs::remove_dir(changed_cwd).unwrap(); - - assert!(succeeded); - // SAFETY: The child exited before this read. - assert_eq!(unsafe { owner.as_ptr().add(SIZE - 1).read() }, 29); - } - - #[test] - fn owner_cleanup_deletes_backing_file_and_preserves_existing_views() { - let owner = create(SIZE).unwrap(); - let id = owner.id().to_owned(); - let path = temp_dir().join(BACKING_DIR).join(format!("{id}.shm")); - let opened = open(&id).unwrap(); - assert!(path.exists()); - - drop(owner); - - assert!(!path.exists()); - // SAFETY: The mapping remains live and no other test access is concurrent. - unsafe { opened.as_ptr().write(17) }; - // SAFETY: The preceding write is complete and the mapping remains live. - assert_eq!(unsafe { opened.as_ptr().read() }, 17); - let reopened = open(&id).unwrap(); - drop(opened); - drop(reopened); - assert!(open(&id).is_err()); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn four_gib_mapping_is_sparse_and_supports_endpoint_access() { - const PRODUCTION_SIZE: usize = 4 * 1024 * 1024 * 1024; - const MAX_ENDPOINT_ALLOCATION: u64 = 16 * 1024 * 1024; - - let owner = create(PRODUCTION_SIZE).unwrap(); - let backing_file = owner.backing_file.as_ref().unwrap(); - let (logical_size, initial_allocation) = sys::file_sizes(backing_file).unwrap(); - assert_eq!(logical_size, PRODUCTION_SIZE as u64); - assert!(initial_allocation < MAX_ENDPOINT_ALLOCATION); - - let opened = open(owner.id()).unwrap(); - // SAFETY: Both endpoint indexes are within the exact mapped length and - // accesses are synchronized within this test. - unsafe { - owner.as_ptr().write(17); - owner.as_ptr().add(PRODUCTION_SIZE - 1).write(29); - assert_eq!(opened.as_ptr().read(), 17); - assert_eq!(opened.as_ptr().add(PRODUCTION_SIZE - 1).read(), 29); - } - - let (logical_size, endpoint_allocation) = sys::file_sizes(backing_file).unwrap(); - assert_eq!(logical_size, PRODUCTION_SIZE as u64); - assert!(endpoint_allocation < MAX_ENDPOINT_ALLOCATION); - } -} diff --git a/crates/fspy_shm/src/windows/sys.rs b/crates/fspy_shm/src/windows/sys.rs deleted file mode 100644 index 9786e5bba..000000000 --- a/crates/fspy_shm/src/windows/sys.rs +++ /dev/null @@ -1,192 +0,0 @@ -use std::{ - io, - iter::once, - os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}, - ptr::NonNull, -}; - -#[cfg(test)] -use windows_sys::Win32::Storage::FileSystem::{ - FILE_STANDARD_INFO, FileStandardInfo, GetFileInformationByHandleEx, -}; -use windows_sys::Win32::{ - Foundation::{ERROR_ALREADY_EXISTS, GetLastError}, - Storage::FileSystem::{ - FILE_ATTRIBUTE_TEMPORARY, FILE_FLAG_DELETE_ON_CLOSE, FILE_SHARE_DELETE, FILE_SHARE_READ, - FILE_SHARE_WRITE, - }, - System::{ - IO::DeviceIoControl, - Ioctl::FSCTL_SET_SPARSE, - Memory::{ - CreateFileMappingW, FILE_MAP_WRITE, MEMORY_BASIC_INFORMATION, - MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile, OpenFileMappingW, PAGE_READWRITE, - UnmapViewOfFile, VirtualQuery, - }, - }, -}; - -pub(super) const SHARE_ALL: u32 = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; -pub(super) const TEMPORARY: u32 = FILE_ATTRIBUTE_TEMPORARY; -pub(super) const DELETE_ON_CLOSE: u32 = FILE_FLAG_DELETE_ON_CLOSE; - -pub(super) fn set_sparse(file: &std::fs::File) -> io::Result<()> { - let mut bytes_returned = 0; - // SAFETY: `file` supplies a valid synchronous file handle. FSCTL_SET_SPARSE - // requires no input or output buffers, and `bytes_returned` is writable for - // the duration of the call. - let result = unsafe { - DeviceIoControl( - file.as_raw_handle().cast(), - FSCTL_SET_SPARSE, - std::ptr::null(), - 0, - std::ptr::null_mut(), - 0, - &raw mut bytes_returned, - std::ptr::null_mut(), - ) - }; - if result == 0 { Err(last_error()) } else { Ok(()) } -} - -#[cfg(test)] -pub(super) fn file_sizes(file: &std::fs::File) -> io::Result<(u64, u64)> { - let mut info = FILE_STANDARD_INFO::default(); - let info_size = u32::try_from(std::mem::size_of::()) - .map_err(|_| io::Error::other("file size information is too large"))?; - // SAFETY: `file` supplies a valid handle and `info` is a writable - // FILE_STANDARD_INFO buffer of exactly `info_size` bytes. - let result = unsafe { - GetFileInformationByHandleEx( - file.as_raw_handle().cast(), - FileStandardInfo, - (&raw mut info).cast(), - info_size, - ) - }; - if result == 0 { - return Err(last_error()); - } - - let logical_size = u64::try_from(info.EndOfFile) - .map_err(|_| io::Error::other("file has a negative logical size"))?; - let allocated_size = u64::try_from(info.AllocationSize) - .map_err(|_| io::Error::other("file has a negative allocated size"))?; - Ok((logical_size, allocated_size)) -} - -pub(super) fn create_file_mapping(file: &std::fs::File, name: &str) -> io::Result { - let name = wide_name(name)?; - // SAFETY: `file` supplies a valid handle, the security pointer is null, and - // `name` is a live, NUL-terminated UTF-16 buffer for the duration of the call. - let raw_handle = unsafe { - CreateFileMappingW( - file.as_raw_handle().cast(), - std::ptr::null(), - PAGE_READWRITE, - 0, - 0, - name.as_ptr(), - ) - }; - if raw_handle.is_null() { - return Err(last_error()); - } - - // CreateFileMappingW reports name collisions through the thread's last-error - // value even though it returns a valid handle. - // SAFETY: GetLastError has no preconditions and immediately follows that call. - let error = unsafe { GetLastError() }; - // SAFETY: A non-null CreateFileMappingW result is an owned mapping handle. - let handle = unsafe { OwnedHandle::from_raw_handle(raw_handle.cast()) }; - if error == ERROR_ALREADY_EXISTS { - Err(io::Error::new(io::ErrorKind::AlreadyExists, "shared-memory mapping already exists")) - } else { - Ok(handle) - } -} - -pub(super) fn open_file_mapping(name: &str) -> io::Result { - let name = wide_name(name)?; - // SAFETY: `name` is a live, NUL-terminated UTF-16 buffer and inheritance is disabled. - let raw_handle = unsafe { OpenFileMappingW(FILE_MAP_WRITE, 0, name.as_ptr()) }; - if raw_handle.is_null() { - return Err(last_error()); - } - - // SAFETY: A non-null OpenFileMappingW result is an owned mapping handle. - Ok(unsafe { OwnedHandle::from_raw_handle(raw_handle.cast()) }) -} - -pub(super) struct MappedView { - pointer: NonNull, - len: usize, - _mapping: OwnedHandle, -} - -impl MappedView { - pub(super) fn new(mapping: OwnedHandle) -> io::Result { - // SAFETY: `mapping` is a valid file-mapping handle. Offset and length - // zero map the complete section. - let view = - unsafe { MapViewOfFile(mapping.as_raw_handle().cast(), FILE_MAP_WRITE, 0, 0, 0) }; - let pointer = NonNull::new(view.Value.cast::()).ok_or_else(last_error)?; - - let mut info = MEMORY_BASIC_INFORMATION::default(); - // SAFETY: `pointer` is inside the mapped view and `info` is writable for - // its exact size. - let result = unsafe { - VirtualQuery( - pointer.as_ptr().cast(), - &raw mut info, - std::mem::size_of::(), - ) - }; - if result == 0 { - let error = last_error(); - // SAFETY: `pointer` is the base address returned by MapViewOfFile. - let _ = unsafe { - UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS { Value: pointer.as_ptr().cast() }) - }; - return Err(error); - } - - let len = info.RegionSize; - Ok(Self { pointer, len, _mapping: mapping }) - } - - pub(super) const fn as_ptr(&self) -> *mut u8 { - self.pointer.as_ptr() - } - - pub(super) const fn len(&self) -> usize { - self.len - } -} - -impl Drop for MappedView { - fn drop(&mut self) { - // SAFETY: `pointer` is the base address returned by MapViewOfFile and - // this guard owns that view until this single unmap operation. - let _ = unsafe { - UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS { Value: self.pointer.as_ptr().cast() }) - }; - } -} - -fn wide_name(name: &str) -> io::Result> { - if name.contains('\0') { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "shared-memory name contains a NUL", - )); - } - Ok(name.encode_utf16().chain(once(0)).collect()) -} - -fn last_error() -> io::Error { - // SAFETY: GetLastError has no preconditions and is called immediately after - // the failing Win32 operation on the same thread. - io::Error::from_raw_os_error(unsafe { GetLastError() }.cast_signed()) -} diff --git a/crates/fspy_test_bin/.clippy.toml b/crates/fspy_test_bin/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/fspy_test_bin/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/fspy_test_bin/Cargo.toml b/crates/fspy_test_bin/Cargo.toml deleted file mode 100644 index bdde14400..000000000 --- a/crates/fspy_test_bin/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "fspy_test_bin" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[target.'cfg(target_os = "linux")'.dependencies] -nix = { workspace = true } - -[lints] -workspace = true diff --git a/crates/fspy_test_bin/src/main.rs b/crates/fspy_test_bin/src/main.rs deleted file mode 100644 index 35e548568..000000000 --- a/crates/fspy_test_bin/src/main.rs +++ /dev/null @@ -1,48 +0,0 @@ -#[expect(clippy::unimplemented, reason = "fspy_test_bin is a test-only binary for Linux")] -#[cfg(not(target_os = "linux"))] -fn main() { - unimplemented!("fspy_test_bin is only for Linux"); -} - -#[cfg(target_os = "linux")] -fn main() { - use std::fs::File; - - use nix::fcntl::{AT_FDCWD, OFlag, OpenHow, openat2}; - let args = std::env::args().collect::>(); - assert!(args.len() == 3, "expected 2 arguments: "); - let action = args[1].as_str(); - let path = args[2].as_str(); - - match action { - "open_read" => { - let _ = File::open(path); - } - "open_write" => { - let _ = File::options().write(true).open(path); - } - "open_readwrite" => { - let _ = File::options().read(true).write(true).open(path); - } - "openat2_read" => { - let _ = openat2(AT_FDCWD, path, OpenHow::new().flags(OFlag::O_RDONLY)); - } - "openat2_write" => { - let _ = openat2(AT_FDCWD, path, OpenHow::new().flags(OFlag::O_WRONLY)); - } - "openat2_readwrite" => { - let _ = openat2(AT_FDCWD, path, OpenHow::new().flags(OFlag::O_RDWR)); - } - "readdir" => { - let mut entries = std::fs::read_dir(path).unwrap(); - let _ = entries.next(); - } - "stat" => { - let _ = std::fs::metadata(path); - } - "execve" => { - let _ = std::process::Command::new(path).spawn(); - } - _ => panic!("unknown action: {action}"), - } -} diff --git a/crates/materialized_artifact/.clippy.toml b/crates/materialized_artifact/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/materialized_artifact/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/materialized_artifact/Cargo.toml b/crates/materialized_artifact/Cargo.toml deleted file mode 100644 index 643c40a15..000000000 --- a/crates/materialized_artifact/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "materialized_artifact" -version = "0.0.0" -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -tempfile = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false -test = false diff --git a/crates/materialized_artifact/README.md b/crates/materialized_artifact/README.md deleted file mode 100644 index 60b505d35..000000000 --- a/crates/materialized_artifact/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# materialized_artifact - -Materialize a compile-time–embedded file to disk on demand, for APIs that -need a filesystem path (`LoadLibrary`, `LD_PRELOAD`, helper binaries) rather -than the bytes you'd get from `include_bytes!`. The on-disk filename is -content-addressed so repeated calls skip writing, multiple versions coexist, -and stale files are never mistaken for current ones. See crate-level docs -for details. diff --git a/crates/materialized_artifact/src/lib.rs b/crates/materialized_artifact/src/lib.rs deleted file mode 100644 index 7380129ed..000000000 --- a/crates/materialized_artifact/src/lib.rs +++ /dev/null @@ -1,199 +0,0 @@ -//! Materialize a compile-time–embedded file to disk on demand. -//! -//! Some APIs need a file on disk — `LoadLibrary` and `LD_PRELOAD` take a -//! path, and helper binaries have to exist as actual files to be spawned — -//! but we want to ship a single executable. `materialized_artifact` embeds -//! the file content as a `&'static [u8]` at compile time via the -//! [`artifact!`] macro (same as `include_bytes!`), and [`Materialize::at`] -//! writes it out to disk when first needed — that materialization step is -//! the value-add over a bare `include_bytes!`. -//! -//! Materialized files are named `{name}_{hash}{suffix}` in the caller-chosen -//! directory. The hash (computed at build time by -//! `materialized_artifact_build::register`) gives three properties without -//! any coordination between processes: -//! -//! - **No repeated writes.** [`Materialize::at`] returns the existing path if -//! the file is already there; repeated calls and re-runs skip I/O. -//! - **Correctness.** Two binaries with different embedded content produce -//! different filenames, so a stale file from an older build is never -//! mistaken for the current one. -//! - **Coexistence.** Multiple versions of a materialized artifact (e.g. from -//! different builds of the host program on the same machine) share `dir` -//! without overwriting each other. - -use std::{ - fs, - io::{self, Write}, - path::{Path, PathBuf}, -}; - -/// A file embedded into the executable at compile time. -/// -/// Construct with [`artifact!`]; materialize to disk via -/// [`Artifact::materialize`] + [`Materialize::at`]. See the [crate docs] for -/// the design rationale. -/// -/// [crate docs]: crate -#[derive(Clone, Copy)] -pub struct Artifact { - name: &'static str, - content: &'static [u8], - hash: &'static str, -} - -/// Construct an [`Artifact`] from the env vars published by a build script -/// via `materialized_artifact_build::register`. -#[macro_export] -macro_rules! artifact { - ($name:literal) => { - $crate::Artifact::__new( - $name, - ::core::include_bytes!(::core::env!(::core::concat!( - "MATERIALIZED_ARTIFACT_", - $name, - "_PATH" - ))), - ::core::env!(::core::concat!("MATERIALIZED_ARTIFACT_", $name, "_HASH")), - ) - }; -} - -impl Artifact { - #[doc(hidden)] - #[must_use] - pub const fn __new(name: &'static str, content: &'static [u8], hash: &'static str) -> Self { - Self { name, content, hash } - } - - /// Start a fluent materialize chain. Supply optional [`Materialize::suffix`] - /// / [`Materialize::executable`] knobs, then terminate with - /// [`Materialize::at`]. - pub const fn materialize(&self) -> Materialize<'static> { - Materialize { - artifact: *self, - suffix: "", - #[cfg(unix)] - executable: false, - } - } -} - -/// Builder returned by [`Artifact::materialize`]. Terminate with -/// [`Materialize::at`] to write the file. -#[derive(Clone, Copy)] -#[must_use = "materialize() only configures — call .at(dir) to write the file"] -pub struct Materialize<'a> { - artifact: Artifact, - suffix: &'a str, - #[cfg(unix)] - executable: bool, -} - -impl Materialize<'_> { - /// Filename suffix appended after `{name}_{hash}` (e.g. `.dll`, `.dylib`). - /// Defaults to empty. - pub const fn suffix(self, suffix: &str) -> Materialize<'_> { - Materialize { - artifact: self.artifact, - suffix, - #[cfg(unix)] - executable: self.executable, - } - } - - /// Mark the materialized file as executable (`0o755` on Unix; no-op on - /// Windows where the filesystem has no executable bit). - #[cfg_attr(not(unix), expect(unused_mut, reason = "executable is Unix-only"))] - pub const fn executable(mut self) -> Self { - #[cfg(unix)] - { - self.executable = true; - } - self - } - - /// Materialize the artifact in `dir` under a content-addressed filename, - /// writing it if missing. On Unix, newly created files get `0o755` when - /// [`Materialize::executable`] was called and `0o644` otherwise, and an - /// existing file's mode is reconciled if it drifted. - /// - /// Returns the final path. If the target already exists and its mode - /// already matches, no I/O beyond the stat is performed. - /// - /// # Preconditions - /// - /// `dir` must already exist — this method does not create it. - /// - /// # Errors - /// - /// Returns an error if the directory can't be read/written, the stat - /// fails for any reason other than not-found, or the temp-file rename - /// fails and the destination still doesn't exist. - pub fn at(self, dir: impl AsRef) -> io::Result { - let dir = dir.as_ref(); - let path = - dir.join(format!("{}_{}{}", self.artifact.name, self.artifact.hash, self.suffix)); - - #[cfg(unix)] - let want_mode: u32 = if self.executable { 0o755 } else { 0o644 }; - - // Fast path: one stat tells us both whether the file exists and, - // on Unix, what its permission bits are. The content is assumed - // correct because the hash is in the filename, so there is nothing - // else to verify. - match fs::metadata(&path) { - #[cfg(unix)] - Ok(meta) => { - use std::os::unix::fs::PermissionsExt; - // Reconcile a drifted mode (e.g. someone chmod'd it away) - // but skip the syscall when it already matches. - if meta.permissions().mode() & 0o777 != want_mode { - fs::set_permissions(&path, fs::Permissions::from_mode(want_mode))?; - } - return Ok(path); - } - // On non-Unix there is no mode to reconcile; existence alone is - // enough to declare success. - #[cfg(not(unix))] - Ok(_) => return Ok(path), - // Not found: fall through to the create-and-rename path. - Err(err) if err.kind() == io::ErrorKind::NotFound => {} - // Any other stat failure (permission denied, I/O error, etc.) - // propagates — we can't reason about what's on disk. - Err(err) => return Err(err), - } - - // Slow path: write to a unique temp file in the same directory, then - // rename into place atomically. The temp must live in `dir` (not the - // system temp) so the final rename stays within one filesystem — cross- - // filesystem rename isn't atomic. `NamedTempFile`'s `Drop` removes the - // temp on any early return, so we never leak partial files on error. - #[cfg(unix)] - let mut tmp = { - use std::os::unix::fs::PermissionsExt; - // `Builder::permissions` sets the mode at open(2) time, so there's - // no window where the temp exists with the wrong bits. - tempfile::Builder::new() - .permissions(fs::Permissions::from_mode(want_mode)) - .tempfile_in(dir)? - }; - #[cfg(not(unix))] - let mut tmp = tempfile::NamedTempFile::new_in(dir)?; - tmp.as_file_mut().write_all(self.artifact.content)?; - - // `persist_noclobber` (link+unlink on Unix, MoveFileExW without - // REPLACE_EXISTING on Windows) fails atomically if the destination - // already exists — so two racing processes can't clobber each other - // mid-write, and the loser sees the error below. - if let Err(err) = tmp.persist_noclobber(&path) { - // If another process won the race and the destination now exists, - // treat that as success; `err.file` drops here, cleaning up our - // temp. Otherwise propagate the original error. - if !fs::exists(&path)? { - return Err(err.error); - } - } - Ok(path) - } -} diff --git a/crates/materialized_artifact_build/.clippy.toml b/crates/materialized_artifact_build/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/materialized_artifact_build/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/materialized_artifact_build/Cargo.toml b/crates/materialized_artifact_build/Cargo.toml deleted file mode 100644 index c2d5dbd3a..000000000 --- a/crates/materialized_artifact_build/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "materialized_artifact_build" -version = "0.0.0" -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -xxhash-rust = { workspace = true, features = ["xxh3"] } - -[lints] -workspace = true - -[lib] -doctest = false -test = false diff --git a/crates/materialized_artifact_build/README.md b/crates/materialized_artifact_build/README.md deleted file mode 100644 index 7f727fada..000000000 --- a/crates/materialized_artifact_build/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# materialized_artifact_build - -Build-script helper for publishing artifacts consumed by -`materialized_artifact`'s `artifact!` macro. diff --git a/crates/materialized_artifact_build/src/lib.rs b/crates/materialized_artifact_build/src/lib.rs deleted file mode 100644 index cc6c4fc77..000000000 --- a/crates/materialized_artifact_build/src/lib.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::{fs, path::Path}; - -/// Namespace prefix for the env vars set by [`register`] and consumed by -/// `materialized_artifact`'s `artifact!` macro. Exported so both crates agree -/// on the same prefix. -pub const ENV_PREFIX: &str = "MATERIALIZED_ARTIFACT_"; - -/// Publish an artifact at `path` so `materialized_artifact`'s `artifact!($name)` -/// macro can embed it. -/// -/// Emits three `cargo:…` directives: -/// `rerun-if-changed={path}`, -/// `rustc-env=MATERIALIZED_ARTIFACT_{name}_PATH={path}`, and -/// `rustc-env=MATERIALIZED_ARTIFACT_{name}_HASH={hex}`. The runtime resolves -/// these at compile time via `include_bytes!(env!(…))` and `env!(…)`. -/// -/// `name` is used both as the env-var key and as the on-disk filename prefix -/// (in `Materialize::at`), so it must be a valid identifier-like string -/// that matches the one passed to `artifact!`. -/// -/// # Panics -/// -/// Panics if `path` is not valid UTF-8 or cannot be read. -pub fn register(name: &str, path: &Path) { - let path_str = path.to_str().expect("artifact path must be valid UTF-8"); - #[expect(clippy::print_stdout, reason = "cargo build-script directives")] - { - // Emit rerun-if-changed before reading so cargo still sees it even if - // reading the file below panics. - println!("cargo:rerun-if-changed={path_str}"); - let bytes = - fs::read(path).unwrap_or_else(|e| panic!("failed to read artifact at {path_str}: {e}")); - let hash = format!("{:x}", xxhash_rust::xxh3::xxh3_128(&bytes)); - println!("cargo:rustc-env={ENV_PREFIX}{name}_PATH={path_str}"); - println!("cargo:rustc-env={ENV_PREFIX}{name}_HASH={hash}"); - } -} diff --git a/crates/native_str/Cargo.toml b/crates/native_str/Cargo.toml deleted file mode 100644 index 0b84da406..000000000 --- a/crates/native_str/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "native_str" -version = "0.0.0" -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -bumpalo = { workspace = true } -bytemuck = { workspace = true, features = ["must_cast", "derive"] } -wincode = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/native_str/README.md b/crates/native_str/README.md deleted file mode 100644 index 426f28889..000000000 --- a/crates/native_str/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# native_str - -A platform-native string type for lossless, zero-copy IPC. - -`NativeStr` is a `#[repr(transparent)]` newtype over `[u8]` that represents OS strings in their native encoding: - -- **Unix**: raw bytes (same as `OsStr`) -- **Windows**: raw wide character bytes (from `&[u16]`, stored as `&[u8]` for uniform handling) - -## Why not `OsStr`? - -`OsStr` requires valid UTF-8 for serialization. `NativeStr` can be serialized/deserialized losslessly regardless of encoding, with zero-copy support via wincode's `SchemaRead`. - -## Limitations - -**Not portable across platforms.** The binary representation of a `NativeStr` is platform-specific — Unix uses raw bytes while Windows uses wide character pairs. Deserializing a `NativeStr` that was serialized on a different platform leads to unspecified behavior (garbage data), but is not unsafe. - -This type is designed for same-platform IPC (e.g., shared memory between a parent process and its children), not for cross-platform data exchange or persistent storage. For portable paths, use UTF-8 strings instead. - -## Usage - -```rust -use native_str::NativeStr; - -// Unix: construct from bytes -#[cfg(unix)] -let s: &NativeStr = NativeStr::from_bytes(b"/tmp/foo"); - -// Windows: construct from wide chars -#[cfg(windows)] -let s: &NativeStr = NativeStr::from_wide(&[0x0048, 0x0069]); // "Hi" - -// Convert back to OsStr/OsString -let os = s.to_cow_os_str(); -``` diff --git a/crates/native_str/src/lib.rs b/crates/native_str/src/lib.rs deleted file mode 100644 index 647f01035..000000000 --- a/crates/native_str/src/lib.rs +++ /dev/null @@ -1,206 +0,0 @@ -#[cfg(windows)] -use std::ffi::OsString; -#[cfg(unix)] -use std::os::unix::ffi::OsStrExt as _; -#[cfg(windows)] -use std::os::windows::ffi::OsStrExt as _; -#[cfg(windows)] -use std::os::windows::ffi::OsStringExt as _; -use std::{borrow::Cow, ffi::OsStr, fmt::Debug, mem::MaybeUninit}; - -use bumpalo::Bump; -#[cfg(windows)] -use bytemuck::must_cast_slice; -use bytemuck::{TransparentWrapper, TransparentWrapperAlloc}; -use wincode::{ - SchemaRead, SchemaWrite, - config::Config, - error::{ReadResult, WriteResult}, - io::{Reader, Writer}, -}; - -/// A platform-native string type for lossless, zero-copy IPC. -/// -/// Similar to [`OsStr`], but: -/// - Can be infallibly and losslessly encoded/decoded using wincode. -/// (`SchemaWrite`/`SchemaRead` implementations for `OsStr` require it to be valid UTF-8. This does not.) -/// - Can be constructed from wide characters on Windows with zero copy. -/// - Supports zero-copy `SchemaRead`. -/// -/// # Platform representation -/// -/// - **Unix**: raw bytes of the `OsStr`. -/// - **Windows**: raw bytes transmuted from `&[u16]` (wide chars). See `to_os_string` for decoding. -/// -/// # Limitations -/// -/// **Not portable across platforms.** The binary representation is platform-specific. -/// Deserializing a `NativeStr` serialized on a different platform leads to unspecified -/// behavior (garbage data), but is not unsafe. Designed for same-platform IPC only. -#[derive(TransparentWrapper, PartialEq, Eq, Hash)] -#[repr(transparent)] -pub struct NativeStr { - // On unix, this is the raw bytes of the OsStr. - // On windows, this is safely transmuted from `&[u16]` in `NativeStr::from_wide`. We don't declare it as `&[u16]` to allow zero-copy read. - // Transmuting back to `&[u16]` would be unsafe because of different alignments between `u8` and `u16` (See `to_os_string`). - data: [u8], -} - -impl NativeStr { - #[cfg(unix)] - #[must_use] - pub fn from_bytes(bytes: &[u8]) -> &Self { - Self::wrap_ref(bytes) - } - - #[cfg(windows)] - #[must_use] - pub fn from_wide(wide: &[u16]) -> &Self { - Self::wrap_ref(must_cast_slice(wide)) - } - - #[cfg(unix)] - #[must_use] - pub fn as_os_str(&self) -> &OsStr { - OsStr::from_bytes(&self.data) - } - - #[cfg(windows)] - #[must_use] - pub fn to_os_string(&self) -> OsString { - use bytemuck::{allocation::pod_collect_to_vec, try_cast_slice}; - - try_cast_slice::(&self.data).map_or_else( - |_| { - let wide = pod_collect_to_vec::(&self.data); - OsString::from_wide(&wide) - }, - OsString::from_wide, - ) - } - - #[must_use] - pub fn to_cow_os_str(&self) -> Cow<'_, OsStr> { - #[cfg(windows)] - return Cow::Owned(self.to_os_string()); - #[cfg(unix)] - return Cow::Borrowed(self.as_os_str()); - } - - pub fn clone_in<'bump>(&self, bump: &'bump Bump) -> &'bump Self { - Self::wrap_ref(bump.alloc_slice_copy(&self.data)) - } -} - -impl Debug for NativeStr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(self.to_cow_os_str().as_ref(), f) - } -} - -// Manual impl: wincode derive requires Sized, but NativeStr wraps unsized [u8]. -// SAFETY: Delegates to `[u8]`'s SchemaWrite impl, preserving its size/write invariants. -unsafe impl SchemaWrite for NativeStr { - type Src = Self; - - fn size_of(src: &Self::Src) -> WriteResult { - <[u8] as SchemaWrite>::size_of(&src.data) - } - - fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { - <[u8] as SchemaWrite>::write(writer, &src.data) - } -} - -// SchemaRead for &NativeStr: zero-copy borrow from input bytes -// SAFETY: Delegates to `&[u8]`'s SchemaRead impl; dst is initialized on Ok. -unsafe impl<'de, C: Config> SchemaRead<'de, C> for &'de NativeStr { - type Dst = &'de NativeStr; - - fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { - let data: &'de [u8] = <&[u8] as SchemaRead<'de, C>>::get(&mut reader)?; - dst.write(NativeStr::wrap_ref(data)); - Ok(()) - } -} - -// SAFETY: Delegates to `NativeStr`'s SchemaWrite impl, preserving its invariants. -unsafe impl SchemaWrite for Box { - type Src = Self; - - fn size_of(src: &Self::Src) -> WriteResult { - >::size_of(src) - } - - fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { - >::write(writer, src) - } -} - -// SchemaRead for Box: owned decode -// SAFETY: Delegates to `&[u8]`'s SchemaRead impl; dst is initialized on Ok. -unsafe impl<'de, C: Config> SchemaRead<'de, C> for Box { - type Dst = Self; - - fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { - let data: &[u8] = <&[u8] as SchemaRead<'de, C>>::get(&mut reader)?; - dst.write(NativeStr::wrap_box(data.into())); - Ok(()) - } -} - -#[cfg(unix)] -impl<'a, S: AsRef + ?Sized> From<&'a S> for &'a NativeStr { - fn from(value: &'a S) -> Self { - NativeStr::from_bytes(value.as_ref().as_bytes()) - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - NativeStr::wrap_box(self.data.into()) - } -} - -impl> From for Box { - #[cfg(unix)] - fn from(value: S) -> Self { - NativeStr::wrap_box(value.as_ref().as_bytes().into()) - } - - #[cfg(windows)] - fn from(value: S) -> Self { - let wide: Vec = value.as_ref().encode_wide().collect(); - let data: &[u8] = must_cast_slice(&wide); - NativeStr::wrap_box(data.into()) - } -} - -#[cfg(test)] -mod tests { - #[cfg(windows)] - use super::*; - - #[cfg(windows)] - #[test] - fn test_from_wide() { - use std::os::windows::ffi::OsStrExt; - - let wide_str: &[u16] = &[528, 491]; - let native_str = NativeStr::from_wide(wide_str); - - let mut encoded = wincode::serialize(native_str).unwrap(); - - let decoded: &NativeStr = wincode::deserialize(&encoded).unwrap(); - let decoded_wide = decoded.to_os_string().encode_wide().collect::>(); - assert_eq!(decoded_wide, wide_str); - - let encoded_len = encoded.len(); - encoded.push(0); - encoded.copy_within(..encoded_len, 1); - - let decoded: &NativeStr = wincode::deserialize(&encoded[1..]).unwrap(); - let decoded_wide = decoded.to_os_string().encode_wide().collect::>(); - assert_eq!(decoded_wide, wide_str); - } -} diff --git a/crates/preload_test_lib/Cargo.toml b/crates/preload_test_lib/Cargo.toml deleted file mode 100644 index 6a0d4fe56..000000000 --- a/crates/preload_test_lib/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "preload_test_lib" -version = "0.0.0" -edition.workspace = true -license.workspace = true -publish = false - -[lib] -crate-type = ["cdylib"] -test = false -doctest = false - -[target.'cfg(target_os = "linux")'.dependencies] -libc = { workspace = true } - -[lints] -workspace = true diff --git a/crates/preload_test_lib/src/lib.rs b/crates/preload_test_lib/src/lib.rs deleted file mode 100644 index 6b6dbdc9c..000000000 --- a/crates/preload_test_lib/src/lib.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! Test-only `LD_PRELOAD` library used by the `preexisting_ld_preload` e2e -//! fixture. Intercepts `open`/`openat` (and their `64` variants) to exercise -//! two behaviours fspy must tolerate when appended to a pre-existing -//! `LD_PRELOAD` list: -//! -//! 1. For paths containing the marker `preload_test_short_circuit`, the -//! call is short-circuited with `ENOENT` *without* forwarding to the -//! next preloaded library. Because fspy is appended after this library -//! in the preload list, fspy never observes the call — exactly the -//! property we want to verify. -//! 2. For every other path the call is forwarded via -//! `dlsym(RTLD_NEXT, …)`, so fspy still sees the real accesses and can -//! track them as cache inputs. -#![cfg(target_os = "linux")] -#![feature(c_variadic)] - -use std::{ - ffi::{CStr, c_char, c_int}, - sync::OnceLock, -}; - -const MARKER: &[u8] = b"preload_test_short_circuit"; - -fn should_short_circuit(path: *const c_char) -> bool { - if path.is_null() { - return false; - } - // SAFETY: callers of `open`/`openat` pass a valid NUL-terminated C string - // (or NULL, handled above). - let bytes = unsafe { CStr::from_ptr(path) }.to_bytes(); - bytes.windows(MARKER.len()).any(|w| w == MARKER) -} - -fn fail_with_enoent() -> c_int { - // SAFETY: `__errno_location` is async-signal-safe and always returns a - // valid pointer to the per-thread errno. - unsafe { *libc::__errno_location() = libc::ENOENT }; - -1 -} - -const fn has_mode_arg(flags: c_int) -> bool { - flags & libc::O_CREAT != 0 || flags & libc::O_TMPFILE != 0 -} - -type OpenFn = unsafe extern "C" fn(*const c_char, c_int, ...) -> c_int; -type OpenatFn = unsafe extern "C" fn(c_int, *const c_char, c_int, ...) -> c_int; - -fn load_next_fn(name: &CStr) -> F { - // SAFETY: `dlsym` with `RTLD_NEXT` returns either NULL or a valid - // function pointer for a symbol that must exist in libc. The cast is - // valid because the caller supplies a `F` whose layout is a function - // pointer of the corresponding libc signature. - let ptr = unsafe { libc::dlsym(libc::RTLD_NEXT, name.as_ptr()) }; - assert!(!ptr.is_null(), "dlsym RTLD_NEXT returned null"); - // SAFETY: see above. - unsafe { std::mem::transmute_copy(&ptr) } -} - -fn next_open() -> OpenFn { - static S: OnceLock = OnceLock::new(); - *S.get_or_init(|| load_next_fn(c"open")) -} -fn next_open64() -> OpenFn { - static S: OnceLock = OnceLock::new(); - *S.get_or_init(|| load_next_fn(c"open64")) -} -fn next_openat() -> OpenatFn { - static S: OnceLock = OnceLock::new(); - *S.get_or_init(|| load_next_fn(c"openat")) -} -fn next_openat64() -> OpenatFn { - static S: OnceLock = OnceLock::new(); - *S.get_or_init(|| load_next_fn(c"openat64")) -} - -/// # Safety -/// Interposer over libc `open(2)`; same contract as the real function. Must -/// only be called by the dynamic loader after installation via `LD_PRELOAD`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn open(path: *const c_char, flags: c_int, mut args: ...) -> c_int { - if should_short_circuit(path) { - return fail_with_enoent(); - } - if has_mode_arg(flags) { - // SAFETY: `O_CREAT`/`O_TMPFILE` guarantees a `mode_t` follows per - // the `open(2)` contract. - let mode: libc::mode_t = unsafe { args.next_arg() }; - // SAFETY: forwarding the caller's arguments unchanged. - unsafe { next_open()(path, flags, mode) } - } else { - // SAFETY: forwarding the caller's arguments unchanged. - unsafe { next_open()(path, flags) } - } -} - -/// # Safety -/// Interposer over libc `open64(2)`; same contract as the real function. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn open64(path: *const c_char, flags: c_int, mut args: ...) -> c_int { - if should_short_circuit(path) { - return fail_with_enoent(); - } - if has_mode_arg(flags) { - // SAFETY: `O_CREAT`/`O_TMPFILE` guarantees a `mode_t` follows per - // the `open64(2)` contract. - let mode: libc::mode_t = unsafe { args.next_arg() }; - // SAFETY: forwarding the caller's arguments unchanged. - unsafe { next_open64()(path, flags, mode) } - } else { - // SAFETY: forwarding the caller's arguments unchanged. - unsafe { next_open64()(path, flags) } - } -} - -/// # Safety -/// Interposer over libc `openat(2)`; same contract as the real function. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn openat( - dirfd: c_int, - path: *const c_char, - flags: c_int, - mut args: ... -) -> c_int { - if should_short_circuit(path) { - return fail_with_enoent(); - } - if has_mode_arg(flags) { - // SAFETY: `O_CREAT`/`O_TMPFILE` guarantees a `mode_t` follows per - // the `openat(2)` contract. - let mode: libc::mode_t = unsafe { args.next_arg() }; - // SAFETY: forwarding the caller's arguments unchanged. - unsafe { next_openat()(dirfd, path, flags, mode) } - } else { - // SAFETY: forwarding the caller's arguments unchanged. - unsafe { next_openat()(dirfd, path, flags) } - } -} - -/// # Safety -/// Interposer over libc `openat64(2)`; same contract as the real function. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn openat64( - dirfd: c_int, - path: *const c_char, - flags: c_int, - mut args: ... -) -> c_int { - if should_short_circuit(path) { - return fail_with_enoent(); - } - if has_mode_arg(flags) { - // SAFETY: `O_CREAT`/`O_TMPFILE` guarantees a `mode_t` follows per - // the `openat64(2)` contract. - let mode: libc::mode_t = unsafe { args.next_arg() }; - // SAFETY: forwarding the caller's arguments unchanged. - unsafe { next_openat64()(dirfd, path, flags, mode) } - } else { - // SAFETY: forwarding the caller's arguments unchanged. - unsafe { next_openat64()(dirfd, path, flags) } - } -} diff --git a/crates/pty_terminal/.clippy.toml b/crates/pty_terminal/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/pty_terminal/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/pty_terminal/Cargo.toml b/crates/pty_terminal/Cargo.toml deleted file mode 100644 index 77643cef6..000000000 --- a/crates/pty_terminal/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "pty_terminal" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -anyhow = { workspace = true } -portable-pty = { workspace = true } -vt100 = { workspace = true } - -[dev-dependencies] -ctor = { workspace = true } -ctrlc = { workspace = true } -ntest = { workspace = true } -subprocess_test = { workspace = true, features = ["portable-pty"] } -terminal_size = { workspace = true } - -[target.'cfg(unix)'.dev-dependencies] -nix = { workspace = true } -signal-hook = "0.4" - -[lints] -workspace = true - -[lib] -test = false -doctest = false - -[package.metadata.cargo-shear] -ignored = ["ctor"] diff --git a/crates/pty_terminal/README.md b/crates/pty_terminal/README.md deleted file mode 100644 index 442cf8dc2..000000000 --- a/crates/pty_terminal/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# pty_terminal - -A headless terminal emulator built on top of [portable-pty](https://crates.io/crates/portable-pty) and [vt100](https://crates.io/crates/vt100). It spawns child processes inside a pseudo-terminal (PTY) and provides an API for reading, writing, and resizing the terminal programmatically. - -## Features - -- Cross-platform PTY support (Unix and Windows via ConPTY) -- Built-in VT100 terminal emulation with screen state tracking -- Synchronous read-until pattern matching for interactive process control -- Terminal resize with proper signal delivery (SIGWINCH on Unix) -- Ctrl+C support via PTY input - -## Usage - -```rust -use pty_terminal::{geo::ScreenSize, terminal::{CommandBuilder, Terminal}}; - -let cmd = CommandBuilder::new("echo"); -cmd.arg("hello"); - -let mut terminal = Terminal::spawn(ScreenSize { rows: 24, cols: 80 }, cmd)?; -terminal.read_until("hello")?; -let status = terminal.read_to_end()?; -assert!(status.success()); -``` diff --git a/crates/pty_terminal/src/geo.rs b/crates/pty_terminal/src/geo.rs deleted file mode 100644 index d7482a985..000000000 --- a/crates/pty_terminal/src/geo.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[derive(Debug, Clone, Copy)] -pub struct ScreenSize { - pub rows: u16, - pub cols: u16, -} - -#[derive(Debug, Clone, Copy)] -pub struct CursorPosition { - pub rows: u16, - pub cols: u16, -} diff --git a/crates/pty_terminal/src/lib.rs b/crates/pty_terminal/src/lib.rs deleted file mode 100644 index 07fe7a20c..000000000 --- a/crates/pty_terminal/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod geo; -pub mod terminal; - -pub use portable_pty::ExitStatus; diff --git a/crates/pty_terminal/src/terminal.rs b/crates/pty_terminal/src/terminal.rs deleted file mode 100644 index 3382cfff4..000000000 --- a/crates/pty_terminal/src/terminal.rs +++ /dev/null @@ -1,379 +0,0 @@ -use std::{ - collections::VecDeque, - io::{Read, Write}, - sync::{Arc, Mutex, OnceLock}, - thread, -}; - -pub use portable_pty::CommandBuilder; -use portable_pty::{ChildKiller, ExitStatus, MasterPty}; - -use crate::geo::ScreenSize; - -type ChildWaitResult = Result>; - -/// The read half of a PTY connection. Implements [`Read`]. -/// -/// Reading feeds data through an internal vt100 parser (shared with [`PtyWriter`]), -/// keeping `screen_contents()` up-to-date with parsed terminal output. -pub struct PtyReader { - reader: Box, - parser: Arc>>, -} - -/// The write half of a PTY connection. Implements [`Write`]. -/// -/// The writer is shared with `Vt100Callbacks` (for DSR query responses) and the -/// background child-monitoring thread (which sets it to `None` on child exit). -pub struct PtyWriter { - writer: Arc>>>, - parser: Arc>>, - master: Arc>>>, -} - -/// A cloneable handle to a child process spawned in a PTY. -pub struct ChildHandle { - child_killer: Box, - exit_status: Arc>, -} - -impl Clone for ChildHandle { - fn clone(&self) -> Self { - Self { - child_killer: self.child_killer.clone_killer(), - exit_status: Arc::clone(&self.exit_status), - } - } -} - -/// A headless terminal consisting of a PTY reader, writer, and a child process handle. -pub struct Terminal { - pub pty_reader: PtyReader, - pub pty_writer: PtyWriter, - pub child_handle: ChildHandle, -} - -struct Vt100Callbacks { - writer: Arc>>>, - window_titles: VecDeque>, -} - -impl vt100::Callbacks for Vt100Callbacks { - fn set_window_title(&mut self, _screen: &mut vt100::Screen, title: &[u8]) { - self.window_titles.push_back(title.to_vec()); - } - - fn unhandled_csi( - &mut self, - screen: &mut vt100::Screen, - i1: Option, - i2: Option, - params: &[&[u16]], - c: char, - ) { - // CSI 6 n = Device Status Report (cursor position query) - // Response: ESC [ Pl ; Pc R - if let Some(&[6]) = params.first() - && i1.is_none() - && i2.is_none() - && c == 'n' - { - let (row, col) = screen.cursor_position(); - let response = format!("\x1b[{};{}R", row + 1, col + 1); - if let Some(writer) = self.writer.lock().unwrap().as_mut() { - let _ = writer.write_all(response.as_bytes()); - } - } - } -} - -impl Read for PtyReader { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - let n = self.reader.read(buf)?; - if n > 0 { - self.parser.lock().unwrap().process(&buf[..n]); - } - Ok(n) - } -} - -impl Write for PtyWriter { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - let mut guard = - self.writer.lock().map_err(|e| std::io::Error::other(format!("Poisoned lock: {e}")))?; - - guard.as_mut().map_or_else( - || Err(std::io::Error::new(std::io::ErrorKind::BrokenPipe, "Child process has exited")), - |writer| writer.write(buf), - ) - } - - fn flush(&mut self) -> std::io::Result<()> { - let mut guard = - self.writer.lock().map_err(|e| std::io::Error::other(format!("Poisoned lock: {e}")))?; - - guard.as_mut().map_or(Ok(()), Write::flush) - } -} - -impl PtyReader { - /// Returns the current terminal screen contents as a string (parsed by the vt100 emulator). - /// - /// # Panics - /// - /// Panics if the parser lock is poisoned. - #[must_use] - pub fn screen_contents(&self) -> String { - self.parser.lock().unwrap().screen().contents() - } - - /// Returns the screen contents row-by-row with inline ANSI SGR escapes - /// preserved — useful for snapshot tests that need to assert colour/style. - /// - /// Rows are produced via [`vt100::Screen::rows_formatted`], which emits - /// only the SGR attribute escapes (no cursor positioning, no - /// screen-erase sequences), so the output is platform-stable. Trailing - /// fully-empty rows are dropped; remaining rows are joined with `\n`. - /// - /// Bare SGR-reset sequences (`\x1b[m`) are also stripped: Unix PTYs emit - /// them between styled spans and at the end of styled runs, but Windows - /// `ConPTY` consolidates the byte stream and elides those resets. Stripping - /// them produces identical output on all platforms while preserving the - /// non-reset SGR transitions that the test actually cares about. - /// - /// # Panics - /// - /// Panics if the parser lock is poisoned. - #[expect( - clippy::significant_drop_tightening, - reason = "vt100::Screen::rows_formatted yields borrowed iterators that need the guard alive" - )] - #[must_use] - pub fn screen_contents_formatted(&self) -> Vec { - const RESET: &[u8] = b"\x1b[m"; - let guard = self.parser.lock().unwrap(); - let screen = guard.screen(); - let cols = screen.size().1; - let rows: Vec> = screen - .rows_formatted(0, cols) - .map(|mut row| { - while let Some(idx) = row.windows(RESET.len()).position(|w| w == RESET) { - row.drain(idx..idx + RESET.len()); - } - row - }) - .collect(); - let last_non_empty = rows.iter().rposition(|r| !r.is_empty()).map_or(0, |i| i + 1); - let mut out = Vec::new(); - for (i, row) in rows[..last_non_empty].iter().enumerate() { - if i > 0 { - out.push(b'\n'); - } - out.extend_from_slice(row); - } - out - } - - /// Takes the next window title received while parsing PTY output. - /// - /// # Panics - /// - /// Panics if the parser lock is poisoned. - #[must_use] - pub fn take_window_title(&self) -> Option> { - self.parser.lock().unwrap().callbacks_mut().window_titles.pop_front() - } - - /// Returns the current cursor position as `(row, col)`, both 0-indexed. - /// - /// # Panics - /// - /// Panics if the parser lock is poisoned. - #[must_use] - pub fn cursor_position(&self) -> (u16, u16) { - self.parser.lock().unwrap().screen().cursor_position() - } -} - -impl PtyWriter { - /// Returns `true` if the child process write channel has been closed. - /// - /// # Panics - /// - /// Panics if the writer lock is poisoned. - #[must_use] - pub fn is_closed(&self) -> bool { - self.writer.lock().unwrap().is_none() - } - - /// Writes `line` followed by a platform-appropriate line ending to the child process. - /// - /// On Unix, appends `\n`. On Windows `ConPTY`, appends `\r\n` for proper line handling. - /// - /// # Errors - /// - /// Returns an error if the child process has exited or writing fails. - pub fn write_line(&mut self, line: &[u8]) -> std::io::Result<()> { - self.write_all(line)?; - - #[cfg(not(target_os = "windows"))] - self.write_all(b"\n")?; - - #[cfg(target_os = "windows")] - self.write_all(b"\r\n")?; - - self.flush() - } - - /// Sends Ctrl+C (SIGINT) to the child process. - /// - /// Writes ETX (0x03) to the PTY. On Unix, the terminal driver converts this - /// to SIGINT for the child's process group. On Windows, `ConPTY` intercepts - /// the byte and generates `CTRL_C_EVENT` for the child. - /// - /// # Errors - /// - /// Returns an error if the child process has already exited or writing fails. - pub fn send_ctrl_c(&mut self) -> std::io::Result<()> { - self.write_all(&[0x03])?; - self.flush() - } - - /// Resizes the terminal to the given size. - /// - /// On Unix, delivers SIGWINCH to the child process. On Windows, `ConPTY` resizes synchronously. - /// - /// # Errors - /// - /// Returns an error if the child process has exited or the PTY cannot be resized. - /// - /// # Panics - /// - /// Panics if the PTY master or parser lock is poisoned. - pub fn resize(&self, size: ScreenSize) -> anyhow::Result<()> { - self.master - .lock() - .unwrap() - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Child process has exited"))? - .resize(portable_pty::PtySize { - rows: size.rows, - cols: size.cols, - pixel_width: 0, - pixel_height: 0, - })?; - - self.parser.lock().unwrap().screen_mut().set_size(size.rows, size.cols); - - Ok(()) - } -} - -impl ChildHandle { - /// Blocks until the child process has exited and returns its exit status. - /// - /// # Errors - /// - /// Returns an error if waiting for the child process exit status fails. - pub fn wait(&self) -> anyhow::Result { - match self.exit_status.wait() { - Ok(status) => Ok(status.clone()), - Err(error) => Err(anyhow::Error::new(Arc::clone(error))), - } - } - - /// Kills the child process. - /// - /// # Errors - /// - /// Returns an error if the child process cannot be killed. - pub fn kill(&mut self) -> anyhow::Result<()> { - self.child_killer.kill()?; - Ok(()) - } -} - -impl Terminal { - /// Spawns a new child process in a headless terminal with the given size and command. - /// - /// # Errors - /// - /// Returns an error if the PTY cannot be opened or the command fails to spawn. - /// - pub fn spawn(size: ScreenSize, cmd: CommandBuilder) -> anyhow::Result { - // On musl libc (Alpine Linux), concurrent PTY operations trigger - // SIGSEGV/SIGBUS in musl internals (sysconf, fcntl). This affects - // both openpty+fork and FD cleanup (close) from background threads. - // Serialize all PTY lifecycle operations that touch musl internals. - #[cfg(target_env = "musl")] - static PTY_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); - #[cfg(target_env = "musl")] - let _spawn_guard = PTY_LOCK.lock().unwrap_or_else(|e| e.into_inner()); - - let pty_pair = portable_pty::native_pty_system().openpty(portable_pty::PtySize { - rows: size.rows, - cols: size.cols, - pixel_width: 0, - pixel_height: 0, - })?; - // Create reader BEFORE spawning child to ensure it's ready for data - let reader = pty_pair.master.try_clone_reader()?; - let writer: Arc>>> = - Arc::new(Mutex::new(Some(pty_pair.master.take_writer()?))); - let mut child = pty_pair.slave.spawn_command(cmd)?; - let child_killer = child.clone_killer(); - let master = Arc::new(Mutex::new(Some(pty_pair.master))); - let exit_status: Arc> = Arc::new(OnceLock::new()); - - // Background thread: wait for child to exit, then clean up. - // - // The slave is kept alive until after `child.wait()` returns rather than - // being dropped immediately after spawn. On macOS, if the parent's slave - // fd is closed early (before spawn) and the child exits quickly, ALL - // slave references close before the reader issues its first `read()`. - // macOS then returns EIO on the master without draining the output buffer, - // causing data loss. Holding the slave until the background thread takes - // over guarantees the PTY stays connected while the child runs. - thread::spawn({ - let writer = Arc::clone(&writer); - let master = Arc::downgrade(&master); - let exit_status = Arc::clone(&exit_status); - let slave = pty_pair.slave; - move || { - let result = child.wait().map_err(Arc::new); - // Pin the master before publishing the result so a waiter cannot - // race cleanup and run a blocking ClosePseudoConsole itself. - let master = master.upgrade(); - let _ = exit_status.set(result); - // On musl, serialize FD cleanup (close) with PTY spawn to - // prevent racing on musl-internal state. - #[cfg(target_env = "musl")] - let _cleanup_guard = PTY_LOCK.lock().unwrap_or_else(|e| e.into_inner()); - // Close writer and slave before the master. On pre-26100 Windows, - // dropping the master calls ClosePseudoConsole and may block until - // the caller concurrently drains the output pipe. - let writer = - writer.lock().unwrap_or_else(std::sync::PoisonError::into_inner).take(); - drop(writer); - drop(slave); - let master = master.and_then(|master| { - master.lock().unwrap_or_else(std::sync::PoisonError::into_inner).take() - }); - drop(master); - } - }); - - let parser = Arc::new(Mutex::new(vt100::Parser::new_with_callbacks( - size.rows, - size.cols, - 0, - Vt100Callbacks { writer: Arc::clone(&writer), window_titles: VecDeque::new() }, - ))); - - Ok(Self { - pty_reader: PtyReader { reader, parser: Arc::clone(&parser) }, - pty_writer: PtyWriter { writer, parser, master }, - child_handle: ChildHandle { child_killer, exit_status }, - }) - } -} diff --git a/crates/pty_terminal/tests/terminal.rs b/crates/pty_terminal/tests/terminal.rs deleted file mode 100644 index 0d0b06e9a..000000000 --- a/crates/pty_terminal/tests/terminal.rs +++ /dev/null @@ -1,409 +0,0 @@ -use std::{ - io::{BufRead, BufReader, IsTerminal, Read, Write, stderr, stdin, stdout}, - time::{Duration, Instant}, -}; - -use ntest::timeout; -use portable_pty::CommandBuilder; -use pty_terminal::{geo::ScreenSize, terminal::Terminal}; -use subprocess_test::command_for_fn; - -#[test] -#[timeout(5000)] -#[expect(clippy::print_stdout, reason = "subprocess test output")] -fn is_terminal() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - println!("{} {} {}", stdin().is_terminal(), stdout().is_terminal(), stderr().is_terminal()); - })); - - let Terminal { mut pty_reader, pty_writer: _pty_writer, child_handle, .. } = - Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - let mut discard = Vec::new(); - pty_reader.read_to_end(&mut discard).unwrap(); - let _ = child_handle.wait().unwrap(); - let output = pty_reader.screen_contents(); - assert_eq!(output.trim(), "true true true"); -} - -#[test] -#[timeout(5000)] -#[expect(clippy::print_stdout, reason = "subprocess test output")] -fn write_basic_echo() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - use std::io::{BufRead, Write, stdin, stdout}; - let stdin = stdin(); - let mut stdout = stdout(); - let first_line = stdin.lock().lines().map_while(Result::ok).next(); - if let Some(line) = first_line { - print!("{line}"); - stdout.flush().unwrap(); - } - })); - - let Terminal { mut pty_reader, mut pty_writer, child_handle, .. } = - Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - - pty_writer.write_line(b"hello world").unwrap(); - - let mut discard = Vec::new(); - pty_reader.read_to_end(&mut discard).unwrap(); - let _ = child_handle.wait().unwrap(); - - let output = pty_reader.screen_contents(); - // PTY echoes the input, so we see "hello world\nhello world" - assert_eq!(output.trim(), "hello world\nhello world"); -} - -#[test] -#[timeout(5000)] -#[expect(clippy::print_stdout, reason = "subprocess test output")] -fn write_multiple_lines() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - use std::io::{BufRead, Write, stdin, stdout}; - let stdin = stdin(); - let mut stdout = stdout(); - for line in stdin.lock().lines().map_while(Result::ok) { - println!("Echo: {line}"); - stdout.flush().unwrap(); - if line == "third" { - break; - } - } - })); - - let Terminal { mut pty_reader, mut pty_writer, child_handle, .. } = - Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - - pty_writer.write_line(b"first").unwrap(); - { - let mut buf_reader = BufReader::new(&mut pty_reader); - let mut line = Vec::new(); - // Read PTY echo of "first\n" - buf_reader.read_until(b'\n', &mut line).unwrap(); - line.clear(); - // Read child response "Echo: first\n" - buf_reader.read_until(b'\n', &mut line).unwrap(); - } - - pty_writer.write_line(b"second").unwrap(); - { - let mut buf_reader = BufReader::new(&mut pty_reader); - let mut line = Vec::new(); - buf_reader.read_until(b'\n', &mut line).unwrap(); - line.clear(); - buf_reader.read_until(b'\n', &mut line).unwrap(); - } - - pty_writer.write_line(b"third").unwrap(); - - let mut discard = Vec::new(); - pty_reader.read_to_end(&mut discard).unwrap(); - let _ = child_handle.wait().unwrap(); - - let output = pty_reader.screen_contents(); - // PTY echoes input, then child prints "Echo: {line}\n" for each - assert_eq!(output.trim(), "first\nEcho: first\nsecond\nEcho: second\nthird\nEcho: third"); -} - -#[test] -#[timeout(5000)] -#[expect(clippy::print_stdout, reason = "subprocess test output")] -fn write_after_exit() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - print!("exiting"); - })); - - let Terminal { mut pty_reader, mut pty_writer, child_handle, .. } = - Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - - // Read all output - this blocks until child exits and EOF is reached - let mut discard = Vec::new(); - pty_reader.read_to_end(&mut discard).unwrap(); - let _ = child_handle.wait().unwrap(); - - // Writer shutdown is done by a background thread after child wait returns. - // Poll briefly for the writer state to flip to closed before asserting write failure. - let deadline = Instant::now() + Duration::from_millis(300); - while !pty_writer.is_closed() { - assert!(Instant::now() <= deadline, "writer did not close after child exit"); - std::thread::yield_now(); - } - - let result = pty_writer.write_all(b"too late\n"); - assert!(result.is_err()); -} - -#[test] -#[timeout(5000)] -#[expect(clippy::print_stdout, reason = "subprocess test output")] -fn write_interactive_prompt() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - use std::io::{Write, stdin, stdout}; - let mut stdout = stdout(); - // Use "Name:\n" instead of "Name: " so the test can synchronize with - // `read_until(b'\n')`. On Windows ConPTY, `read_until(b' ')` on the - // raw PTY stream can hang. - println!("Name:"); - stdout.flush().unwrap(); - - let mut input = std::string::String::new(); - stdin().read_line(&mut input).unwrap(); - print!("Hello, {}", input.trim()); - stdout.flush().unwrap(); - })); - - let Terminal { mut pty_reader, mut pty_writer, child_handle, .. } = - Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - - // Wait for the "Name:" prompt line. - { - let mut buf_reader = BufReader::new(&mut pty_reader); - let mut line = Vec::new(); - buf_reader.read_until(b'\n', &mut line).unwrap(); - assert!(String::from_utf8_lossy(&line).contains("Name:")); - } - - // Send response - pty_writer.write_line(b"Alice").unwrap(); - - let mut discard = Vec::new(); - pty_reader.read_to_end(&mut discard).unwrap(); - let _ = child_handle.wait().unwrap(); - - let output = pty_reader.screen_contents(); - assert_eq!(output.trim(), "Name:\nAlice\nHello, Alice"); -} - -#[test] -#[timeout(5000)] -#[expect(clippy::print_stdout, reason = "subprocess test output")] -fn resize_terminal() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - use std::io::{Write, stdin, stdout}; - #[cfg(unix)] - use std::sync::Arc; - #[cfg(unix)] - use std::sync::atomic::{AtomicBool, Ordering}; - - // Cross-platform function to get terminal size - fn get_size() -> (u16, u16) { - if let Some((terminal_size::Width(w), terminal_size::Height(h))) = - terminal_size::terminal_size() - { - (h, w) - } else { - (0, 0) - } - } - - #[cfg(unix)] - let resized = Arc::new(AtomicBool::new(false)); - #[cfg(unix)] - let resized_clone = Arc::clone(&resized); - - // Install SIGWINCH handler on Unix - #[cfg(unix)] - // SAFETY: The closure only performs an atomic store, which is signal-safe. - unsafe { - signal_hook::low_level::register(signal_hook::consts::SIGWINCH, move || { - resized_clone.store(true, Ordering::SeqCst); - }) - .unwrap(); - } - - // Print initial size - let (rows, cols) = get_size(); - println!("initial: {rows} {cols}"); - stdout().flush().unwrap(); - - // Wait for input to synchronize - let mut input = std::string::String::new(); - stdin().read_line(&mut input).unwrap(); - - // On Unix, check if resize signal was detected - #[cfg(unix)] - { - if resized.load(Ordering::SeqCst) { - println!("RESIZE_DETECTED"); - } - } - - // On Windows, ConPTY resizes synchronously - detect by checking size change - #[cfg(windows)] - { - let (new_rows, new_cols) = get_size(); - if (new_rows, new_cols) != (rows, cols) { - println!("RESIZE_DETECTED"); - } - } - - // Print new size - let (rows, cols) = get_size(); - println!("resized: {rows} {cols}"); - stdout().flush().unwrap(); - })); - - let Terminal { mut pty_reader, mut pty_writer, .. } = - Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - - // Wait for initial size line (synchronize before resizing) - { - let mut buf_reader = BufReader::new(&mut pty_reader); - let mut line = Vec::new(); - buf_reader.read_until(b'\n', &mut line).unwrap(); - assert!(String::from_utf8_lossy(&line).contains("initial: 80 80")); - } - - // Perform resize - pty_writer.resize(ScreenSize { rows: 40, cols: 40 }).unwrap(); - - // Signal the process to continue and check resize - pty_writer.write_line(b"").unwrap(); - - // Read remaining output - let mut discard = Vec::new(); - pty_reader.read_to_end(&mut discard).unwrap(); - - let output = pty_reader.screen_contents(); - // Verify resize was detected (SIGWINCH on Unix, synchronous on Windows) - assert!(output.contains("RESIZE_DETECTED")); - // Verify new size is correct - assert!(output.contains("resized: 40 40")); -} - -#[test] -#[timeout(5000)] -#[expect(clippy::print_stdout, reason = "subprocess test output")] -fn send_ctrl_c_interrupts_process() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - use std::io::{Write, stdout}; - - // On Linux, use signalfd to wait for SIGINT without signal handlers or - // background threads. This avoids musl issues where threads spawned during - // .init_array (via ctor) are blocked by musl's internal lock. - #[cfg(target_os = "linux")] - { - use nix::sys::{ - signal::{SigSet, Signal}, - signalfd::SignalFd, - }; - - // Block SIGINT so it goes to signalfd instead of the default handler. - let mut mask = SigSet::empty(); - mask.add(Signal::SIGINT); - mask.thread_block().unwrap(); - - let sfd = SignalFd::new(&mask).unwrap(); - - println!("ready"); - stdout().flush().unwrap(); - - // Block until SIGINT arrives via signalfd. - sfd.read_signal().unwrap().unwrap(); - print!("INTERRUPTED"); - stdout().flush().unwrap(); - std::process::exit(0); - } - - // On macOS/Windows, use ctrlc which works fine (no .init_array/musl issue). - #[cfg(not(target_os = "linux"))] - { - // On Windows, an ancestor process may have been created with - // CREATE_NEW_PROCESS_GROUP, which implicitly sets the per-process - // CTRL_C ignore flag (CONSOLE_IGNORE_CTRL_C in PEB ConsoleFlags). - // This flag is inherited by all descendants and silently drops - // CTRL_C_EVENT before it reaches registered handlers. Clear it. - // Ref: https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags - #[cfg(windows)] - { - // SAFETY: Declaring correct signature for SetConsoleCtrlHandler from kernel32. - unsafe extern "system" { - fn SetConsoleCtrlHandler( - handler: Option i32>, - add: i32, - ) -> i32; - } - - // SAFETY: Clearing the "ignore CTRL_C" flag so handlers are invoked. - unsafe { - SetConsoleCtrlHandler(None, 0); // FALSE = remove ignore - } - } - - ctrlc::set_handler(move || { - // Write directly and exit from the handler to avoid races. - use std::io::Write; - let _ = write!(std::io::stdout(), "INTERRUPTED"); - let _ = std::io::stdout().flush(); - std::process::exit(0); - }) - .unwrap(); - - println!("ready"); - stdout().flush().unwrap(); - - // Block until Ctrl+C handler exits the process. - loop { - std::thread::park(); - } - } - })); - - let Terminal { mut pty_reader, mut pty_writer, .. } = - Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - - // Wait for process to be ready - { - let mut buf_reader = BufReader::new(&mut pty_reader); - let mut line = Vec::new(); - buf_reader.read_until(b'\n', &mut line).unwrap(); - assert!(String::from_utf8_lossy(&line).contains("ready")); - } - - // Send Ctrl+C - pty_writer.send_ctrl_c().unwrap(); - - // Read remaining output - let mut discard = Vec::new(); - pty_reader.read_to_end(&mut discard).unwrap(); - - let output = pty_reader.screen_contents(); - // Verify interruption was detected - assert!(output.contains("INTERRUPTED")); -} - -#[test] -#[timeout(5000)] -#[expect(clippy::print_stdout, reason = "subprocess test output")] -fn read_to_end_returns_exit_status_success() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - println!("success"); - })); - - let Terminal { mut pty_reader, pty_writer: _retained_pty_writer, child_handle, .. } = - Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - // Keep the writer alive: the child monitor must release the PTY master while output is drained. - let mut discard = Vec::new(); - pty_reader.read_to_end(&mut discard).unwrap(); - let status = child_handle.wait().unwrap(); - assert!(status.success()); - assert_eq!(status.exit_code(), 0); - assert!(String::from_utf8_lossy(&discard).contains("success")); -} - -#[test] -#[timeout(5000)] -fn read_to_end_returns_exit_status_nonzero() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - std::process::exit(42); - })); - - let Terminal { mut pty_reader, pty_writer: _retained_pty_writer, child_handle, .. } = - Terminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - // Keep the writer alive: the child monitor must release the PTY master while output is drained. - let mut discard = Vec::new(); - pty_reader.read_to_end(&mut discard).unwrap(); - let status = child_handle.wait().unwrap(); - assert!(!status.success()); - assert_eq!(status.exit_code(), 42); -} diff --git a/crates/pty_terminal_test/.clippy.toml b/crates/pty_terminal_test/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/pty_terminal_test/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/pty_terminal_test/Cargo.toml b/crates/pty_terminal_test/Cargo.toml deleted file mode 100644 index e47ca1ffa..000000000 --- a/crates/pty_terminal_test/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "pty_terminal_test" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -anyhow = { workspace = true } -portable-pty = { workspace = true } -pty_terminal = { workspace = true } -pty_terminal_test_client = { workspace = true } - -[dev-dependencies] -crossterm = { workspace = true } -ctor = { workspace = true } -ntest = { workspace = true } -pty_terminal_test_client = { workspace = true, features = ["testing"] } -subprocess_test = { workspace = true, features = ["portable-pty"] } - -[lints] -workspace = true - -[lib] -test = false -doctest = false - -[package.metadata.cargo-shear] -ignored = ["ctor"] diff --git a/crates/pty_terminal_test/README.md b/crates/pty_terminal_test/README.md deleted file mode 100644 index 27110c3b1..000000000 --- a/crates/pty_terminal_test/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# pty_terminal_test - -`pty_terminal_test` is a thin test helper on top of `pty_terminal` for writing -integration tests against interactive CLI processes. - -It provides: - -- `TestTerminal::spawn(...)` to start a child process in a PTY. -- `writer` (`PtyWriter`) to send input to the child. -- `reader` (`Reader`) to wait for milestones and collect final exit status. - -## Why this crate exists - -Reading raw PTY bytes is often not enough for deterministic interactive tests. -You usually need explicit synchronization points from the child process. - -This crate solves that by pairing: - -- `pty_terminal_test_client::mark_milestone("name")` in the child process, and -- `reader.expect_milestone("name")` in the test process. - -## Core API - -```rust -use portable_pty::CommandBuilder; -use pty_terminal::geo::ScreenSize; -use pty_terminal_test::TestTerminal; - -let cmd = CommandBuilder::from("your-binary-or-subprocess-test-command"); -let TestTerminal { mut writer, mut reader, child_handle: _ } = - TestTerminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd)?; - -// Wait until child reaches a known point. -let _screen = reader.expect_milestone("ready"); - -// Interact with child. -writer.write_all(b"q")?; -writer.flush()?; - -// Wait for completion. -let status = reader.wait_for_exit(); -assert!(status.success()); -# Ok::<(), Box>(()) -``` - -## Milestone protocol - -Milestones are encoded as unique window titles: - -```text -pty-terminal-test:<32-hex-random-id>: -``` - -`Reader::expect_milestone` works like this: - -1. Drain title events captured by `PtyReader`. -2. Ignore ordinary titles and completed non-target milestones. -3. If no match exists, continue reading from the PTY and repeat. -4. Return the current screen once the requested title is observed. - -## Cross-platform behavior - -On Windows the client calls `SetConsoleTitleW`; ConPTY emits the resulting title -through its asynchronous renderer after preceding text and cursor state. On Unix -the client emits an OSC 2 title update, which follows normal PTY byte ordering. -The same token decoder and test API are used on every platform. - -## Typical test pattern - -In the child process: - -```rust -pty_terminal_test_client::mark_milestone("ready"); -// do work... -pty_terminal_test_client::mark_milestone("after-input"); -``` - -In the parent test: - -```rust -let _ = reader.expect_milestone("ready"); -writer.write_all(b"input")?; -writer.flush()?; -let screen = reader.expect_milestone("after-input"); -``` diff --git a/crates/pty_terminal_test/src/lib.rs b/crates/pty_terminal_test/src/lib.rs deleted file mode 100644 index c3b69d8cf..000000000 --- a/crates/pty_terminal_test/src/lib.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::io::{BufReader, Read}; - -pub use portable_pty::CommandBuilder; -use pty_terminal::terminal::{PtyReader, Terminal}; -pub use pty_terminal::{ - ExitStatus, - geo::ScreenSize, - terminal::{ChildHandle, PtyWriter}, -}; - -/// A test-oriented terminal that provides milestone-based synchronization. -/// -/// Wraps a PTY terminal, splitting it into a [`PtyWriter`] for sending input -/// and a [`Reader`] that can wait for named milestones emitted by the child -/// process via [`pty_terminal_test_client::mark_milestone`]. -pub struct TestTerminal { - pub writer: PtyWriter, - pub reader: Reader, - pub child_handle: ChildHandle, -} - -/// The read half of a test terminal, wrapping [`PtyReader`] with milestone support. -pub struct Reader { - pty: BufReader, - child_handle: ChildHandle, -} - -impl TestTerminal { - /// Spawns a new child process in a test terminal. - /// - /// # Errors - /// - /// Returns an error if the PTY cannot be opened or the command fails to spawn. - pub fn spawn(size: ScreenSize, cmd: CommandBuilder) -> anyhow::Result { - let Terminal { pty_reader, pty_writer, child_handle, .. } = Terminal::spawn(size, cmd)?; - Ok(Self { - writer: pty_writer, - reader: Reader { pty: BufReader::new(pty_reader), child_handle: child_handle.clone() }, - child_handle, - }) - } -} - -impl Reader { - /// Returns the current terminal screen contents. - #[must_use] - pub fn screen_contents(&self) -> String { - self.pty.get_ref().screen_contents() - } - - /// Returns the screen contents with inline ANSI SGR escape codes preserved. - /// Useful for snapshot tests that need to assert colour or style attributes. - #[must_use] - pub fn screen_contents_formatted(&self) -> Vec { - self.pty.get_ref().screen_contents_formatted() - } - - /// Reads from the PTY until a milestone with the given name is encountered. - /// - /// Returns the terminal screen contents at the moment the milestone is detected. - /// - /// Milestones use a uniform title token across platforms. - /// - /// # Panics - /// - /// Panics if the child process exits (EOF) before the named milestone is received, - /// or if a read error occurs. - #[must_use] - pub fn expect_milestone(&mut self, name: &str) -> String { - let mut buf = [0u8; 4096]; - - loop { - while let Some(title) = self.pty.get_ref().take_window_title() { - if pty_terminal_test_client::decode_milestone_title(&title) - .is_some_and(|milestone| milestone == name) - { - return self.screen_contents(); - } - } - - let n = self.pty.read(&mut buf).expect("PTY read failed"); - assert!(n > 0, "EOF reached before milestone '{name}'"); - } - } - - /// Reads all remaining PTY output until the child exits, then returns the exit status. - /// - /// # Errors - /// - /// Returns an error if waiting for the child process exit status fails. - /// - /// # Panics - /// - /// Panics if reading from the PTY fails. - pub fn wait_for_exit(&mut self) -> anyhow::Result { - let mut discard = Vec::new(); - self.pty.read_to_end(&mut discard).expect("PTY read_to_end failed"); - self.child_handle.wait() - } -} diff --git a/crates/pty_terminal_test/tests/milestone.rs b/crates/pty_terminal_test/tests/milestone.rs deleted file mode 100644 index 0b7bba03e..000000000 --- a/crates/pty_terminal_test/tests/milestone.rs +++ /dev/null @@ -1,154 +0,0 @@ -use std::io::Write; - -use ntest::timeout; -use portable_pty::CommandBuilder; -use pty_terminal::geo::ScreenSize; -use pty_terminal_test::TestTerminal; -use subprocess_test::command_for_fn; - -#[test] -#[timeout(5000)] -fn milestone_raw_mode_keystrokes() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - use std::io::{Read, Write, stdout}; - - // Enable raw mode (cross-platform via crossterm) - crossterm::terminal::enable_raw_mode().unwrap(); - - // Signal that raw mode is ready - pty_terminal_test_client::mark_milestone("ready"); - - let mut stdin = std::io::stdin(); - let mut stdout = stdout(); - let mut byte = [0u8; 1]; - - loop { - stdin.read_exact(&mut byte).unwrap(); - let ch = byte[0] as char; - - // Clear screen and print the keystroke at top-left - write!(stdout, "\x1b[2J\x1b[H{ch}").unwrap(); - stdout.flush().unwrap(); - - pty_terminal_test_client::mark_milestone("keystroke"); - - if ch == 'q' { - break; - } - } - - crossterm::terminal::disable_raw_mode().unwrap(); - })); - - let TestTerminal { mut writer, mut reader, child_handle: _ } = - TestTerminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - - // Wait for the subprocess to be ready - let _ = reader.expect_milestone("ready"); - - // Write 'a', expect keystroke, verify screen - writer.write_all(b"a").unwrap(); - writer.flush().unwrap(); - let screen = reader.expect_milestone("keystroke"); - assert_eq!(screen.trim(), "a"); - - // Write 'b', expect keystroke, verify screen - writer.write_all(b"b").unwrap(); - writer.flush().unwrap(); - let screen = reader.expect_milestone("keystroke"); - assert_eq!(screen.trim(), "b"); - - // Write 'c', expect keystroke, verify screen - writer.write_all(b"c").unwrap(); - writer.flush().unwrap(); - let screen = reader.expect_milestone("keystroke"); - assert_eq!(screen.trim(), "c"); - - // Write 'q' to quit and wait for the child to exit - writer.write_all(b"q").unwrap(); - writer.flush().unwrap(); - let status = reader.wait_for_exit().unwrap(); - assert!(status.success()); -} - -/// Verifies that the non-visual milestone title in `mark_milestone` does not -/// pollute `screen_contents()`. The subprocess appends characters without -/// clearing the screen, so any leftover space would appear between them. -#[test] -#[timeout(5000)] -fn milestone_does_not_pollute_screen() { - let cmd = CommandBuilder::from(command_for_fn!((), |(): ()| { - use std::io::{Read, Write, stdout}; - - crossterm::terminal::enable_raw_mode().unwrap(); - pty_terminal_test_client::mark_milestone("ready"); - - let mut stdin = std::io::stdin(); - let mut stdout = stdout(); - let mut byte = [0u8; 1]; - - loop { - stdin.read_exact(&mut byte).unwrap(); - let ch = byte[0] as char; - - // Append the character without clearing the screen - write!(stdout, "{ch}").unwrap(); - stdout.flush().unwrap(); - - pty_terminal_test_client::mark_milestone("keystroke"); - - if ch == 'q' { - break; - } - } - - crossterm::terminal::disable_raw_mode().unwrap(); - })); - - let TestTerminal { mut writer, mut reader, child_handle: _ } = - TestTerminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - - let _ = reader.expect_milestone("ready"); - - writer.write_all(b"a").unwrap(); - writer.flush().unwrap(); - let screen = reader.expect_milestone("keystroke"); - assert_eq!(screen.trim(), "a"); - - writer.write_all(b"b").unwrap(); - writer.flush().unwrap(); - let screen = reader.expect_milestone("keystroke"); - assert_eq!(screen.trim(), "ab"); - - writer.write_all(b"q").unwrap(); - writer.flush().unwrap(); - let status = reader.wait_for_exit().unwrap(); - assert!(status.success()); -} - -#[test] -#[timeout(5000)] -#[expect(clippy::redundant_clone, reason = "command_for_fn evaluates its argument twice")] -fn descendant_process_can_mark_milestone() { - let nested = command_for_fn!((), |(): ()| { - pty_terminal_test_client::mark_milestone("nested"); - }); - let nested_program = nested.program.to_string_lossy().into_owned(); - let nested_args = - nested.args.iter().map(|arg| arg.to_string_lossy().into_owned()).collect::>(); - - let cmd = CommandBuilder::from(command_for_fn!( - (nested_program.clone(), nested_args.clone()), - |(nested_program, nested_args): (String, Vec)| { - pty_terminal_test_client::mark_milestone("root"); - let status = - std::process::Command::new(nested_program).args(nested_args).status().unwrap(); - assert!(status.success()); - } - )); - - let TestTerminal { writer: _, mut reader, child_handle: _ } = - TestTerminal::spawn(ScreenSize { rows: 80, cols: 80 }, cmd).unwrap(); - let _ = reader.expect_milestone("nested"); - assert!(reader.wait_for_exit().unwrap().success()); -} diff --git a/crates/pty_terminal_test_client/.clippy.toml b/crates/pty_terminal_test_client/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/pty_terminal_test_client/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/pty_terminal_test_client/Cargo.toml b/crates/pty_terminal_test_client/Cargo.toml deleted file mode 100644 index 8af6077af..000000000 --- a/crates/pty_terminal_test_client/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "pty_terminal_test_client" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[features] -default = [] -testing = ["dep:getrandom", "dep:winapi"] - -[dependencies] -base64 = { workspace = true } -getrandom = { workspace = true, optional = true } - -[target.'cfg(windows)'.dependencies] -winapi = { workspace = true, features = ["wincon"], optional = true } - -[dev-dependencies] -getrandom = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/pty_terminal_test_client/README.md b/crates/pty_terminal_test_client/README.md deleted file mode 100644 index 8fb964a38..000000000 --- a/crates/pty_terminal_test_client/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# pty_terminal_test_client - -`pty_terminal_test_client` is the child-side helper used with -`pty_terminal_test`. - -It provides `mark_milestone("name")`, which emits milestone markers from the -subprocess so the parent test can synchronize on them. - -Reader-side behavior and protocol details are documented in: - -- `crates/pty_terminal_test/README.md` diff --git a/crates/pty_terminal_test_client/src/lib.rs b/crates/pty_terminal_test_client/src/lib.rs deleted file mode 100644 index c6afd8f82..000000000 --- a/crates/pty_terminal_test_client/src/lib.rs +++ /dev/null @@ -1,108 +0,0 @@ -use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; - -const MILESTONE_TITLE_MARKER: &str = "pty-terminal-test:"; - -#[cfg(any(feature = "testing", test))] -fn encode_milestone_title(name: &str) -> String { - let mut random = [0u8; 16]; - getrandom::fill(&mut random).expect("failed to generate milestone identity"); - let id = u128::from_be_bytes(random); - let encoded_name = URL_SAFE_NO_PAD.encode(name.as_bytes()); - format!("{MILESTONE_TITLE_MARKER}{id:032x}:{encoded_name}") -} - -/// Decodes a milestone title, ignoring ordinary application title updates. -#[must_use] -pub fn decode_milestone_title(title: &[u8]) -> Option { - let encoded = title.strip_prefix(MILESTONE_TITLE_MARKER.as_bytes())?; - let (encoded_id, encoded_name) = encoded.split_at_checked(32)?; - let (&b':', encoded_name) = encoded_name.split_first()? else { - return None; - }; - if !encoded_id.iter().all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(byte)) { - return None; - } - - let name_bytes = URL_SAFE_NO_PAD.decode(encoded_name).ok()?; - if URL_SAFE_NO_PAD.encode(&name_bytes).as_bytes() != encoded_name { - return None; - } - String::from_utf8(name_bytes).ok() -} - -/// Emits a milestone marker as a unique window-title update. -/// -/// The child process calls this to signal it has reached a named synchronization -/// point. The test harness (via `pty_terminal_test::Reader::expect_milestone`) -/// detects this marker and returns the screen contents at that point. -/// -/// Windows uses `SetConsoleTitleW`, which `ConPTY` emits through its renderer after -/// preceding text and cursor state. Other platforms emit the equivalent OSC 2 -/// title update through the ordered PTY byte stream. -/// -/// When the `testing` feature is disabled, this is a no-op. -/// -/// # Panics -/// -/// Panics if secure randomness is unavailable or emitting the title fails. -#[cfg(feature = "testing")] -pub fn mark_milestone(name: &str) { - emit_title(&encode_milestone_title(name)).expect("failed to emit milestone title"); -} - -#[cfg(all(feature = "testing", windows))] -fn emit_title(title: &str) -> std::io::Result<()> { - use std::io::Write as _; - - std::io::stdout().flush()?; - let mut wide = title.encode_utf16().collect::>(); - wide.push(0); - // SAFETY: `wide` is a valid NUL-terminated UTF-16 title. - if unsafe { winapi::um::wincon::SetConsoleTitleW(wide.as_ptr()) } == 0 { - Err(std::io::Error::last_os_error()) - } else { - Ok(()) - } -} - -#[cfg(all(feature = "testing", not(windows)))] -fn emit_title(title: &str) -> std::io::Result<()> { - use std::io::Write as _; - - let mut stdout = std::io::stdout().lock(); - stdout.flush()?; - write!(stdout, "\x1b]2;{title}\x1b\\")?; - stdout.flush() -} - -/// Does nothing when milestone instrumentation is disabled. -/// -/// When the `testing` feature is disabled, this is a no-op. -#[cfg(not(feature = "testing"))] -pub const fn mark_milestone(_name: &str) {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn title_round_trip() { - let title = encode_milestone_title("task-select:lib#:0"); - assert_eq!(decode_milestone_title(title.as_bytes()).as_deref(), Some("task-select:lib#:0")); - } - - #[test] - fn repeated_names_get_unique_titles() { - assert_ne!(encode_milestone_title("ready"), encode_milestone_title("ready")); - } - - #[test] - fn ignores_normal_and_malformed_titles() { - assert!(decode_milestone_title(b"normal title").is_none()); - assert!(decode_milestone_title(b"pty-terminal-test:not-hex:cmVhZHk").is_none()); - assert!( - decode_milestone_title(b"pty-terminal-test:00000000000000000000000000000000:*") - .is_none() - ); - } -} diff --git a/crates/snapshot_test/Cargo.toml b/crates/snapshot_test/Cargo.toml deleted file mode 100644 index 329f9de45..000000000 --- a/crates/snapshot_test/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "snapshot_test" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[lints] -workspace = true - -[lib] -test = false -doctest = false - -[dependencies] -serde = { workspace = true } -serde_json = { workspace = true } -similar = { workspace = true } diff --git a/crates/snapshot_test/README.md b/crates/snapshot_test/README.md deleted file mode 100644 index 8dd9bf13c..000000000 --- a/crates/snapshot_test/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# snapshot_test - -Minimal snapshot testing primitive for this workspace. Two methods: - -```rust -snapshots.check_snapshot(name, actual)?; // raw text → {name} -snapshots.check_json_snapshot(name, comment, value)?; // JSON → {name}.jsonc with `// {comment}` header -``` - -Both return `Result<(), String>` — the `String` contains a unified diff on mismatch, pointing to a `.new` file. Set `UPDATE_SNAPSHOTS=1` to accept new output in-place. - -## Why not `insta`? - -`insta::assert_*!` panics on mismatch and prints the diff to stderr. When run under `libtest-mimic` (which doesn't capture stdout/stderr), that diff appears in the middle of the test-runner output stream, _not_ inside the `failures:` summary section. The summary only shows the panic message (`snapshot assertion for 'X' failed in line N`). Returning `Result<(), String>` lets us put the full diff directly into the failure message where `cargo test` prints it at the end. diff --git a/crates/snapshot_test/src/lib.rs b/crates/snapshot_test/src/lib.rs deleted file mode 100644 index 19c2e2b1e..000000000 --- a/crates/snapshot_test/src/lib.rs +++ /dev/null @@ -1,104 +0,0 @@ -#![expect( - clippy::disallowed_types, - clippy::disallowed_macros, - clippy::disallowed_methods, - clippy::missing_panics_doc, - clippy::missing_errors_doc, - reason = "standalone test utility crate; std types, methods, and format! are appropriate" -)] - -use std::{ - fmt::Write as _, - fs, - path::{Path, PathBuf}, -}; - -use serde::Serialize; - -/// Scoped snapshot configuration. Created per-fixture, drives all assertions within. -pub struct Snapshots { - snapshot_dir: PathBuf, - update: bool, -} - -impl Snapshots { - pub fn new(snapshot_dir: impl Into) -> Self { - let update = std::env::var("UPDATE_SNAPSHOTS").is_ok_and(|v| v == "1"); - Self { snapshot_dir: snapshot_dir.into(), update } - } - - /// Serialize `value` as pretty JSON, prepend `// {comment}`, and compare - /// against `{snapshot_dir}/{name}.jsonc`. - pub fn check_json_snapshot( - &self, - name: &str, - comment: &str, - value: &impl Serialize, - ) -> Result<(), String> { - let json = serde_json::to_string_pretty(value).expect("failed to serialize snapshot value"); - self.check_snapshot(&format!("{name}.jsonc"), &format!("// {comment}\n{json}\n")) - } - - /// Compare `actual` text against `{snapshot_dir}/{name}`. - /// `name` is used as the filename directly (caller includes any extension). - pub fn check_snapshot(&self, name: &str, actual: &str) -> Result<(), String> { - let snap_path = self.snapshot_dir.join(name); - - if self.update { - fs::create_dir_all(&self.snapshot_dir).expect("failed to create snapshot directory"); - fs::write(&snap_path, actual).expect("failed to write snapshot"); - let _ = fs::remove_file(new_path(&snap_path)); - return Ok(()); - } - - let expected = match fs::read_to_string(&snap_path) { - Ok(content) => content.replace("\r\n", "\n"), - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - fs::create_dir_all(&self.snapshot_dir) - .expect("failed to create snapshot directory"); - fs::write(new_path(&snap_path), actual).expect("failed to write new snapshot"); - return Err(format_new_snapshot(name, &snap_path, actual)); - } - Err(e) => { - return Err(format!("failed to read snapshot {}: {e}", snap_path.display())); - } - }; - - if expected == actual { - return Ok(()); - } - - fs::write(new_path(&snap_path), actual).expect("failed to write new snapshot"); - Err(format_diff(name, &snap_path, &expected, actual)) - } -} - -/// Append `.new` to the full filename (e.g. `foo.jsonc` → `foo.jsonc.new`). -fn new_path(snap_path: &Path) -> PathBuf { - let mut s = snap_path.as_os_str().to_owned(); - s.push(".new"); - PathBuf::from(s) -} - -fn format_diff(name: &str, snap_path: &Path, expected: &str, actual: &str) -> String { - let diff = similar::TextDiff::from_lines(expected, actual); - let mut out = String::new(); - let _ = writeln!(out, "Snapshot: {name}"); - let _ = writeln!(out, "Stored new snapshot at: {}", new_path(snap_path).display()); - let _ = writeln!(out); - let _ = write!(out, "{}", diff.unified_diff().context_radius(3).header("expected", "actual")); - let _ = writeln!(out); - let _ = writeln!(out, "To update, re-run with UPDATE_SNAPSHOTS=1"); - out -} - -fn format_new_snapshot(name: &str, snap_path: &Path, actual: &str) -> String { - let mut out = String::new(); - let _ = writeln!(out, "Snapshot: {name}"); - let _ = writeln!(out, "Stored new snapshot at: {}", new_path(snap_path).display()); - let _ = writeln!(out); - let _ = writeln!(out, "{actual}"); - let _ = writeln!(out); - let _ = writeln!(out, "To update, re-run with UPDATE_SNAPSHOTS=1"); - out -} diff --git a/crates/subprocess_test/.clippy.toml b/crates/subprocess_test/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/subprocess_test/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/subprocess_test/Cargo.toml b/crates/subprocess_test/Cargo.toml deleted file mode 100644 index 720099a6a..000000000 --- a/crates/subprocess_test/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "subprocess_test" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -base64 = { workspace = true } -wincode = { workspace = true } -ctor = { workspace = true } -fspy = { workspace = true, optional = true } -portable-pty = { workspace = true, optional = true } -rustc-hash = { workspace = true } - -[features] -default = [] -fspy = ["dep:fspy"] -portable-pty = ["dep:portable-pty"] - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/subprocess_test/README.md b/crates/subprocess_test/README.md deleted file mode 100644 index 461abd617..000000000 --- a/crates/subprocess_test/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# subprocess_test - -Provides the `command_for_fn!` macro for running functions in separate processes during tests. - -This crate is shared by both `fspy` and `vite_*` crates, so it uses no prefix. - -## Usage - -To use `command_for_fn!`, you need to add `ctor` as a dependency (usually dev-dependency for tests): - -```toml -[dev-dependencies] -ctor = { workspace = true } -subprocess_test = { workspace = true } -``` - -Then use the macro in your tests: - -```rust -use subprocess_test::command_for_fn; - -let cmd = command_for_fn!(42u32, |arg: u32| { - println!("{}", arg); -}); - -// Convert to std::process::Command and execute -let output = std::process::Command::from(cmd).output().unwrap(); -``` diff --git a/crates/subprocess_test/src/lib.rs b/crates/subprocess_test/src/lib.rs deleted file mode 100644 index f9f81b970..000000000 --- a/crates/subprocess_test/src/lib.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::{env::current_exe, ffi::OsString, path::PathBuf, process::Command as StdCommand}; - -use base64::{Engine, prelude::BASE64_STANDARD_NO_PAD}; -use rustc_hash::FxHashMap; -use wincode::{SchemaReadOwned, SchemaWrite, config::DefaultConfig}; - -/// A command configuration that can be converted to `std::process::Command` -/// or `fspy::Command` for execution. -#[derive(Debug, Clone)] -pub struct Command { - pub program: OsString, - pub args: Vec, - pub envs: FxHashMap, - pub cwd: PathBuf, -} - -impl From for StdCommand { - fn from(cmd: Command) -> Self { - let mut std_cmd = Self::new(cmd.program); - std_cmd.args(cmd.args); - std_cmd.env_clear().envs(cmd.envs); - std_cmd.current_dir(cmd.cwd); - std_cmd - } -} - -#[cfg(feature = "fspy")] -impl From for fspy::Command { - fn from(cmd: Command) -> Self { - let mut fspy_cmd = Self::new(cmd.program); - fspy_cmd.args(cmd.args).envs(cmd.envs); - fspy_cmd.current_dir(cmd.cwd); - fspy_cmd - } -} - -#[cfg(feature = "portable-pty")] -impl From for portable_pty::CommandBuilder { - fn from(cmd: Command) -> Self { - let mut cmd_builder = Self::new(cmd.program); - cmd_builder.args(cmd.args); - cmd_builder.env_clear(); - for (key, value) in cmd.envs { - cmd_builder.env(key, value); - } - cmd_builder.cwd(cmd.cwd); - cmd_builder - } -} - -/// Creates a `subprocess_test::Command` that only executes the provided function. -/// -/// - $arg: The argument to pass to the function, must implement `SchemaWrite` and `SchemaReadOwned`. -/// - $f: The function to run in the separate process, takes one argument of the type of $arg. -#[macro_export] -macro_rules! command_for_fn { - ($arg: expr, $f: expr) => {{ - // Generate a unique ID for every invocation of this macro. - const ID: &str = - ::core::concat!(::core::file!(), ":", ::core::line!(), ":", ::core::column!()); - - fn assert_arg_type(_arg: &A, _f: impl FnOnce(A)) {} - assert_arg_type(&$arg, $f); - - // Register an initializer that runs the provided function when the process is started - #[::ctor::ctor(unsafe)] - unsafe fn init() { - $crate::init_impl(ID, $f); - } - // Create the command - $crate::create_command(ID, $arg) - }}; -} - -/// Read command-line arguments in a way that works during `.init_array`. -/// -/// On Linux, `std::env::args()` may return empty during `.init_array` -/// constructors (observed on musl targets) because the Rust runtime hasn't -/// initialized its argument storage yet. We fall back to reading -/// `/proc/self/cmdline` directly. -fn get_args() -> Vec { - let args: Vec = std::env::args().collect(); - if !args.is_empty() { - return args; - } - - // Fallback: read /proc/self/cmdline directly. - #[cfg(target_os = "linux")] - { - if let Some(args) = read_proc_cmdline() { - return args; - } - } - - args -} - -/// Read `/proc/self/cmdline` as a fallback that works before Rust runtime -/// initialization (during `.init_array` constructors). -#[cfg(target_os = "linux")] -fn read_proc_cmdline() -> Option> { - let buf = std::fs::read("/proc/self/cmdline").ok()?; - if buf.is_empty() { - return None; - } - - // /proc/self/cmdline has null-separated args with a trailing null. - // We must preserve empty args (e.g., empty base64 for `()` arg) but - // remove the trailing empty entry from the final null terminator. - let mut args: Vec = buf - .split(|&b| b == 0) - .filter_map(|s| std::str::from_utf8(s).ok().map(String::from)) - .collect(); - // Remove trailing empty string from the final null byte - if args.last().is_some_and(String::is_empty) { - args.pop(); - } - Some(args) -} - -#[doc(hidden)] -pub fn init_impl>(expected_id: &str, f: impl FnOnce(A)) { - let args = get_args(); - // - let (Some(current_id), Some(arg_base64)) = (args.get(1), args.get(2)) else { - return; - }; - if current_id != expected_id { - return; - } - let arg_bytes = BASE64_STANDARD_NO_PAD.decode(arg_base64).expect("Failed to decode base64 arg"); - let arg: A = wincode::deserialize(&arg_bytes).expect("Failed to decode wincode arg"); - f(arg); - std::process::exit(0); -} - -#[doc(hidden)] -pub fn create_command>(id: &str, arg: T) -> Command { - let program = current_exe().unwrap().into_os_string(); - let arg_bytes = wincode::serialize(&arg).expect("Failed to encode arg"); - let arg_base64 = BASE64_STANDARD_NO_PAD.encode(&arg_bytes); - - let args = vec![OsString::from(id), OsString::from(arg_base64)]; - let envs: FxHashMap = std::env::vars_os().collect(); - let cwd = std::env::current_dir().unwrap(); - - Command { program, args, envs, cwd } -} - -#[cfg(test)] -mod tests { - use std::str::from_utf8; - - use crate::StdCommand; - - #[test] - #[expect(clippy::print_stdout, reason = "test diagnostics")] - fn test_command_for_fn() { - let command = command_for_fn!(42u32, |arg: u32| { - print!("{arg}"); - }); - let output = StdCommand::from(command).output().unwrap(); - assert_eq!(from_utf8(&output.stdout), Ok("42")); - assert!(output.status.success()); - } -} diff --git a/crates/vite_glob/Cargo.toml b/crates/vite_glob/Cargo.toml deleted file mode 100644 index c5d31c807..000000000 --- a/crates/vite_glob/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "vite_glob" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -globset = { workspace = true } -thiserror = { workspace = true } -wax = { workspace = true } - -[dev-dependencies] -vite_str = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/vite_glob/README.md b/crates/vite_glob/README.md deleted file mode 100644 index 5c91b6d93..000000000 --- a/crates/vite_glob/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# vite_glob - -Centralizes glob-matching semantics so every crate in the workspace matches -patterns the same way, instead of each call site reaching for an ad-hoc glob -engine with subtly different rules (separators, case sensitivity, negation). - -Two use cases, each with its own module, matcher, and error type: - -- **`env`** — environment-variable **name** matching. Names are flat strings, - not paths, so this is backed by `globset` with path-separator handling - disabled: `*`/`?`/`[...]`/`{a,b}` are plain-string wildcards, and matching is - case-sensitive on Unix and case-insensitive on Windows (mirroring env lookup). - Use `EnvGlob` for one literal pattern, or `EnvGlobSet` for a set with - negation: a `!`-prefixed pattern excludes (e.g. `["VITE_*", "!VITE_SECRET"]`). -- **`path`** — filesystem **path** matching with gitignore semantics, backed by - `wax`. `!`-prefixed patterns negate; first-match-wins, or last-match-wins once - any negation is present. Use `PathGlobSet`. - -Keeping both behind one crate means a change to how, say, env names are matched -happens in exactly one place and applies everywhere — the runner's cache -fingerprinting, the IPC server's `getEnvs`, workspace package discovery, and so -on. diff --git a/crates/vite_glob/src/env.rs b/crates/vite_glob/src/env.rs deleted file mode 100644 index 65e163311..000000000 --- a/crates/vite_glob/src/env.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! Glob matching for environment-variable **names** (flat strings, never paths). -//! -//! Backed by `globset` with path-separator handling disabled, so `*`, `?`, -//! `[...]`, and `{a,b}` behave as plain-string wildcards. Matching is -//! case-sensitive on Unix and case-insensitive on Windows, mirroring how -//! environment variables are looked up on each platform. -//! -//! [`EnvGlobSet`] supports negation: a `!`-prefixed pattern *excludes* names, -//! and a name matches the set when it matches an include pattern and no exclude -//! pattern. [`EnvGlob`] matches a single pattern literally — `!` is an ordinary -//! character there (no negation), since a lone exclude has nothing to subtract -//! from. - -use globset::{Glob, GlobBuilder, GlobMatcher, GlobSet, GlobSetBuilder}; - -/// Error compiling an environment-variable name pattern. -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct EnvGlobError(#[from] globset::Error); - -/// Compiles `pattern` into a `globset::Glob` configured for env-name matching: -/// separators are not special, and case follows the platform's env semantics. -fn build(pattern: &str) -> Result { - GlobBuilder::new(pattern) - // Env names contain no path separators, so disabling separator handling - // makes `*`/`?` match any character — a pure string match. - .literal_separator(false) - // Env lookups are case-insensitive on Windows, case-sensitive elsewhere. - .case_insensitive(cfg!(windows)) - .build() -} - -/// Matches a single environment-variable name against one glob pattern. -#[derive(Debug, Clone)] -pub struct EnvGlob { - matcher: GlobMatcher, -} - -impl EnvGlob { - /// Compiles `pattern` into an env-name matcher. - /// - /// # Errors - /// Returns an error if `pattern` is not a valid glob. - pub fn new(pattern: &str) -> Result { - Ok(Self { matcher: build(pattern)?.compile_matcher() }) - } - - /// Returns whether `name` matches the pattern. - #[must_use] - pub fn is_match(&self, name: &str) -> bool { - self.matcher.is_match(name) - } -} - -/// Matches an environment-variable name against a **set** of glob patterns, -/// with negation. -/// -/// Patterns are split into includes and excludes: a `!`-prefixed pattern is an -/// **exclude**, any other pattern is an **include**. -/// -/// A name matches when it matches some include pattern and no exclude pattern. -/// A set with no include patterns matches nothing (an exclude has nothing to -/// subtract from), so an empty set — or a set of only excludes — never matches. -#[derive(Debug, Clone)] -pub struct EnvGlobSet { - include: GlobSet, - exclude: GlobSet, -} - -impl EnvGlobSet { - /// Compiles `patterns` into a combined env-name matcher. - /// - /// # Errors - /// Returns an error if any pattern is not a valid glob. - pub fn new(patterns: I) -> Result - where - I: IntoIterator, - S: AsRef, - { - let mut include = GlobSetBuilder::new(); - let mut exclude = GlobSetBuilder::new(); - for pattern in patterns { - let pattern = pattern.as_ref(); - if let Some(rest) = pattern.strip_prefix('!') { - exclude.add(build(rest)?); - } else { - include.add(build(pattern)?); - } - } - Ok(Self { include: include.build()?, exclude: exclude.build()? }) - } - - /// Returns whether `name` matches an include pattern and no exclude pattern. - #[must_use] - pub fn is_match(&self, name: &str) -> bool { - self.include.is_match(name) && !self.exclude.is_match(name) - } -} - -#[cfg(test)] -mod tests { - use super::{EnvGlob, EnvGlobSet}; - - #[test] - fn matches_star_prefix_and_suffix() { - let g = EnvGlob::new("VITE_*").unwrap(); - assert!(g.is_match("VITE_FOO")); - assert!(g.is_match("VITE_")); // `*` matches the empty string - assert!(!g.is_match("MYVITE_FOO")); - - let g = EnvGlob::new("*_KEY").unwrap(); - assert!(g.is_match("MY_KEY")); - assert!(!g.is_match("MY_KEYS")); - - let g = EnvGlob::new("*_CREDENTIAL*").unwrap(); - assert!(g.is_match("AWS_CREDENTIALS")); - assert!(g.is_match("X_CREDENTIAL_Y")); - } - - #[test] - fn question_mark_matches_exactly_one_char() { - let g = EnvGlob::new("APP?_*").unwrap(); - assert!(g.is_match("APP1_TOKEN")); - assert!(g.is_match("APP2_NAME")); - // `?` requires exactly one character, so `APP_X` (nothing before `_`) does not match. - assert!(!g.is_match("APP_X")); - } - - #[test] - fn brace_alternation_is_supported() { - let g = EnvGlob::new("{VITE,NEXT}_*").unwrap(); - assert!(g.is_match("VITE_FOO")); - assert!(g.is_match("NEXT_BAR")); - assert!(!g.is_match("NUXT_BAR")); - } - - #[test] - fn dot_and_separators_are_literal_not_path_special() { - // Env names are flat strings: `*` spans `.` and `/` (no path semantics), - // and a literal `.` in the pattern matches a literal `.`. - assert!(EnvGlob::new("A*").unwrap().is_match("A.B")); - assert!(EnvGlob::new("A*").unwrap().is_match("A/B")); - assert!(EnvGlob::new("*.local").unwrap().is_match("APP.local")); - assert!(!EnvGlob::new("*.local").unwrap().is_match("APPXlocal")); - } - - #[test] - fn single_glob_bang_is_a_literal_character() { - // A single `EnvGlob` has no negation: `!FOO` matches the literal name - // `!FOO`, not `FOO`. - let g = EnvGlob::new("!FOO").unwrap(); - assert!(g.is_match("!FOO")); - assert!(!g.is_match("FOO")); - } - - #[test] - fn non_match_default_is_false() { - assert!(!EnvGlob::new("VITE_*").unwrap().is_match("PATH")); - } - - #[test] - fn set_matches_any_pattern() { - let set = EnvGlobSet::new(["VITE_*", "*_KEY", "APP?_*"]).unwrap(); - assert!(set.is_match("VITE_FOO")); - assert!(set.is_match("MY_KEY")); - assert!(set.is_match("APP1_TOKEN")); - assert!(!set.is_match("PATH")); - assert!(!set.is_match("APP_X")); - } - - #[test] - fn empty_set_matches_nothing() { - let set = EnvGlobSet::new(std::iter::empty::<&str>()).unwrap(); - assert!(!set.is_match("VITE_FOO")); - } - - #[test] - fn set_negation_excludes_matching_names() { - // `!VITE_SECRET` excludes that name from the `VITE_*` include set. - let set = EnvGlobSet::new(["VITE_*", "!VITE_SECRET"]).unwrap(); - assert!(set.is_match("VITE_FOO")); - assert!(set.is_match("VITE_BAR")); - assert!(!set.is_match("VITE_SECRET")); - assert!(!set.is_match("PATH")); - - // An exclude glob can itself be a wildcard. - let set = EnvGlobSet::new(["*", "!*_SECRET"]).unwrap(); - assert!(set.is_match("VITE_FOO")); - assert!(!set.is_match("API_SECRET")); - } - - #[test] - fn set_only_excludes_matches_nothing() { - // With no include patterns there is nothing to subtract from. - let set = EnvGlobSet::new(["!FOO"]).unwrap(); - assert!(!set.is_match("FOO")); - assert!(!set.is_match("BAR")); - } - - #[test] - #[cfg(not(windows))] - fn unix_matching_is_case_sensitive() { - let g = EnvGlob::new("VITE_*").unwrap(); - assert!(g.is_match("VITE_FOO")); - assert!(!g.is_match("vite_foo")); - let set = EnvGlobSet::new(["VITE_*"]).unwrap(); - assert!(!set.is_match("vite_foo")); - } - - #[test] - #[cfg(windows)] - fn windows_matching_is_case_insensitive() { - let g = EnvGlob::new("VITE_*").unwrap(); - assert!(g.is_match("VITE_FOO")); - assert!(g.is_match("vite_foo")); - let set = EnvGlobSet::new(["VITE_*"]).unwrap(); - assert!(set.is_match("vite_foo")); - } -} diff --git a/crates/vite_glob/src/lib.rs b/crates/vite_glob/src/lib.rs deleted file mode 100644 index 7f04aaef1..000000000 --- a/crates/vite_glob/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Glob matching, split by use case: -//! -//! - [`mod@env`] — environment-variable **name** matching (flat strings), -//! backed by `globset` with path semantics disabled. -//! - [`mod@path`] — filesystem **path** matching with gitignore semantics, -//! backed by `wax`. -//! -//! Each module owns its own error type ([`env::EnvGlobError`] / -//! [`path::PathGlobError`]). - -pub mod env; -pub mod path; diff --git a/crates/vite_glob/src/path.rs b/crates/vite_glob/src/path.rs deleted file mode 100644 index 330e614fd..000000000 --- a/crates/vite_glob/src/path.rs +++ /dev/null @@ -1,375 +0,0 @@ -//! Glob matching for filesystem **paths** with gitignore semantics. -//! -//! Backed by `wax`. `!`-prefixed patterns negate. With no negation present, -//! first-match-wins; with any negation, last-match-wins (gitignore). - -#[expect(clippy::disallowed_types, reason = "wax::Glob::is_match requires std::path::Path")] -use std::path::Path; - -use wax::{Glob, Program}; - -/// Error compiling a path glob pattern. -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct PathGlobError(#[from] wax::BuildError); - -/// Matches filesystem paths against an ordered set of glob patterns. -/// -/// If there are no negated patterns, it will follow the first match wins semantics. -/// Otherwise, it will follow the last match wins semantics. -#[derive(Debug)] -pub struct PathGlobSet<'a> { - /// (`glob_pattern`, `match_or_not`) - patterns: Vec<(Glob<'a>, bool)>, - has_negated: bool, -} - -impl<'a> PathGlobSet<'a> { - /// # Errors - /// Returns an error if any glob pattern is invalid. - pub fn new(match_patterns: I) -> Result - where - I: IntoIterator, - S: AsRef + 'a + ?Sized, - { - let mut patterns = Vec::new(); - let mut has_negated = false; - for pattern in match_patterns { - let pattern_str = pattern.as_ref(); - if let Some(negated) = pattern_str.strip_prefix('!') { - // negated pattern, ignore the path - patterns.push((Glob::new(negated)?, false)); - // set to true to follow last match wins semantics - has_negated = true; - } else { - // positive pattern, match the path - patterns.push((Glob::new(pattern_str)?, true)); - } - } - Ok(Self { patterns, has_negated }) - } - - #[expect(clippy::disallowed_types, reason = "wax::Glob::is_match requires std::path::Path")] - pub fn is_match(&self, path: impl AsRef) -> bool { - let mut should_match = false; // Default: don't match - for (glob, match_or_not) in &self.patterns { - if glob.is_match(path.as_ref()) { - should_match = *match_or_not; - if !self.has_negated { - // first match wins semantics - break; - } - } - } - should_match - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_match_ignores_node_modules() -> Result<(), PathGlobError> { - let patterns = vec![ - // ignore all paths - "**/*", - // keep node_modules directories themselves - "!**/node_modules", - "!node_modules", - // keep lock files and package.json - "!**/package.json", - "!**/package-lock.json", - "!**/yarn.lock", - "!**/pnpm-lock.yaml", - ]; - let ignores = PathGlobSet::new(&patterns)?; - - // Should ignore paths inside node_modules - assert!(ignores.is_match("node_modules/react/index.js")); - assert!(ignores.is_match("apps/web/node_modules/react/index.js")); - assert!(ignores.is_match("packages/cli/node_modules/@types/node/index.d.ts")); - - // Should ignore paths outside node_modules - assert!(ignores.is_match("src/index.js")); - assert!(ignores.is_match("tsbuildinfo.json")); - - // Should NOT ignore node_modules directories themselves (due to negation) - assert!(!ignores.is_match("node_modules")); - assert!(!ignores.is_match("apps/web/node_modules")); - assert!(!ignores.is_match("packages/cli/node_modules")); - - // Should NOT ignore lock files and package.json - assert!(!ignores.is_match("package.json")); - assert!(!ignores.is_match("apps/web/package.json")); - assert!(!ignores.is_match("package-lock.json")); - assert!(!ignores.is_match("apps/web/yarn.lock")); - assert!(!ignores.is_match("pnpm-lock.yaml")); - assert!(!ignores.is_match("node_modules/react/package.json")); - - Ok(()) - } - - #[test] - fn test_match_ignores_with_file_patterns() -> Result<(), PathGlobError> { - let patterns = vec!["*.log", "**/*.tmp", "!important.log"]; - let ignores = PathGlobSet::new(&patterns)?; - - // Should ignore matching files - assert!(ignores.is_match("debug.log")); - assert!(ignores.is_match("error.log")); - assert!(ignores.is_match("temp/file.tmp")); - assert!(ignores.is_match("deep/nested/path/cache.tmp")); - #[expect(clippy::disallowed_types, reason = "wax::Glob::is_match requires std::path::Path")] - { - assert!(ignores.is_match(String::from("deep/nested/path/cache.tmp"))); - assert!(ignores.is_match(Path::new("deep/nested/path/cache.tmp"))); - } - - // Should NOT ignore negated patterns - assert!(!ignores.is_match("important.log")); - - // Should NOT ignore non-matching files - assert!(!ignores.is_match("file.txt")); - assert!(!ignores.is_match("logs/file.txt")); - - Ok(()) - } - - #[test] - fn test_match_ignores_directory_patterns() -> Result<(), PathGlobError> { - let patterns = vec!["dist/**", "build/**", "!dist/public/**"]; - let ignores = PathGlobSet::new(&patterns)?; - - // Should ignore paths in dist and build - assert!(ignores.is_match("dist/bundle.js")); - assert!(ignores.is_match("dist/assets/style.css")); - assert!(ignores.is_match("build/output.js")); - assert!(ignores.is_match("build/assets/image.png")); - - // Should NOT ignore negated paths - assert!(!ignores.is_match("dist/public/index.html")); - assert!(!ignores.is_match("dist/public/assets/logo.png")); - - // Should NOT ignore paths outside target directories - assert!(!ignores.is_match("src/index.js")); - assert!(!ignores.is_match("public/index.html")); - - Ok(()) - } - - #[test] - fn test_match_ignores_complex_patterns() -> Result<(), PathGlobError> { - let patterns = vec![ - "**/*.test.js", - "**/*.spec.ts", - "**/test/**", - "**/tests/**", - "!**/integration/tests/**", - ]; - let ignores = PathGlobSet::new(&patterns)?; - - // Should ignore test files - assert!(ignores.is_match("src/utils.test.js")); - assert!(ignores.is_match("components/Button.spec.ts")); - assert!(ignores.is_match("lib/test/helper.js")); - assert!(ignores.is_match("src/tests/unit/math.js")); - - // Should NOT ignore negated patterns - assert!(!ignores.is_match("integration/tests/e2e.js")); - assert!(!ignores.is_match("integration/tests/api/user.js")); - - // Should NOT ignore non-test files - assert!(!ignores.is_match("src/index.js")); - assert!(!ignores.is_match("lib/utils.js")); - - Ok(()) - } - - #[test] - fn test_match_ignores_empty_patterns() -> Result<(), PathGlobError> { - let patterns: Vec<&str> = vec![]; - let ignores = PathGlobSet::new(&patterns)?; - - // Should not ignore anything with empty patterns - assert!(!ignores.is_match("node_modules/package.json")); - assert!(!ignores.is_match("src/index.js")); - assert!(!ignores.is_match("dist/bundle.js")); - - Ok(()) - } - - #[test] - fn test_match_ignores_with_wildcards() -> Result<(), PathGlobError> { - let patterns = vec!["*.{js,ts,jsx,tsx}", "!index.js", "!main.ts"]; - let ignores = PathGlobSet::new(&patterns)?; - - // Should ignore matching extensions - assert!(ignores.is_match("utils.js")); - assert!(ignores.is_match("component.tsx")); - assert!(ignores.is_match("service.ts")); - assert!(ignores.is_match("App.jsx")); - - // Should NOT ignore negated files - assert!(!ignores.is_match("index.js")); - assert!(!ignores.is_match("main.ts")); - - // Should NOT ignore other extensions - assert!(!ignores.is_match("styles.css")); - assert!(!ignores.is_match("data.json")); - - Ok(()) - } - - #[test] - fn test_match_ignores_dotfiles() -> Result<(), PathGlobError> { - let patterns = vec![".*", "!.gitignore", "!.env.example"]; - let ignores = PathGlobSet::new(&patterns)?; - - // Should ignore dotfiles - assert!(ignores.is_match(".env")); - assert!(ignores.is_match(".DS_Store")); - assert!(ignores.is_match(".vscode")); - - // Should NOT ignore negated dotfiles - assert!(!ignores.is_match(".gitignore")); - assert!(!ignores.is_match(".env.example")); - - // Should NOT ignore regular files - assert!(!ignores.is_match("README.md")); - assert!(!ignores.is_match("src/index.js")); - - Ok(()) - } - - #[test] - fn test_match_ignores_root_patterns() -> Result<(), PathGlobError> { - // Note: wax doesn't support leading / for root patterns like gitignore - // Using glob patterns that work with wax - let patterns = vec![ - "**/dist", // Match dist at any level - "!dist/public", - "**/node_modules", - ]; - let ignores = PathGlobSet::new(&patterns)?; - // Patterns match at any level - assert!(ignores.is_match("dist")); - assert!(ignores.is_match("src/dist")); // Also matches nested - - // Negation works - assert!(!ignores.is_match("dist/public")); - - // Node_modules patterns - assert!(ignores.is_match("node_modules")); - assert!(ignores.is_match("src/node_modules")); - assert!(ignores.is_match("packages/app/node_modules")); - - Ok(()) - } - - #[test] - fn test_match_ignores_directory_only_patterns() -> Result<(), PathGlobError> { - let patterns = vec![ - "build/**", // Match everything under build - "!build/keep/**", // But not under build/keep - ]; - let ignores = PathGlobSet::new(&patterns)?; - // Directory patterns - assert!(ignores.is_match("build/output.js")); - assert!(ignores.is_match("build/assets/style.css")); - - // Negated directory - assert!(!ignores.is_match("build/keep/important.txt")); - - Ok(()) - } - - #[test] - fn test_match_ignores_mixed_patterns() -> Result<(), PathGlobError> { - let patterns = vec![ - "**/*.log", // Match .log files at any depth - "**/temp/**", - "node_modules/**", - "!**/temp/keep/**", - "!debug.log", - ]; - let ignores = PathGlobSet::new(&patterns)?; - - // Test various patterns together - assert!(ignores.is_match("error.log")); - assert!(ignores.is_match("src/app.log")); - assert!(!ignores.is_match("debug.log")); // Negated - - assert!(ignores.is_match("temp/file.txt")); - assert!(ignores.is_match("src/temp/cache.dat")); - assert!(!ignores.is_match("temp/keep/important.txt")); // Negated - - assert!(ignores.is_match("node_modules/react/index.js")); - assert!(ignores.is_match("node_modules/@types/node/index.d.ts")); - - assert!(!ignores.is_match("src/index.js")); - assert!(!ignores.is_match("package.json")); - - Ok(()) - } - - #[expect( - clippy::disallowed_types, - reason = "tests that is_match accepts various argument types" - )] - #[test] - fn test_generic_api_with_different_types() -> Result<(), PathGlobError> { - use vite_str::Str; - - // Test with Vec<&str> - let patterns_str = vec!["*.log", "!important.log"]; - let ignores_str = PathGlobSet::new(&patterns_str)?; - assert!(ignores_str.is_match("debug.log")); - assert!(!ignores_str.is_match("important.log")); - - // Test with Vec - let patterns_string = vec![String::from("*.tmp"), String::from("!keep.tmp")]; - let ignores_string = PathGlobSet::new(&patterns_string)?; - assert!(ignores_string.is_match("temp.tmp")); - assert!(!ignores_string.is_match("keep.tmp")); - - // Test with Vec - let patterns_vite_str = vec![Str::from("*.rs"), Str::from("!main.rs")]; - let ignores_vite_str = PathGlobSet::new(&patterns_vite_str)?; - assert!(ignores_vite_str.is_match("lib.rs")); - assert!(!ignores_vite_str.is_match("main.rs")); - - // Test with array - let patterns_array = ["build/**", "!build/dist/**"]; - let ignores_array = PathGlobSet::new(&patterns_array)?; - assert!(ignores_array.is_match("build/src/main.js")); - assert!(!ignores_array.is_match("build/dist/bundle.js")); - - // Test with iterator - let patterns_iter = ["*.md", "!README.md"].iter(); - let ignores_iter = PathGlobSet::new(patterns_iter)?; - assert!(ignores_iter.is_match("CHANGELOG.md")); - assert!(!ignores_iter.is_match("README.md")); - - Ok(()) - } - - #[test] - fn test_match_ignores_last_matching_pattern() -> Result<(), PathGlobError> { - // Test that the last matching pattern wins (gitignore semantics) - let patterns = vec![ - "logs/**", // First: ignore everything in logs/ - "!logs/important.log", // Second: don't ignore important.log - "logs/important.log", // Third: ignore important.log again (this wins) - ]; - let ignores = PathGlobSet::new(&patterns)?; - - assert!(ignores.is_match("logs/error.log")); - assert!(ignores.is_match("logs/src/app.log")); - assert!(ignores.is_match("logs/debug.log")); - // The last pattern "logs/important.log" (positive) wins over "!logs/important.log" (negative) - assert!(ignores.is_match("logs/important.log")); // Should be ignored! - - Ok(()) - } -} diff --git a/crates/vite_graph_ser/Cargo.toml b/crates/vite_graph_ser/Cargo.toml deleted file mode 100644 index 26795745a..000000000 --- a/crates/vite_graph_ser/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "vite_graph_ser" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -petgraph = { workspace = true } -serde = { workspace = true, features = ["derive"] } - -[dev-dependencies] -serde_json = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/vite_graph_ser/src/lib.rs b/crates/vite_graph_ser/src/lib.rs deleted file mode 100644 index 04470cb9f..000000000 --- a/crates/vite_graph_ser/src/lib.rs +++ /dev/null @@ -1,139 +0,0 @@ -use petgraph::{ - graph::DiGraph, - visit::{EdgeRef as _, IntoNodeReferences}, -}; -use serde::{Serialize, Serializer}; - -/// Trait for getting a unique key for a node in the graph. -/// This key is used for serializing the graph with `serialize_by_key`. -pub trait GetKey { - type Key<'a>: Serialize + Ord - where - Self: 'a; - /// # Errors - /// Returns an error if the key cannot be computed. - #[expect(clippy::disallowed_types, reason = "trait error type is String for simplicity")] - fn key(&self) -> Result, String>; -} - -#[derive(Serialize)] -#[serde(bound = "N: Serialize")] -struct DiGraphNodeItem<'a, N: GetKey> { - key: N::Key<'a>, - node: &'a N, - neighbors: Vec>, -} - -/// A wrapper around `DiGraph` that serializes nodes by their keys. -/// -/// Only node connectivity is recorded — edge weights are ignored in the output. -#[derive(Serialize)] -#[serde(transparent)] -pub struct SerializeByKey<'a, N: GetKey + Serialize, E, Ix: petgraph::graph::IndexType>( - #[serde(serialize_with = "serialize_by_key")] pub &'a DiGraph, -); - -/// Serialize a directed graph into a map from node keys to their values and neighbors by keys. -/// -/// Keys in nodes and edges are sorted lexicographically. -/// -/// If there are multiple nodes with the same key, or multiple edges between nodes with the same keys, -/// an error will be returned. -/// -/// This is useful for serializing graphs in a stable and human-readable way. -/// -/// # Errors -/// Returns a serialization error if the graph cannot be serialized. -/// -/// # Panics -/// Panics if an edge references a node index not present in the graph. -pub fn serialize_by_key( - graph: &DiGraph, - serializer: S, -) -> Result { - let mut items = Vec::>::with_capacity(graph.node_count()); - for (node_idx, node) in graph.node_references() { - let mut neighbors = Vec::>::new(); - - for edge in graph.edges(node_idx) { - let target_idx = edge.target(); - let target_node = graph.node_weight(target_idx).unwrap(); - neighbors.push(target_node.key().map_err(serde::ser::Error::custom)?); - } - neighbors.sort_unstable(); - items.push(DiGraphNodeItem { - key: node.key().map_err(serde::ser::Error::custom)?, - node, - neighbors, - }); - } - items.sort_unstable_by(|a, b| a.key.cmp(&b.key)); - items.serialize(serializer) -} - -#[cfg(test)] -mod tests { - use petgraph::graph::DiGraph; - - use super::*; - - #[derive(Debug, Clone, Serialize)] - struct TestNode { - id: &'static str, - value: i32, - } - - impl GetKey for TestNode { - type Key<'a> - = &'a str - where - Self: 'a; - - #[expect(clippy::disallowed_types, reason = "trait requires String error type")] - fn key(&self) -> Result, String> { - Ok(self.id) - } - } - - #[derive(Serialize)] - struct GraphWrapper { - #[serde(serialize_with = "serialize_by_key")] - graph: DiGraph, - } - - #[test] - fn test_serialize_graph_happy_path() { - let mut graph = DiGraph::::new(); - let a = graph.add_node(TestNode { id: "a", value: 1 }); - let b = graph.add_node(TestNode { id: "b", value: 2 }); - let c = graph.add_node(TestNode { id: "c", value: 3 }); - - graph.add_edge(a, b, ()); - graph.add_edge(a, c, ()); - graph.add_edge(b, c, ()); - - let json = serde_json::to_value(GraphWrapper { graph }).unwrap(); - assert_eq!( - json, - serde_json::json!({ - "graph": [ - { - "key": "a", - "node": {"id": "a", "value": 1}, - "neighbors": ["b", "c"] - }, - { - "key": "b", - "node": {"id": "b", "value": 2}, - "neighbors": ["c"] - }, - { - "key": "c", - "node": {"id": "c", "value": 3}, - "neighbors": [] - } - ] - }) - ); - } -} diff --git a/crates/vite_path/Cargo.toml b/crates/vite_path/Cargo.toml deleted file mode 100644 index ccabe482c..000000000 --- a/crates/vite_path/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "vite_path" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -wincode = { workspace = true, features = ["derive"] } -diff-struct = { workspace = true } -path-clean = { workspace = true } -ref-cast = { workspace = true } -serde = { workspace = true, features = ["derive", "rc"] } -thiserror = { workspace = true } -ts-rs = { workspace = true, optional = true } -vite_str = { workspace = true } - -[target.'cfg(target_os = "windows")'.dependencies] -os_str_bytes = { workspace = true } - -[features] -absolute-redaction = [] -ts-rs = ["dep:ts-rs", "vite_str/ts-rs"] - -[lints] -workspace = true - -[dev-dependencies] -assert2 = { workspace = true } - -[lib] -doctest = false diff --git a/crates/vite_path/README.md b/crates/vite_path/README.md deleted file mode 100644 index 7be13b300..000000000 --- a/crates/vite_path/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# vite_path - -Provides path typed with its relativity: `AbsolutePath(Buf)` and `RelativePath(Buf)`, and safe methods to convert between them (for example, `AbsolutePath::join(RelativePath)` produces `AbsolutePathBuf`). diff --git a/crates/vite_path/src/absolute/mod.rs b/crates/vite_path/src/absolute/mod.rs deleted file mode 100644 index 04086add6..000000000 --- a/crates/vite_path/src/absolute/mod.rs +++ /dev/null @@ -1,458 +0,0 @@ -#[cfg(feature = "absolute-redaction")] -pub mod redaction; - -use std::{ - ffi::OsStr, - fmt::{Debug, Display}, - hash::Hash, - ops::Deref, - path::{Path, PathBuf}, - sync::Arc, -}; - -use ref_cast::{RefCastCustom, ref_cast_custom}; -use serde::Serialize; - -use crate::relative::{FromPathError, InvalidPathDataError, RelativePathBuf}; - -/// A path that is guaranteed to be absolute -#[derive(RefCastCustom, PartialEq, Eq, PartialOrd, Ord)] -#[repr(transparent)] -pub struct AbsolutePath(Path); -impl AsRef for AbsolutePath { - fn as_ref(&self) -> &Self { - self - } -} - -impl Debug for AbsolutePath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&self.0, f) - } -} - -impl Display for AbsolutePath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0.display(), f) - } -} - -impl Serialize for AbsolutePath { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - #[cfg(feature = "absolute-redaction")] - if let Some(redacted_path) = self.try_redact().map_err(serde::ser::Error::custom)? { - return serializer.serialize_str(&redacted_path); - } - self.as_path().serialize(serializer) - } -} - -impl PartialEq for AbsolutePath { - fn eq(&self, other: &AbsolutePathBuf) -> bool { - self.0 == other.0 - } -} -impl PartialEq for &AbsolutePath { - fn eq(&self, other: &AbsolutePathBuf) -> bool { - self.0 == other.0 - } -} - -impl Hash for AbsolutePath { - fn hash(&self, state: &mut H) { - self.0.hash(state); - } -} - -impl From<&AbsolutePath> for Arc { - fn from(path: &AbsolutePath) -> Self { - let arc: Arc = path.0.into(); - let arc_raw = Arc::into_raw(arc) as *const AbsolutePath; - // SAFETY: AbsolutePath is #[repr(transparent)] over Path, so the pointer cast - // from Arc to Arc preserves layout. The source path is - // already verified absolute since it comes from an &AbsolutePath. - unsafe { Self::from_raw(arc_raw) } - } -} - -impl From<&AbsolutePath> for Box { - fn from(path: &AbsolutePath) -> Self { - let path_box: Box = path.0.into(); - let path_box_raw = Box::into_raw(path_box) as *mut AbsolutePath; - // SAFETY: AbsolutePath is #[repr(transparent)] over Path, so the pointer cast - // from Box to Box preserves layout. The source path is - // already verified absolute since it comes from an &AbsolutePath. - unsafe { Self::from_raw(path_box_raw) } - } -} - -impl AbsolutePath { - /// Creates a [`AbsolutePath`] if the give path is absolute. - pub fn new + ?Sized>(path: &P) -> Option<&Self> { - let path = path.as_ref(); - if path.is_absolute() { - // SAFETY: We just verified that path.is_absolute() is true. - Some(unsafe { Self::assume_absolute(path) }) - } else { - None - } - } - - #[cfg(feature = "absolute-redaction")] - #[expect( - clippy::disallowed_types, - clippy::disallowed_macros, - reason = "try_redact returns std String and uses std format!" - )] - fn try_redact(&self) -> Result, String> { - use redaction::REDACTION_PREFIX; - - if let Some(redaction_prefix) = REDACTION_PREFIX - .with(|redaction_prefix| redaction_prefix.borrow().as_ref().map(Arc::clone)) - { - match self.strip_prefix(redaction_prefix) { - Ok(Some(stripped_path)) => { - return Ok(Some(format!("/{}", stripped_path.as_str()))); - } - Err(strip_error) => { - return Err(format!( - "Failed to redact absolute path '{}': {}", - self.as_path().display(), - strip_error - )); - } - Ok(None) => { /* continue to serialize full path */ } - } - } - Ok(None) - } - - #[ref_cast_custom] - pub(crate) unsafe fn assume_absolute(abs_path: &Path) -> &Self; - - /// Gets the underlying [`Path`] - #[must_use] - pub const fn as_path(&self) -> &Path { - &self.0 - } - - /// Converts `self` to an owned [`AbsolutePathBuf`]. - #[must_use] - pub fn to_absolute_path_buf(&self) -> AbsolutePathBuf { - // SAFETY: self is already an AbsolutePath, so its path data is absolute. - unsafe { AbsolutePathBuf::assume_absolute(self.0.to_path_buf()) } - } - - /// Returns a path that, when joined onto base, yields self. - /// - /// On Windows, path namespace prefixes (`\\?\`, `\\.\`, and `\??\`) are - /// ignored before matching. In that case the returned path round-trips - /// against the prefix-normalized paths rather than necessarily preserving - /// the original namespace prefix. - /// - /// If `base` is not a prefix of `self`, returns [`None`]. - /// - /// If the stripped path is not a valid `RelativePath`. Returns an error with the reason and the stripped path. - /// - /// # Errors - /// - /// Returns an error if the stripped path contains invalid UTF-8 or other path data issues. - pub fn strip_prefix>( - &self, - base: P, - ) -> Result, StripPrefixError<'_>> { - let base = base.as_ref(); - let Ok(stripped_path) = - crate::strip_path_prefix(self.as_path().as_os_str(), base.as_path().as_os_str()) - else { - return Ok(None); - }; - match RelativePathBuf::new(stripped_path) { - Ok(relative_path) => Ok(Some(relative_path)), - Err(FromPathError::NonRelative) => { - unreachable!("stripped path should always be relative") - } - Err(FromPathError::InvalidPathData(invalid_path_data_error)) => { - Err(StripPrefixError { stripped_path, invalid_path_data_error }) - } - } - } - - /// Creates an owned [`AbsolutePathBuf`] with `path` adjoined to `self`. - pub fn join>(&self, path: P) -> AbsolutePathBuf { - let mut absolute_path_buf = self.to_absolute_path_buf(); - absolute_path_buf.push(path); - absolute_path_buf - } - - /// Returns the parent directory of `self`, or `None` if `self` is the root. - #[must_use] - pub fn parent(&self) -> Option<&Self> { - let parent_path = self.0.parent()?; - // SAFETY: The parent of an absolute path is always absolute. - Some(unsafe { Self::assume_absolute(parent_path) }) - } - - /// Creates an owned [`AbsolutePathBuf`] like `self` but with the extension added. - pub fn with_extension>(&self, extension: S) -> AbsolutePathBuf { - let path = self.0.with_extension(extension); - // SAFETY: Changing the extension of an absolute path preserves its absoluteness. - unsafe { AbsolutePathBuf::assume_absolute(path) } - } - - /// Returns true if `self` ends with `path`. - pub fn ends_with>(&self, path: P) -> bool { - self.0.ends_with(path.as_ref()) - } - - /// Lexically normalizes the path by resolving `.` and `..` components - /// without accessing the filesystem. - /// - /// **Symlink limitation**: Because this is purely lexical, it can produce - /// incorrect results when symlinks are involved. For example, if - /// `/a/link` is a symlink to `/x/y`, then cleaning `/a/link/../c` - /// yields `/a/c` instead of the correct `/x/c`. Use - /// [`std::fs::canonicalize`] when you need symlink-correct resolution. - #[must_use] - pub fn clean(&self) -> AbsolutePathBuf { - use path_clean::PathClean as _; - - let cleaned = self.0.clean(); - // SAFETY: Lexical cleaning of an absolute path preserves absoluteness — - // it only removes `.`/`..` components and redundant separators. - unsafe { AbsolutePathBuf::assume_absolute(cleaned) } - } -} - -/// An Error returned from [`AbsolutePath::strip_prefix`] if the stripped path is not a valid `RelativePath` -#[derive(thiserror::Error, Debug)] -pub struct StripPrefixError<'a> { - pub stripped_path: &'a Path, - #[source] - pub invalid_path_data_error: InvalidPathDataError, -} - -impl Display for StripPrefixError<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!( - "{}: {}", - self.stripped_path.display(), - self.invalid_path_data_error - )) - } -} - -impl AsRef for AbsolutePath { - fn as_ref(&self) -> &Path { - self.as_path() - } -} - -/// An owned path buf that is guaranteed to be absolute -#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct AbsolutePathBuf(PathBuf); - -impl From for Arc { - fn from(path: AbsolutePathBuf) -> Self { - let arc: Arc = path.0.into(); - let arc_raw = Arc::into_raw(arc) as *const AbsolutePath; - // SAFETY: AbsolutePath is #[repr(transparent)] over Path, so the pointer cast - // from Arc to Arc preserves layout. The source path is - // already verified absolute since it comes from an AbsolutePathBuf. - unsafe { Self::from_raw(arc_raw) } - } -} - -impl AbsolutePathBuf { - #[must_use] - pub fn new(path: PathBuf) -> Option { - if path.is_absolute() { - // SAFETY: We just verified that path.is_absolute() is true. - Some(unsafe { Self::assume_absolute(path) }) - } else { - None - } - } - - /// # Safety - /// The caller must ensure that `abs_path` is an absolute path. - #[must_use] - pub const unsafe fn assume_absolute(abs_path: PathBuf) -> Self { - Self(abs_path) - } - - #[must_use] - pub fn as_absolute_path(&self) -> &AbsolutePath { - // SAFETY: self is an AbsolutePathBuf, so its inner PathBuf is guaranteed absolute. - unsafe { AbsolutePath::assume_absolute(self.0.as_path()) } - } - - /// Extends `self` with `path`. - /// - /// `path` replaces `self` only when `path` is absolute. Either way, the resulting `self` is always absolute. - pub fn push>(&mut self, path: P) { - self.0.push(path.as_ref()); - } - - #[must_use] - pub fn into_path_buf(self) -> PathBuf { - self.0 - } -} - -impl PartialEq for AbsolutePathBuf { - fn eq(&self, other: &AbsolutePath) -> bool { - self.as_absolute_path().eq(other) - } -} -impl PartialEq<&AbsolutePath> for AbsolutePathBuf { - fn eq(&self, other: &&AbsolutePath) -> bool { - self.as_absolute_path().eq(*other) - } -} - -impl AsRef for AbsolutePathBuf { - fn as_ref(&self) -> &Path { - self.as_absolute_path().as_path() - } -} -impl AsRef for AbsolutePathBuf { - fn as_ref(&self) -> &AbsolutePath { - self.as_absolute_path() - } -} - -impl Deref for AbsolutePathBuf { - type Target = AbsolutePath; - - fn deref(&self) -> &Self::Target { - self.as_absolute_path() - } -} - -#[cfg(test)] -mod tests { - use std::path::Path; - - use super::*; - - #[test] - fn non_absolute() { - assert!(AbsolutePath::new(Path::new("foo/bar")).is_none()); - } - - #[test] - fn strip_prefix() { - let abs_path = AbsolutePath::new(Path::new(if cfg!(windows) { - "C:\\Users\\foo\\bar" - } else { - "/home/foo/bar" - })) - .unwrap(); - - let prefix = - AbsolutePath::new(Path::new(if cfg!(windows) { "C:\\Users" } else { "/home" })) - .unwrap(); - - let rel_path = abs_path.strip_prefix(prefix).unwrap().unwrap(); - assert_eq!(rel_path.as_str(), "foo/bar"); - - assert_eq!(prefix.join(&rel_path), abs_path); - let mut pushed_path = prefix.to_absolute_path_buf(); - pushed_path.push(rel_path); - - assert_eq!(pushed_path, abs_path); - } - - #[test] - fn strip_prefix_trailing_slash() { - let abs_path = AbsolutePath::new(Path::new(if cfg!(windows) { - "C:\\Users\\foo\\bar" - } else { - "/home/foo/bar" - })) - .unwrap(); - - let prefix = - AbsolutePath::new(Path::new(if cfg!(windows) { "C:\\Users\\" } else { "/home//" })) - .unwrap(); - - let rel_path = abs_path.strip_prefix(prefix).unwrap().unwrap(); - assert_eq!(rel_path.as_str(), "foo/bar"); - } - - #[test] - fn strip_prefix_not_found() { - let abs_path = AbsolutePath::new(Path::new(if cfg!(windows) { - "C:\\Users\\foo\\bar" - } else { - "/home/foo/bar" - })) - .unwrap(); - - let prefix = AbsolutePath::new(Path::new(if cfg!(windows) { - "C:\\Users\\barz" - } else { - "/home/baz" - })) - .unwrap(); - - let rel_path = abs_path.strip_prefix(prefix).unwrap(); - assert!(rel_path.is_none()); - } - - #[cfg(windows)] - #[test] - fn strip_prefix_ignores_windows_namespace_prefixes() { - let abs_path = AbsolutePath::new(Path::new(r"\\?\C:\Users\foo\bar")).unwrap(); - let prefix = AbsolutePath::new(Path::new(r"C:\Users")).unwrap(); - - let rel_path = abs_path.strip_prefix(prefix).unwrap().unwrap(); - - assert_eq!(rel_path.as_str(), "foo/bar"); - } - - #[cfg(unix)] - #[test] - fn strip_prefix_invalid_relative() { - use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; - - use assert2::assert; - - let mut abs_path = b"/home/".to_vec(); - abs_path.push(0xC0); - let abs_path = AbsolutePath::new(Path::new(OsStr::from_bytes(&abs_path))).unwrap(); - - let prefix = AbsolutePath::new(Path::new("/home")).unwrap(); - assert!(let Err(err) = abs_path.strip_prefix(prefix)); - - assert_eq!(err.stripped_path.as_os_str().as_bytes(), &[0xC0]); - assert!(let InvalidPathDataError::NonUtf8 = err.invalid_path_data_error); - } - - #[test] - #[cfg(not(windows))] - fn with_extension() { - let abs_path = AbsolutePath::new(Path::new("/home/foo/bar")).unwrap(); - let abs_path_with_extension = abs_path.with_extension("txt"); - assert_eq!(abs_path_with_extension.as_path().as_os_str(), "/home/foo/bar.txt"); - let abs_path_with_extension = abs_path.with_extension("txt").with_extension("tgz"); - assert_eq!(abs_path_with_extension.as_path().as_os_str(), "/home/foo/bar.tgz"); - // abs_path is not changed - assert_eq!(abs_path.as_path().as_os_str(), "/home/foo/bar"); - } - #[test] - #[cfg(windows)] - fn with_extension() { - let abs_path = AbsolutePath::new(Path::new("C:\\home\\foo\\bar")).unwrap(); - let abs_path_with_extension = abs_path.with_extension("txt"); - assert_eq!(abs_path_with_extension.as_path().as_os_str(), "C:\\home\\foo\\bar.txt"); - let abs_path_with_extension = abs_path.with_extension("txt").with_extension("tgz"); - assert_eq!(abs_path_with_extension.as_path().as_os_str(), "C:\\home\\foo\\bar.tgz"); - // abs_path is not changed - assert_eq!(abs_path.as_path().as_os_str(), "C:\\home\\foo\\bar"); - } -} diff --git a/crates/vite_path/src/absolute/redaction.rs b/crates/vite_path/src/absolute/redaction.rs deleted file mode 100644 index ee4d8dfc1..000000000 --- a/crates/vite_path/src/absolute/redaction.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::sync::Arc; - -use super::AbsolutePath; - -thread_local! { - pub(crate) static REDACTION_PREFIX: std::cell::RefCell>> = const { std::cell::RefCell::new(None) }; -} - -#[derive(Debug)] -pub struct RedactionGuard(()); - -impl Drop for RedactionGuard { - fn drop(&mut self) { - REDACTION_PREFIX.set(None); - } -} - -/// # Panics -/// Panics if a `RedactionGuard` is already active. -#[must_use] -pub fn redact_absolute_paths(prefix: &Arc) -> RedactionGuard { - REDACTION_PREFIX.with(|redaction_prefix| { - let mut redaction_prefix = redaction_prefix.borrow_mut(); - assert!(redaction_prefix.is_none(), "RedactionGuard already active"); - *redaction_prefix = Some(Arc::clone(prefix)); - }); - RedactionGuard(()) -} diff --git a/crates/vite_path/src/lib.rs b/crates/vite_path/src/lib.rs deleted file mode 100644 index e5d01fb73..000000000 --- a/crates/vite_path/src/lib.rs +++ /dev/null @@ -1,127 +0,0 @@ -#![expect(clippy::disallowed_types, reason = "vite_path needs to use std path types internally")] - -pub mod absolute; -pub mod relative; - -use std::{ - ffi::OsStr, - io, - path::{Path, StripPrefixError}, -}; - -#[cfg(feature = "absolute-redaction")] -pub use absolute::redaction; -pub use absolute::{AbsolutePath, AbsolutePathBuf}; -pub use relative::{RelativePath, RelativePathBuf}; - -/// Returns the current working directory as an absolute path. -/// -/// # Errors -/// -/// Returns an error if the current directory cannot be determined, which can occur if: -/// - The current directory has been removed -/// - The current directory is not accessible -/// -/// # Panics -/// -/// Panics if `std::env::current_dir()` returns a non-absolute path, which should never happen in practice. -pub fn current_dir() -> io::Result { - #[expect( - clippy::disallowed_methods, - reason = "std current_dir needed to get the current working directory as an absolute path" - )] - let cwd = std::env::current_dir()?; - // `std::env::current_dir` should always return a absolute path but its documentation doesn't guarantee that. - // Do a runtime check just in case. - Ok(AbsolutePathBuf::new(cwd).unwrap()) -} - -/// Strips `base` from `path`, after normalizing Windows path namespace prefixes. -/// -/// On Windows, the `\\?\`, `\\.\`, and `\??\` prefixes are ignored before -/// matching. On other platforms this is equivalent to [`Path::strip_prefix`]. -/// -/// This is purely lexical and does not access the filesystem. -/// -/// # Errors -/// -/// Returns an error if `base` is not a path prefix of `path` after applying the -/// platform-specific prefix normalization above. -pub fn strip_path_prefix<'a>(path: &'a OsStr, base: &OsStr) -> Result<&'a Path, StripPrefixError> { - let path = strip_windows_path_prefix(path); - let base = strip_windows_path_prefix(base); - Path::new(path).strip_prefix(base) -} - -/// Strip the `\\?\`, `\\.\`, `\??\` prefix from a Windows path, if present. -/// Does nothing on non-Windows platforms. -/// -/// `\\?\` and `\\.\` are used to enable long paths and access to device paths. -/// `\??\` is used in Nt* calls. -/// The resulting path is not necessarily valid or points to the same location, -/// but it is enough for lexical path-prefix comparisons. -#[cfg_attr( - not(windows), - expect( - clippy::missing_const_for_fn, - reason = "uses non-const for loop and strip_prefix on Windows" - ) -)] -fn strip_windows_path_prefix(p: &OsStr) -> &OsStr { - #[cfg(windows)] - { - use os_str_bytes::OsStrBytesExt as _; - - for prefix in [r"\\?\", r"\\.\", r"\??\"] { - if let Some(stripped) = p.strip_prefix(prefix) { - return stripped; - } - } - p - } - #[cfg(not(windows))] - { - p - } -} - -#[cfg(test)] -mod tests { - use std::ffi::OsStr; - - use super::*; - - #[test] - fn strip_path_prefix_strips_base() { - let path = - OsStr::new(if cfg!(windows) { r"C:\repo\pkg\file.txt" } else { "/repo/pkg/file.txt" }); - let base = OsStr::new(if cfg!(windows) { r"C:\repo" } else { "/repo" }); - - let stripped = strip_path_prefix(path, base).unwrap(); - - assert_eq!( - stripped, - Path::new(if cfg!(windows) { r"pkg\file.txt" } else { "pkg/file.txt" }) - ); - } - - #[test] - fn strip_path_prefix_reports_mismatch() { - let path = - OsStr::new(if cfg!(windows) { r"C:\repo\pkg\file.txt" } else { "/repo/pkg/file.txt" }); - let base = OsStr::new(if cfg!(windows) { r"C:\other" } else { "/other" }); - - assert!(strip_path_prefix(path, base).is_err()); - } - - #[cfg(windows)] - #[test] - fn strip_path_prefix_ignores_windows_namespace_prefixes() { - let path = OsStr::new(r"\??\C:\repo\pkg\file.txt"); - let base = OsStr::new(r"\\?\C:\repo"); - - let stripped = strip_path_prefix(path, base).unwrap(); - - assert_eq!(stripped, Path::new(r"pkg\file.txt")); - } -} diff --git a/crates/vite_path/src/relative.rs b/crates/vite_path/src/relative.rs deleted file mode 100644 index 46aedcbf4..000000000 --- a/crates/vite_path/src/relative.rs +++ /dev/null @@ -1,474 +0,0 @@ -//! Provides `RelativePath(Buf)`, a relative path type with additional guarantees to make it portable. -//! -//! ## Why not use crate `relative-path` -//! `relative-path::RelativePath` allows backslashes in its components, which is valid in unix systems but not portable to Windows. - -use std::{ - borrow::Borrow, - fmt::Display, - mem::MaybeUninit, - ops::Deref, - path::{Component, Path}, -}; - -use diff::Diff; -use ref_cast::{RefCastCustom, ref_cast_custom}; -use serde::{Deserialize, Serialize}; -use vite_str::Str; -use wincode::{SchemaRead, SchemaWrite, config::Config, error::ReadResult, io::Reader}; - -/// A relative path with additional guarantees to make it portable: -/// -/// - It is valid utf-8 -/// - It uses slashes `/` as separators, not backslashes `\` -/// - There's no backslash `\` in components (this is valid in unix systems but not portable to Windows) -#[derive(RefCastCustom, PartialEq, Eq, Hash)] -#[repr(transparent)] -pub struct RelativePath(str); -impl AsRef for RelativePath { - fn as_ref(&self) -> &Self { - self - } -} - -impl AsRef for RelativePath { - fn as_ref(&self) -> &Path { - self.as_path() - } -} - -impl RelativePath { - #[ref_cast_custom] - unsafe fn assume_portable(path: &str) -> &Self; - - #[must_use] - pub const fn as_str(&self) -> &str { - &self.0 - } - - #[must_use] - pub fn as_path(&self) -> &Path { - Path::new(self.as_str()) - } - - #[must_use] - pub fn to_relative_path_buf(&self) -> RelativePathBuf { - RelativePathBuf(self.0.into()) - } - - /// Creates an owned [`RelativePathBuf`] with `rel_path` adjoined to `self`. - pub fn join>(&self, rel_path: P) -> RelativePathBuf { - let mut relative_path_buf = self.to_relative_path_buf(); - relative_path_buf.push(rel_path); - relative_path_buf - } - - /// Lexically normalizes the path by resolving `..` components without - /// accessing the filesystem. (`.` components are already stripped by - /// [`RelativePathBuf::new`].) - /// - /// **Symlink limitation**: Because this is purely lexical, it can produce - /// incorrect results when symlinks are involved. For example, if - /// `a/link` is a symlink to `x/y`, then cleaning `a/link/../c` - /// yields `a/c` instead of the correct `x/c`. Use - /// [`std::fs::canonicalize`] when you need symlink-correct resolution. - /// - /// # Errors - /// - /// Returns an error if the cleaned path is no longer a valid relative path. - /// This can happen on Windows when malformed inputs such as `foo/C:/bar` - /// are cleaned into drive-prefixed paths. - pub fn clean(&self) -> Result { - use path_clean::PathClean as _; - - let cleaned = self.as_path().clean(); - RelativePathBuf::new(cleaned) - } - - /// Returns a path that, when joined onto `base`, yields `self`. - /// - /// If `base` is not a prefix of `self`, returns [`None`]. - /// - /// # Panics - /// - /// Panics if the stripped path contains non-UTF-8 characters, which should not happen for valid `RelativePath` instances. - pub fn strip_prefix>(&self, base: P) -> Option<&Self> { - let stripped_path = Path::new(self.as_str()).strip_prefix(base.as_ref().as_path()).ok()?; - // SAFETY: The stripped result of a portable RelativePath is still portable: - // it remains valid UTF-8 and contains no backslash separators. - Some(unsafe { Self::assume_portable(stripped_path.to_str().unwrap()) }) - } -} - -/// A owned relative path buf with the same guarantees as `RelativePath` -#[derive( - Debug, SchemaWrite, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, Default, -)] -#[expect( - clippy::unsafe_derive_deserialize, - reason = "unsafe in SchemaRead impl validates portability invariants" -)] -pub struct RelativePathBuf(Str); - -// SAFETY: Delegates to `Str`'s SchemaRead impl; dst is initialized only on Ok. -unsafe impl<'de, C: Config> SchemaRead<'de, C> for RelativePathBuf { - type Dst = Self; - - fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { - let path_str = >::get(&mut reader)?; - Self::new(path_str.as_str()).map_or( - Err(wincode::error::ReadError::Custom("invalid relative path in encoded data")), - |path| { - dst.write(path); - Ok(()) - }, - ) - } -} - -impl AsRef for RelativePathBuf { - fn as_ref(&self) -> &Path { - self.as_path() - } -} - -impl Display for RelativePathBuf { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl PartialEq for RelativePathBuf { - fn eq(&self, other: &RelativePath) -> bool { - self.as_relative_path().eq(other) - } -} -impl PartialEq<&RelativePath> for RelativePathBuf { - fn eq(&self, other: &&RelativePath) -> bool { - self.as_relative_path().eq(other) - } -} - -impl Diff for RelativePathBuf { - type Repr = Option; - - fn diff(&self, other: &Self) -> Self::Repr { - self.0.diff(&other.0) - } - - fn apply(&mut self, diff: &Self::Repr) { - self.0.apply(diff); - } - - fn identity() -> Self { - Self(Str::identity()) - } -} - -impl RelativePathBuf { - #[must_use] - pub fn empty() -> Self { - Self("".into()) - } - - /// Extends `self` with `path`. - /// - /// Unlike [`std::path::PathBuf::push`], `self` and `path` are both always relative, - /// so `self` can only be appended, not replaced - pub fn push>(&mut self, rel_path: P) { - let rel_path_str = rel_path.as_ref().as_str(); - if rel_path_str.is_empty() { - return; - } - if !self.as_str().is_empty() { - self.0.push('/'); - } - self.0.push_str(rel_path_str); - } - - /// Creates a new `RelativePathBuf` from a `Path`. - /// - /// This function normalizes the path by: - /// - Removing `.` components - /// - Replacing backslash `\` separators with slashes `/` (on Windows) - /// - /// # Errors - /// Returns an error if the path is not relative or contains invalid data that makes it non-portable. - pub fn new>(path: P) -> Result { - let path = path.as_ref(); - let mut path_str = Str::with_capacity(path.as_os_str().len()); - for component in path.components() { - match component { - Component::Prefix(_) | Component::RootDir => { - return Err(FromPathError::NonRelative); - } - Component::CurDir => { - // normalize dots - continue; - } - Component::ParentDir => { - path_str.push_str(".."); - } - Component::Normal(os_str) => { - let Some(component) = os_str.to_str() else { - return Err(InvalidPathDataError::NonUtf8.into()); - }; - if component.contains('\\') { - return Err(InvalidPathDataError::BackslashInComponent.into()); - } - path_str.push_str(component); - } - } - path_str.push('/'); - } - path_str.pop(); // remove last pushed '/' - Ok(Self(path_str)) - } - - #[must_use] - pub fn as_relative_path(&self) -> &RelativePath { - // SAFETY: RelativePathBuf's constructors (new, SchemaRead) validate portability - // invariants, so the inner string is guaranteed to be a valid portable path. - unsafe { RelativePath::assume_portable(&self.0) } - } -} - -impl TryFrom<&Path> for RelativePathBuf { - type Error = FromPathError; - - fn try_from(path: &Path) -> Result { - Self::new(path) - } -} - -impl TryFrom<&str> for RelativePathBuf { - type Error = FromPathError; - - fn try_from(path: &str) -> Result { - let path = Path::new(path); - Self::try_from(path) - } -} - -impl AsRef for RelativePathBuf { - fn as_ref(&self) -> &RelativePath { - self.as_relative_path() - } -} - -impl Deref for RelativePathBuf { - type Target = RelativePath; - - fn deref(&self) -> &Self::Target { - self.as_relative_path() - } -} - -impl Borrow for RelativePathBuf { - fn borrow(&self) -> &RelativePath { - self.as_relative_path() - } -} -impl ToOwned for RelativePath { - type Owned = RelativePathBuf; - - fn to_owned(&self) -> Self::Owned { - self.to_relative_path_buf() - } -} - -/// Error when converting a path containing invalid data to `RelativePathbuf` -#[derive(thiserror::Error, Debug)] -pub enum InvalidPathDataError { - /// One of the components contains non-utf8 data. - #[error("path is not portable because contains non-utf8 data")] - NonUtf8, - /// One of the components contains backslashes `\`. - /// - /// This is valid in unix systems but not portable to Windows - #[error("path is not portable because it contains backslash ('\\') in its components")] - BackslashInComponent, -} - -/// Error when converting a `Path` to `RelativePathbuf` -#[derive(thiserror::Error, Debug)] -pub enum FromPathError { - #[error("path is not relative")] - NonRelative, - #[error("{0}")] - InvalidPathData(#[from] InvalidPathDataError), -} - -#[cfg(feature = "ts-rs")] -mod ts_impl { - use ts_rs::TS; - - use super::RelativePathBuf; - - #[expect(clippy::disallowed_types, reason = "ts_rs::TS trait requires returning std String")] - impl TS for RelativePathBuf { - type OptionInnerType = Self; - type WithoutGenerics = Self; - - fn name(_cfg: &ts_rs::Config) -> String { - "string".to_owned() - } - - fn inline(_cfg: &ts_rs::Config) -> String { - "string".to_owned() - } - - fn inline_flattened(_cfg: &ts_rs::Config) -> String { - panic!("RelativePathBuf cannot be flattened") - } - - fn decl(_cfg: &ts_rs::Config) -> String { - panic!("RelativePathBuf is a primitive type") - } - - fn decl_concrete(_cfg: &ts_rs::Config) -> String { - panic!("RelativePathBuf is a primitive type") - } - } -} - -#[cfg(test)] -mod tests { - - #[cfg(windows)] - use std::os::windows::ffi::OsStringExt as _; - - use assert2::assert; - - use super::*; - - #[test] - fn non_relative() { - assert!( - let Err(FromPathError::NonRelative) = - RelativePathBuf::new(if cfg!(windows) { "C:\\Users" } else { "/home" }) - ); - } - - #[cfg(unix)] - #[test] - fn non_utf8() { - use std::{ffi::OsStr, os::unix::ffi::OsStrExt as _}; - - let non_utf8_os_str = OsStr::from_bytes(&[0xC0]); - assert!( - let Err(FromPathError::InvalidPathData(InvalidPathDataError::NonUtf8)) = - RelativePathBuf::new(non_utf8_os_str), - ); - } - - #[cfg(windows)] - #[test] - fn non_utf8() { - use std::ffi::OsString; - // ill-formed UTF-16: XY - let non_utf8_path = OsString::from_wide(&[0x0058, 0xD800, 0x0059]); - assert!( - let Err(FromPathError::InvalidPathData(InvalidPathDataError::NonUtf8)) = - RelativePathBuf::new(non_utf8_path), - ); - } - - #[cfg(unix)] - #[test] - fn backslash_in_component() { - assert!( - let Err(FromPathError::InvalidPathData(InvalidPathDataError::BackslashInComponent)) = - RelativePathBuf::new("foo\\bar") - ); - } - - #[cfg(windows)] - #[test] - fn backslash_in_component() { - assert!(let Ok(path) = RelativePathBuf::new("foo\\bar")); - assert_eq!(path.as_str(), "foo/bar"); - } - - #[cfg(windows)] - #[test] - fn replace_backslash_separators() { - let rel_path = RelativePathBuf::new("foo\\bar").unwrap(); - assert_eq!(rel_path.as_str(), "foo/bar"); - } - - #[test] - fn normalize_dots() { - let rel_path = RelativePathBuf::new("./foo/./bar/.").unwrap(); - assert_eq!(rel_path.as_str(), "foo/bar"); - } - - #[test] - fn normalize_trailing_slashes() { - let rel_path = RelativePathBuf::new("foo/bar//").unwrap(); - assert_eq!(rel_path.as_str(), "foo/bar"); - } - #[test] - fn preserve_double_dots() { - let rel_path = RelativePathBuf::new("../foo/../bar/..").unwrap(); - assert_eq!(rel_path.as_str(), "../foo/../bar/.."); - } - - #[test] - fn push() { - let mut rel_path = RelativePathBuf::new("foo/bar").unwrap(); - rel_path.push(RelativePathBuf::new("baz").unwrap()); - assert_eq!(rel_path.as_str(), "foo/bar/baz"); - } - - #[test] - fn push_empty() { - let mut rel_path = RelativePathBuf::new("foo/bar").unwrap(); - rel_path.push(RelativePathBuf::new("").unwrap()); - assert_eq!(rel_path.as_str(), "foo/bar"); - } - - #[test] - fn join() { - let rel_path = RelativePathBuf::new("foo/bar").unwrap(); - let joined_path = rel_path.as_relative_path().join(RelativePathBuf::new("baz").unwrap()); - assert_eq!(joined_path.as_str(), "foo/bar/baz"); - } - - #[test] - fn join_empty() { - let rel_path = RelativePathBuf::new("").unwrap(); - let joined_path = rel_path.as_relative_path().join(RelativePathBuf::new("baz").unwrap()); - assert_eq!(joined_path.as_str(), "baz"); - } - - #[test] - fn clean() { - let rel_path = RelativePathBuf::new("../foo/../bar").unwrap(); - let cleaned = rel_path.clean().unwrap(); - assert_eq!(cleaned.as_str(), "../bar"); - } - - #[cfg(windows)] - #[test] - fn clean_malformed_drive_path() { - let rel_path = RelativePathBuf::new(r"foo\C:\bar").unwrap(); - assert!(let Err(FromPathError::NonRelative) = rel_path.clean()); - } - - #[test] - fn strip_prefix() { - let rel_path = RelativePathBuf::new("foo/bar/baz").unwrap(); - let prefix = RelativePathBuf::new("foo").unwrap(); - let stripped_path = rel_path.strip_prefix(prefix).unwrap(); - assert_eq!(stripped_path.as_str(), "bar/baz"); - } - - #[test] - fn encode_decode() { - let rel_path = RelativePathBuf::new("foo/bar").unwrap(); - let encoded = wincode::serialize(&rel_path).unwrap(); - let decoded: RelativePathBuf = wincode::deserialize(&encoded).unwrap(); - assert_eq!(rel_path, decoded); - } -} diff --git a/crates/vite_powershell/Cargo.toml b/crates/vite_powershell/Cargo.toml deleted file mode 100644 index b656ee7dd..000000000 --- a/crates/vite_powershell/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "vite_powershell" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -vite_path = { workspace = true } -which = { workspace = true } - -[dev-dependencies] -tempfile = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/vite_powershell/src/lib.rs b/crates/vite_powershell/src/lib.rs deleted file mode 100644 index 2819cdf35..000000000 --- a/crates/vite_powershell/src/lib.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! Windows-specific helpers for routing `.cmd` invocations through -//! `PowerShell` so spawning never goes through `cmd.exe`. -//! -//! Running a `.cmd` from any shell makes `cmd.exe` prompt "Terminate batch -//! job (Y/N)?" on Ctrl+C, which leaves the terminal corrupt. Routing -//! through `PowerShell` against the sibling `.ps1` shim sidesteps the prompt -//! and lets Ctrl+C propagate cleanly. -//! -//! This crate carries only the platform-shared primitives (the -//! `PowerShell` host lookup, the fixed argument prefix, the -//! sibling-`.ps1` discovery, and the stdin-TTY gate). Higher-level wrappers in -//! `vite_task_plan::ps1_shim` (cwd-relative arg rewrite, scoped to -//! `node_modules/.bin`) and `vite_command::ps1_shim` (absolute-path -//! arg rewrite, applied to any `.cmd`) compose these primitives with -//! their own scope rules and return-type conventions. -//! -//! See and -//! . - -use std::sync::Arc; - -use vite_path::{AbsolutePath, AbsolutePathBuf}; - -/// Fixed arguments prepended before the `.ps1` path. `-NoProfile`/`-NoLogo` -/// skip user profile loading; `-ExecutionPolicy Bypass` allows running the -/// unsigned shims that npm/pnpm/yarn install. -pub const POWERSHELL_PREFIX: &[&str] = - &["-NoProfile", "-NoLogo", "-ExecutionPolicy", "Bypass", "-File"]; - -/// Cached location of the `PowerShell` host. Prefers cross-platform -/// `pwsh.exe` when present, falling back to the Windows built-in -/// `powershell.exe`. Returns `None` on non-Windows or when neither host -/// is on `PATH`. -/// -/// Cached as `Arc` so callers that want shared ownership -/// (e.g. `vite_task_plan`'s plan-time rewrite) can do `Arc::clone(host)` -/// without copying the path. -#[cfg(windows)] -#[must_use] -pub fn powershell_host() -> Option<&'static Arc> { - use std::sync::LazyLock; - - static POWERSHELL_HOST: LazyLock>> = LazyLock::new(|| { - let resolved = which::which("pwsh.exe").or_else(|_| which::which("powershell.exe")).ok()?; - AbsolutePathBuf::new(resolved).map(Arc::::from) - }); - POWERSHELL_HOST.as_ref() -} - -#[cfg(not(windows))] -#[must_use] -pub const fn powershell_host() -> Option<&'static Arc> { - None -} - -/// Given a resolved `.cmd` path, return its sibling `.ps1` if one exists -/// on disk. The extension match is case-insensitive (matches `.cmd`, -/// `.CMD`, `.Cmd`). -/// -/// Returns `None` when the path is not a `.cmd` or no `.ps1` sibling -/// exists. Callers that need additional scope checks (e.g. "must live -/// inside the workspace's `node_modules/.bin`") should layer those on -/// top of this primitive. -#[must_use] -pub fn find_ps1_sibling(resolved: &AbsolutePath) -> Option { - let ext = resolved.as_path().extension().and_then(|e| e.to_str())?; - if !ext.eq_ignore_ascii_case("cmd") { - return None; - } - - let ps1 = resolved.with_extension("ps1"); - if !ps1.as_path().is_file() { - return None; - } - - Some(ps1) -} - -/// Cached `stdin.is_terminal()`. The TTY-ness of stdin is fixed for the -/// process lifetime, so the underlying syscall runs at most once per process. -/// -/// Gates the `.cmd` -> PowerShell `.ps1` rewrite that both `vite_task_plan` -/// and `vite_command` perform: the npm/pnpm/yarn `.ps1` wrappers read stdin -/// (`$MyInvocation.ExpectingInput` -> `$input | & node ...`) and hang forever -/// on a non-TTY pipe or null, as on CI runners. Without a terminal there is -/// also no Ctrl+C "Terminate batch job (Y/N)?" prompt to corrupt, so callers -/// fall back to the `.cmd` (which never reads stdin) when this returns `false`. -/// -/// See . -#[must_use] -pub fn is_stdin_terminal() -> bool { - use std::{io::IsTerminal, sync::LazyLock}; - - static IS_TTY: LazyLock = LazyLock::new(|| std::io::stdin().is_terminal()); - *IS_TTY -} - -#[cfg(test)] -mod tests { - use std::fs; - - use tempfile::tempdir; - - use super::*; - - #[expect(clippy::disallowed_types, reason = "tempdir bridges std PathBuf into AbsolutePath")] - fn abs(buf: std::path::PathBuf) -> AbsolutePathBuf { - AbsolutePathBuf::new(buf).unwrap() - } - - #[test] - fn find_ps1_sibling_returns_path_when_both_present() { - let dir = tempdir().unwrap(); - let root = abs(dir.path().canonicalize().unwrap()); - fs::write(root.as_path().join("npm.cmd"), "").unwrap(); - fs::write(root.as_path().join("npm.ps1"), "").unwrap(); - - let resolved = abs(root.as_path().join("npm.cmd")); - let sibling = find_ps1_sibling(&resolved).expect("should find sibling"); - assert_eq!(sibling.as_path(), root.as_path().join("npm.ps1")); - } - - #[test] - fn find_ps1_sibling_is_case_insensitive_on_extension() { - let dir = tempdir().unwrap(); - let root = abs(dir.path().canonicalize().unwrap()); - fs::write(root.as_path().join("pnpm.CMD"), "").unwrap(); - fs::write(root.as_path().join("pnpm.ps1"), "").unwrap(); - - let resolved = abs(root.as_path().join("pnpm.CMD")); - assert!(find_ps1_sibling(&resolved).is_some()); - } - - #[test] - fn find_ps1_sibling_returns_none_when_sibling_missing() { - let dir = tempdir().unwrap(); - let root = abs(dir.path().canonicalize().unwrap()); - fs::write(root.as_path().join("npm.cmd"), "").unwrap(); - - let resolved = abs(root.as_path().join("npm.cmd")); - assert!(find_ps1_sibling(&resolved).is_none()); - } - - #[test] - fn find_ps1_sibling_returns_none_for_non_cmd() { - let dir = tempdir().unwrap(); - let root = abs(dir.path().canonicalize().unwrap()); - fs::write(root.as_path().join("bun.exe"), "").unwrap(); - fs::write(root.as_path().join("bun.ps1"), "").unwrap(); - - let resolved = abs(root.as_path().join("bun.exe")); - assert!(find_ps1_sibling(&resolved).is_none()); - } - - #[test] - fn find_ps1_sibling_returns_none_for_no_extension() { - let dir = tempdir().unwrap(); - let root = abs(dir.path().canonicalize().unwrap()); - fs::write(root.as_path().join("node"), "").unwrap(); - fs::write(root.as_path().join("node.ps1"), "").unwrap(); - - let resolved = abs(root.as_path().join("node")); - assert!(find_ps1_sibling(&resolved).is_none()); - } - - #[test] - fn is_stdin_terminal_is_idempotent() { - // The value depends on how the test runner wires stdin (non-TTY under - // nextest), so assert the cached result is stable rather than a fixed - // value. - assert_eq!(is_stdin_terminal(), is_stdin_terminal()); - } -} diff --git a/crates/vite_select/Cargo.toml b/crates/vite_select/Cargo.toml deleted file mode 100644 index dc6467a1b..000000000 --- a/crates/vite_select/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "vite_select" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[lints] -workspace = true - -[dependencies] -anyhow = { workspace = true } -crossterm = { workspace = true } -nucleo-matcher = { workspace = true } -vite_str = { workspace = true } - -[dev-dependencies] -assert2 = { workspace = true } - -[lib] -doctest = false diff --git a/crates/vite_select/src/fuzzy.rs b/crates/vite_select/src/fuzzy.rs deleted file mode 100644 index f40677436..000000000 --- a/crates/vite_select/src/fuzzy.rs +++ /dev/null @@ -1,104 +0,0 @@ -use nucleo_matcher::{ - Matcher, - pattern::{AtomKind, CaseMatching, Normalization, Pattern}, -}; - -/// Fuzzy-match `query` against a list of strings. -/// -/// Returns original indices sorted by score descending (best match first). -/// When `query` is empty, returns all indices in their original order. -#[must_use] -pub fn fuzzy_match(query: &str, items: &[&str]) -> Vec { - if query.is_empty() { - return (0..items.len()).collect(); - } - - let pattern = Pattern::new(query, CaseMatching::Ignore, Normalization::Smart, AtomKind::Fuzzy); - let mut matcher = Matcher::new(nucleo_matcher::Config::DEFAULT); - - let mut scored: Vec<(usize, u32)> = items - .iter() - .enumerate() - .filter_map(|(idx, item)| { - pattern - .score(nucleo_matcher::Utf32Str::Ascii(item.as_bytes()), &mut matcher) - .map(|score| (idx, score)) - }) - .collect(); - - scored.sort_by_key(|b| std::cmp::Reverse(b.1)); - scored.into_iter().map(|(idx, _)| idx).collect() -} - -#[cfg(test)] -mod tests { - use assert2::{assert, check}; - - use super::*; - - const TASK_NAMES: &[&str] = - &["build", "lint", "test", "app#build", "app#lint", "app#test", "lib#build"]; - - #[test] - fn exact_match_scores_highest() { - let results = fuzzy_match("build", TASK_NAMES); - assert!(!results.is_empty()); - // "build" should be the highest-scoring match - check!(TASK_NAMES[results[0]] == "build"); - } - - #[test] - fn typo_matches_similar() { - let results = fuzzy_match("buid", TASK_NAMES); - assert!(!results.is_empty()); - // Should match "build" and "app#build" and "lib#build" but not "lint" or "test" - let matched_names: Vec<&str> = results.iter().map(|&i| TASK_NAMES[i]).collect(); - check!(matched_names.contains(&"build")); - for name in &matched_names { - check!(!name.contains("lint")); - check!(!name.contains("test")); - } - } - - #[test] - fn empty_query_returns_all() { - let results = fuzzy_match("", TASK_NAMES); - check!(results.len() == TASK_NAMES.len()); - // Indices should be in original order - for (pos, &idx) in results.iter().enumerate() { - check!(idx == pos); - } - } - - #[test] - fn completely_unrelated_query_returns_nothing() { - let results = fuzzy_match("zzzzz", TASK_NAMES); - check!(results.is_empty()); - } - - #[test] - fn package_qualified_match() { - let results = fuzzy_match("app#build", TASK_NAMES); - assert!(!results.is_empty()); - check!(TASK_NAMES[results[0]] == "app#build"); - } - - #[test] - fn lint_matches_lint_tasks() { - let results = fuzzy_match("lint", TASK_NAMES); - assert!(!results.is_empty()); - let matched_names: Vec<&str> = results.iter().map(|&i| TASK_NAMES[i]).collect(); - check!(matched_names.contains(&"lint")); - check!(matched_names.contains(&"app#lint")); - } - - #[test] - fn score_ordering_exact_before_fuzzy() { - let results = fuzzy_match("build", TASK_NAMES); - assert!(results.len() >= 2); - // Exact "build" should appear before "app#build" (higher score = earlier position) - let build_pos = results.iter().position(|&i| TASK_NAMES[i] == "build").unwrap(); - let app_build_pos = results.iter().position(|&i| TASK_NAMES[i] == "app#build").unwrap(); - check!(build_pos <= app_build_pos); - } -} diff --git a/crates/vite_select/src/interactive.rs b/crates/vite_select/src/interactive.rs deleted file mode 100644 index 16531cb5a..000000000 --- a/crates/vite_select/src/interactive.rs +++ /dev/null @@ -1,899 +0,0 @@ -use std::io::{Write, stdout}; - -use crossterm::{ - cursor::{self, MoveToColumn}, - event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}, - style::{Attribute, Color, SetAttribute, SetForegroundColor}, - terminal::{self, Clear, ClearType}, -}; -use vite_str::Str; - -use crate::{RenderState, SelectItem, fuzzy::fuzzy_match}; - -/// Prefix width for root-level items (`" › "` or `" "`). -const ROOT_PREFIX_WIDTH: usize = 4; -/// Prefix width for grouped items (`" › "` or `" "`). -const GROUP_PREFIX_WIDTH: usize = 6; - -struct RawModeGuard; - -impl RawModeGuard { - fn enable() -> anyhow::Result { - terminal::enable_raw_mode()?; - Ok(Self) - } -} - -impl Drop for RawModeGuard { - fn drop(&mut self) { - let _ = terminal::disable_raw_mode(); - } -} - -/// A row in the flattened display list. -pub enum DisplayRow { - /// Non-selectable group header line. - Header(Str), - /// Selectable item. `item_index` is the index into the original `items` slice. - Item { item_index: usize }, -} - -impl DisplayRow { - pub const fn is_item(&self) -> bool { - matches!(self, Self::Item { .. }) - } -} - -/// Filter, group, and flatten items into display rows. -/// -/// Pipeline: fuzzy match → group by `SelectItem::group` (current-package first) -/// → insert header rows at group boundaries. -pub fn build_display_rows(items: &[SelectItem], query: &str) -> Vec { - let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect(); - let mut filtered = fuzzy_match(query, &labels); - group_filtered(items, &mut filtered); - - let mut rows = Vec::new(); - let mut current_group: Option> = None; - for &item_idx in &filtered { - let group = items[item_idx].group.as_deref(); - if current_group != Some(group) { - current_group = Some(group); - if let Some(g) = group { - rows.push(DisplayRow::Header(Str::from(g))); - } - } - rows.push(DisplayRow::Item { item_index: item_idx }); - } - rows -} - -/// Reorder `filtered` so items are grouped by `SelectItem::group`. -/// -/// Groups are ordered by the position of their best-matching item in the -/// original fuzzy-scored `filtered` list. Items with `group: None` -/// (current-package tasks) are always placed first. -fn group_filtered(items: &[SelectItem], filtered: &mut Vec) { - // Collect group ordering: first-seen order preserves fuzzy rank. - let mut group_order: Vec> = Vec::new(); - for &idx in filtered.iter() { - let group = items[idx].group.as_deref(); - if !group_order.contains(&group) { - group_order.push(group); - } - } - // Always put current-package group (None) first. - if let Some(pos) = group_order.iter().position(Option::is_none) - && pos != 0 - { - let g = group_order.remove(pos); - group_order.insert(0, g); - } - let mut new_filtered = Vec::with_capacity(filtered.len()); - for &group in &group_order { - for &idx in filtered.iter() { - if items[idx].group.as_deref() == group { - new_filtered.push(idx); - } - } - } - *filtered = new_filtered; -} - -struct State<'a> { - items: &'a [SelectItem], - /// Flattened display rows (headers + items): the single source of truth - /// after filtering + grouping. - display_rows: Vec, - /// Cached count of selectable items in `display_rows`. - item_count: usize, - #[expect( - clippy::disallowed_types, - reason = "crossterm key events push chars one at a time; String is natural here" - )] - query: String, - /// Index among selectable items (0 = first Item row, 1 = second, etc.). - selected: usize, - /// Index into `display_rows` — first visible row. - scroll_offset: usize, - /// Max visible lines (display rows) in the viewport. - page_size: usize, - /// Number of lines rendered in the last frame (for clearing). - rendered_lines: usize, -} - -impl<'a> State<'a> { - fn new(items: &'a [SelectItem], initial_query: Option<&str>, page_size: usize) -> Self { - let query = initial_query.unwrap_or_default().to_owned(); - let display_rows = build_display_rows(items, &query); - let item_count = display_rows.iter().filter(|r| r.is_item()).count(); - Self { - items, - display_rows, - item_count, - query, - selected: 0, - scroll_offset: 0, - page_size, - rendered_lines: 0, - } - } - - fn refilter(&mut self) { - self.display_rows = build_display_rows(self.items, &self.query); - self.item_count = self.display_rows.iter().filter(|r| r.is_item()).count(); - self.selected = 0; - self.scroll_offset = 0; - } - - /// Find the display row index for the Nth selectable item. - fn display_row_of_selected(&self) -> Option { - let mut count = 0; - for (i, row) in self.display_rows.iter().enumerate() { - if row.is_item() { - if count == self.selected { - return Some(i); - } - count += 1; - } - } - None - } - - /// Get the original item index for the currently selected item. - fn selected_item_index(&self) -> Option { - let row_idx = self.display_row_of_selected()?; - match &self.display_rows[row_idx] { - DisplayRow::Item { item_index } => Some(*item_index), - DisplayRow::Header(_) => None, - } - } - - /// Ensure the selected item is within the visible viewport. - fn ensure_selected_visible(&mut self) { - let Some(row_idx) = self.display_row_of_selected() else { - self.scroll_offset = 0; - return; - }; - if row_idx < self.scroll_offset { - // If the selected item is a first item in a group, also show the header above it - self.scroll_offset = - if row_idx > 0 && matches!(self.display_rows[row_idx - 1], DisplayRow::Header(_)) { - row_idx - 1 - } else { - row_idx - }; - } else if row_idx >= self.scroll_offset + self.page_size { - self.scroll_offset = row_idx + 1 - self.page_size; - } - } - - fn move_up(&mut self) { - if self.selected > 0 { - self.selected -= 1; - self.ensure_selected_visible(); - } - } - - fn move_down(&mut self) { - if self.item_count > 0 && self.selected < self.item_count - 1 { - self.selected += 1; - self.ensure_selected_visible(); - } - } - - fn visible_display_rows(&self) -> std::ops::Range { - let end = (self.scroll_offset + self.page_size).min(self.display_rows.len()); - self.scroll_offset..end - } - - /// Count selectable items (not headers) beyond the visible window. - fn hidden_item_count(&self) -> usize { - let visible_end = (self.scroll_offset + self.page_size).min(self.display_rows.len()); - self.display_rows[visible_end..].iter().filter(|r| r.is_item()).count() - } -} - -/// Parameters for rendering a task list. -pub struct RenderParams<'a> { - pub items: &'a [SelectItem], - pub display_rows: &'a [DisplayRow], - /// Which selectable item is highlighted (0-based among Item rows), - /// or `None` for non-interactive. - pub selected: Option, - /// Which slice of `display_rows` to show. - pub visible_row_range: std::ops::Range, - /// Number of selectable items beyond the visible range. - pub hidden_count: usize, - pub header: Option<&'a str>, - /// Prompt line text, rendered when `query` is `Some` (interactive only). - pub prompt: &'a str, - /// Current search text. `Some` enables the prompt line (interactive only). - pub query: Option<&'a str>, - /// Whether to render group header lines. When `false`, items are still - /// grouped/sorted by `SelectItem::group` but headers are hidden. - pub show_group_headers: bool, - /// `"\r\n"` for raw mode, `"\n"` for normal. - pub line_ending: &'a str, - /// Maximum visible width per line. Descriptions are truncated to prevent - /// line wrapping, which would break cursor-based clearing in interactive mode. - /// Use `usize::MAX` to disable truncation (non-interactive / piped output). - pub max_line_width: usize, -} - -/// Truncate a description string to fit within `max_chars`, appending ellipsis if needed. -fn truncate_desc<'a>(desc: &'a str, max_chars: usize, buf: &'a mut Str) -> &'a str { - let char_count = desc.chars().count(); - if char_count <= max_chars { - return desc; - } - let take = max_chars.saturating_sub(1); // room for "…" - #[expect(clippy::disallowed_types, reason = "intermediate collect for char truncation")] - let prefix: std::string::String = desc.chars().take(take).collect(); - *buf = vite_str::format!("{prefix}\u{2026}"); - buf.as_str() -} - -/// Render the item list. Shared rendering logic used by both interactive -/// and non-interactive modes (via [`crate::non_interactive`]). -/// -/// Returns the number of lines written. -#[expect(clippy::too_many_lines, reason = "single rendering function with sequential layout logic")] -pub fn render_items(writer: &mut impl Write, params: &RenderParams<'_>) -> anyhow::Result { - let RenderParams { - items, - display_rows, - selected, - visible_row_range, - hidden_count, - header, - prompt, - query, - show_group_headers, - line_ending, - max_line_width: _, - } = params; - - let mut lines = 0usize; - - // Header (e.g. error message) - if let Some(header) = header { - write!(writer, "{header}{line_ending}")?; - lines += 1; - } - - let is_interactive = query.is_some(); - - // Prompt line (interactive only) - if let Some(q) = query { - // Print ": " separator before query only when query is non-empty, - // to avoid a trailing space that Windows ConPTY would strip. - if q.is_empty() { - write!(writer, "{prompt}{line_ending}")?; - } else { - write!(writer, "{prompt} {q}{line_ending}")?; - } - write!(writer, "{line_ending}")?; - lines += 2; - } - - // Single pre-pass: compute has_groups, max_name_width, has_items, and - // item_ordinal (count of Item rows before the visible range) together. - let mut has_groups = false; - let mut max_name_width = 0usize; - let mut has_items = false; - let mut item_ordinal = 0usize; - if is_interactive { - for (i, row) in display_rows.iter().enumerate() { - match row { - DisplayRow::Header(_) => has_groups = *show_group_headers, - DisplayRow::Item { item_index } => { - has_items = true; - let w = items[*item_index].display_name.chars().count(); - if w > max_name_width { - max_name_width = w; - } - if i < visible_row_range.start { - item_ordinal += 1; - } - } - } - } - } else { - has_items = display_rows.iter().any(DisplayRow::is_item); - } - - // Compute the absolute column where commands start (interactive only). - // All items — root and grouped — align their descriptions to the same column. - let max_prefix = if has_groups { GROUP_PREFIX_WIDTH } else { ROOT_PREFIX_WIDTH }; - // command_col = max_prefix + max_name_width + " " - let command_col = if is_interactive { max_prefix + max_name_width + 1 } else { 0 }; - - // Render visible display rows - for ri in visible_row_range.clone() { - let row = &display_rows[ri]; - match row { - DisplayRow::Header(group_name) => { - if !show_group_headers { - continue; - } - if is_interactive { - write!( - writer, - " {dim}{name}{reset}{line_ending}", - dim = SetAttribute(Attribute::Dim), - name = group_name, - reset = SetAttribute(Attribute::Reset), - )?; - } else { - write!(writer, " {group_name}{line_ending}")?; - } - lines += 1; - } - DisplayRow::Item { item_index } => { - let item = &items[*item_index]; - let is_selected = *selected == Some(item_ordinal); - item_ordinal += 1; - let is_in_group = item.group.is_some(); - - let name = item.display_name.as_str(); - let name_width = name.chars().count(); - - let prefix_width = if is_interactive { - if is_in_group { GROUP_PREFIX_WIDTH } else { ROOT_PREFIX_WIDTH } - } else { - 2 - }; - - // Padding after name to align all commands at `command_col`. - let name_padding = - if is_interactive { command_col - prefix_width - name_width - 1 } else { 0 }; - let max_desc_chars = params.max_line_width.saturating_sub(if is_interactive { - command_col - } else { - prefix_width + name_width + 2 - }); - - let mut truncated = Str::default(); - let display_desc = - truncate_desc(item.description.as_str(), max_desc_chars, &mut truncated); - - if is_interactive { - let prefix = match (is_selected, is_in_group) { - (true, true) => " \u{203a} ", - (true, false) => " \u{203a} ", - (false, true) => " ", - (false, false) => " ", - }; - let reset = SetAttribute(Attribute::Reset); - let dark_grey = SetForegroundColor(Color::DarkGrey); - if is_selected { - let blue = SetForegroundColor(Color::Blue); - let bold = SetAttribute(Attribute::Bold); - write!( - writer, - "{dark_grey}{prefix}{reset}{blue}{bold}{name}{reset}{dark_grey}{:>pad$} {desc}{reset}{line_ending}", - "", - pad = name_padding, - desc = display_desc, - )?; - } else { - write!( - writer, - "{prefix}{name}{dark_grey}{:>pad$} {desc}{reset}{line_ending}", - "", - pad = name_padding, - desc = display_desc, - )?; - } - } else if is_selected { - let bold = SetAttribute(Attribute::Bold); - let reset = SetAttribute(Attribute::Reset); - write!( - writer, - "{bold}> {name}: {desc}{reset}{line_ending}", - name = item.display_name, - desc = display_desc, - )?; - } else { - write!(writer, " {}: {display_desc}{line_ending}", item.display_name)?; - } - lines += 1; - } - } - } - - // Footer: hidden items count - if *hidden_count > 0 { - write!(writer, " (\u{2026}{hidden_count} more){line_ending}")?; - lines += 1; - } - - // Empty state - if !has_items { - write!(writer, " No matching tasks. Press Escape to clear search.{line_ending}")?; - lines += 1; - } - - writer.flush()?; - Ok(lines) -} - -fn render( - stdout: &mut impl Write, - state: &mut State<'_>, - header: Option<&str>, - prompt: &str, -) -> anyhow::Result<()> { - // Move cursor up to clear previous render - if state.rendered_lines > 0 { - let move_up = u16::try_from(state.rendered_lines) - .expect("rendered_lines fits in u16: at most header + page_size + footer lines"); - crossterm::execute!( - stdout, - cursor::MoveUp(move_up), - MoveToColumn(0), - Clear(ClearType::FromCursorDown), - )?; - } - - // Query terminal width on each render to handle resize - let max_line_width = terminal::size().map_or(80, |(w, _)| w as usize); - - let lines = render_items( - stdout, - &RenderParams { - items: state.items, - display_rows: &state.display_rows, - selected: Some(state.selected), - visible_row_range: state.visible_display_rows(), - hidden_count: state.hidden_item_count(), - header, - prompt, - query: Some(&state.query), - show_group_headers: true, - line_ending: "\r\n", - max_line_width, - }, - )?; - - state.rendered_lines = lines; - Ok(()) -} - -pub fn run( - items: &[SelectItem], - initial_query: Option<&str>, - selected_index: &mut usize, - header: Option<&str>, - prompt: &str, - page_size: usize, - mut after_render: impl FnMut(&RenderState<'_>), -) -> anyhow::Result { - if items.is_empty() { - anyhow::bail!("No tasks available"); - } - - let _guard = RawModeGuard::enable()?; - // Hide cursor while the widget is active - let mut out = stdout(); - crossterm::execute!(out, cursor::Hide)?; - - let mut state = State::new(items, initial_query, page_size); - - // Initial render - render(&mut out, &mut state, header, prompt)?; - after_render(&RenderState { query: &state.query, selected_index: state.selected }); - - loop { - let ev = event::read()?; - match ev { - Event::Key(KeyEvent { code, modifiers, kind: KeyEventKind::Press, .. }) => match code { - KeyCode::Esc => { - // Clear the search query and reset the filter - state.query.clear(); - state.refilter(); - } - KeyCode::Char('c') if modifiers.contains(KeyModifiers::CONTROL) => { - cleanup(&mut out, &state)?; - return Ok(super::SelectResult::Cancelled); - } - KeyCode::Enter => { - let Some(idx) = state.selected_item_index() else { - continue; - }; - *selected_index = idx; - cleanup(&mut out, &state)?; - return Ok(super::SelectResult::Selected); - } - KeyCode::Up => { - state.move_up(); - } - KeyCode::Down => { - state.move_down(); - } - KeyCode::Char(c) => { - state.query.push(c); - state.refilter(); - } - KeyCode::Backspace => { - state.query.pop(); - state.refilter(); - } - _ => continue, - }, - _ => continue, - } - - render(&mut out, &mut state, header, prompt)?; - after_render(&RenderState { query: &state.query, selected_index: state.selected }); - } -} - -/// Clear the widget output and restore cursor. -fn cleanup(stdout: &mut impl Write, state: &State<'_>) -> anyhow::Result<()> { - if state.rendered_lines > 0 { - let move_up = u16::try_from(state.rendered_lines) - .expect("rendered_lines fits in u16: at most header + page_size + footer lines"); - crossterm::execute!( - stdout, - cursor::MoveUp(move_up), - MoveToColumn(0), - Clear(ClearType::FromCursorDown), - )?; - } - crossterm::execute!(stdout, cursor::Show)?; - stdout.flush()?; - Ok(()) -} - -#[cfg(test)] -mod tests { - const TASK_PROMPT: &str = "Select a task (\u{2191}/\u{2193}, Enter to run, type to search):"; - - use super::*; - - fn make_items(items: &[(&str, &str)]) -> Vec { - items - .iter() - .map(|(label, desc)| SelectItem { - label: (*label).into(), - display_name: (*label).into(), - description: (*desc).into(), - group: None, - }) - .collect() - } - - /// Create items with explicit groups: (label, `display_name`, description, group) - fn make_grouped_items(items: &[(&str, &str, &str, Option<&str>)]) -> Vec { - items - .iter() - .map(|(label, display_name, desc, group)| SelectItem { - label: (*label).into(), - display_name: (*display_name).into(), - description: (*desc).into(), - group: group.map(Str::from), - }) - .collect() - } - - /// Strip ANSI escape sequences from output for easier assertions. - #[expect(clippy::disallowed_types, reason = "test helper building arbitrary output string")] - fn strip_ansi(s: &str) -> String { - let mut result = String::new(); - let mut chars = s.chars(); - while let Some(c) = chars.next() { - if c == '\x1b' { - // Skip until we hit a letter (end of escape sequence) - for c in chars.by_ref() { - if c.is_ascii_alphabetic() { - break; - } - } - } else { - result.push(c); - } - } - result - } - - #[expect(clippy::disallowed_types, reason = "test helper building arbitrary output string")] - fn render_to_string(items: &[SelectItem], max_line_width: usize) -> String { - let display_rows = build_display_rows(items, ""); - let len = display_rows.len(); - let mut buf = Vec::new(); - render_items( - &mut buf, - &RenderParams { - items, - display_rows: &display_rows, - selected: Some(0), - visible_row_range: 0..len, - hidden_count: 0, - header: None, - prompt: TASK_PROMPT, - query: None, - show_group_headers: false, - line_ending: "\n", - max_line_width, - }, - ) - .unwrap(); - strip_ansi(&String::from_utf8(buf).unwrap()) - } - - #[expect(clippy::disallowed_types, reason = "test helper building arbitrary output string")] - fn render_interactive_to_string( - items: &[SelectItem], - query: &str, - max_line_width: usize, - ) -> String { - let display_rows = build_display_rows(items, query); - let len = display_rows.len(); - let mut buf = Vec::new(); - render_items( - &mut buf, - &RenderParams { - items, - display_rows: &display_rows, - selected: Some(0), - visible_row_range: 0..len, - hidden_count: 0, - header: None, - prompt: TASK_PROMPT, - query: Some(query), - show_group_headers: true, - line_ending: "\n", - max_line_width, - }, - ) - .unwrap(); - strip_ansi(&String::from_utf8(buf).unwrap()) - } - - #[test] - fn truncates_long_description() { - let items = make_items(&[("build", "a really long command that exceeds the width limit")]); - // " build: a really long..." = 2 + 5 + 2 + desc - // max_line_width = 30 => max_desc = 30 - 9 = 21 chars - let output = render_to_string(&items, 30); - let line = output.lines().next().unwrap(); - // "> " (2) + "build" (5) + ": " (2) + desc (21) = 30 - assert!( - line.chars().count() <= 30, - "line should be at most 30 chars, got {}: {line:?}", - line.chars().count() - ); - assert!(line.contains('\u{2026}'), "truncated line should contain ellipsis: {line:?}"); - } - - #[test] - fn does_not_truncate_short_description() { - let items = make_items(&[("build", "echo ok")]); - let output = render_to_string(&items, 80); - let line = output.lines().next().unwrap(); - assert!(!line.contains('\u{2026}'), "short line should not be truncated: {line:?}"); - assert!(line.contains("echo ok"), "full description should appear: {line:?}"); - } - - #[test] - fn max_line_width_max_disables_truncation() { - let long_desc = "x".repeat(500); - let items = make_items(&[("build", &long_desc)]); - let output = render_to_string(&items, usize::MAX); - let line = output.lines().next().unwrap(); - assert!(!line.contains('\u{2026}'), "usize::MAX should disable truncation: {line:?}"); - assert!(line.contains(&long_desc), "full 500-char description should appear"); - } - - #[test] - fn each_line_fits_within_max_width() { - let items = make_items(&[ - ("build", "tsc -p tsconfig.build.json && echo done"), - ("lint", "oxlint --fix"), - ("test", "vitest run --reporter=verbose --coverage"), - ]); - let max_width = 40; - let output = render_to_string(&items, max_width); - for line in output.lines() { - assert!( - line.chars().count() <= max_width, - "line exceeds max width {max_width}: ({}) {line:?}", - line.chars().count() - ); - } - } - - #[test] - fn truncation_preserves_label() { - let items = make_items(&[("my-task", "very long description here")]); - // " my-task: very..." => prefix(2) + label(7) + sep(2) + desc - // max_line_width = 20 => max_desc = 20 - 11 = 9 chars - let output = render_to_string(&items, 20); - let line = output.lines().next().unwrap(); - assert!(line.contains("my-task"), "label should always be preserved: {line:?}"); - } - - #[test] - fn interactive_style_matches_vp_selector_marker_and_indent() { - let items = make_items(&[("build", "echo build"), ("lint", "echo lint")]); - let output = render_interactive_to_string(&items, "", 80); - let mut lines = output.lines(); - let prompt = lines.next().unwrap(); - let spacer = lines.next().unwrap(); - let selected = lines.next().unwrap(); - let unselected = lines.next().unwrap(); - assert_eq!(prompt, "Select a task (\u{2191}/\u{2193}, Enter to run, type to search):"); - assert!(spacer.is_empty()); - assert_eq!(selected, " \u{203a} build echo build"); - assert_eq!(unselected, " lint echo lint"); - } - - #[test] - fn interactive_commands_are_aligned() { - let items = - make_items(&[("build", "echo build"), ("lint", "echo lint"), ("test", "vitest run")]); - let output = render_interactive_to_string(&items, "", 80); - let item_lines: Vec<&str> = output.lines().skip(2).collect(); - // max_name_width = 5 ("build") - // prefix(4) + max_name(5) + padding + " " + desc - assert_eq!(item_lines[0], " \u{203a} build echo build"); - assert_eq!(item_lines[1], " lint echo lint"); - assert_eq!(item_lines[2], " test vitest run"); - } - - #[test] - fn interactive_truncation_accounts_for_padding() { - let items = make_items(&[ - ("build", "a really long command that exceeds the width limit"), - ("lint", "short"), - ]); - // max_name_width = 5, prefix(4) + max_name(5) + sep(1) = 10 - // max_line_width = 30 => max_desc = 30 - 11 = 19 chars - let output = render_interactive_to_string(&items, "", 30); - for line in output.lines().skip(2) { - assert!( - line.chars().count() <= 30, - "line exceeds 30 chars: ({}) {line:?}", - line.chars().count() - ); - } - let build_line = output.lines().nth(2).unwrap(); - assert!( - build_line.contains('\u{2026}'), - "long description should be truncated: {build_line:?}" - ); - } - - #[test] - fn interactive_tree_view_with_groups() { - let items = make_grouped_items(&[ - ("build", "build", "echo build app", None), - ("lint", "lint", "echo lint app", None), - ("lib#build", "build", "echo build lib", Some("lib (packages/lib)")), - ("lib#lint", "lint", "echo lint lib", Some("lib (packages/lib)")), - ]); - let output = render_interactive_to_string(&items, "", 80); - let item_lines: Vec<&str> = output.lines().skip(2).collect(); - // max_name=5, has_groups → max_prefix=6, command_col=12 - // Root items get extra padding to align with grouped items - assert_eq!(item_lines[0], " \u{203a} build echo build app"); - assert_eq!(item_lines[1], " lint echo lint app"); - // Group header - assert_eq!(item_lines[2], " lib (packages/lib)"); - // Grouped items (indented by 2 more, less padding) - assert_eq!(item_lines[3], " build echo build lib"); - assert_eq!(item_lines[4], " lint echo lint lib"); - } - - #[test] - fn interactive_tree_view_alignment_across_groups() { - let items = make_grouped_items(&[ - ("build", "build", "echo build", None), - ("typecheck", "typecheck", "echo tc", None), - ("lib#build", "build", "echo build lib", Some("lib")), - ]); - let output = render_interactive_to_string(&items, "", 80); - let item_lines: Vec<&str> = output.lines().skip(2).collect(); - // max_name=9, has_groups → max_prefix=6, command_col=16 - // All commands start at column 16 regardless of indent level - assert_eq!(item_lines[0], " \u{203a} build echo build"); - assert_eq!(item_lines[1], " typecheck echo tc"); - assert_eq!(item_lines[2], " lib"); - assert_eq!(item_lines[3], " build echo build lib"); - } - - #[test] - fn interactive_tree_view_truncation_with_groups() { - let items = make_grouped_items(&[ - ("build", "build", "a really long command that exceeds the limit", None), - ( - "lib#build", - "build", - "another really long command that exceeds the limit", - Some("lib"), - ), - ]); - let output = render_interactive_to_string(&items, "", 30); - for line in output.lines().skip(2) { - if !line.is_empty() { - assert!( - line.chars().count() <= 30, - "line exceeds 30 chars: ({}) {line:?}", - line.chars().count() - ); - } - } - } - - #[test] - fn display_rows_built_correctly() { - let items = make_grouped_items(&[ - ("build", "build", "echo build", None), - ("lib#build", "build", "echo build lib", Some("lib")), - ("lib#lint", "lint", "echo lint lib", Some("lib")), - ("app#build", "build", "echo build app", Some("app")), - ]); - let rows = build_display_rows(&items, ""); - assert_eq!(rows.len(), 6); // 4 items + 2 headers ("lib", "app") - assert!(matches!(&rows[0], DisplayRow::Item { item_index: 0 })); - assert!(matches!(&rows[1], DisplayRow::Header(h) if h.as_str() == "lib")); - assert!(matches!(&rows[2], DisplayRow::Item { item_index: 1 })); - assert!(matches!(&rows[3], DisplayRow::Item { item_index: 2 })); - assert!(matches!(&rows[4], DisplayRow::Header(h) if h.as_str() == "app")); - assert!(matches!(&rows[5], DisplayRow::Item { item_index: 3 })); - } - - /// Mirrors the E2E scenario: items sorted alphabetically by package name - /// (app before lib), with lib being the current package (group: None). - /// Verifies that None-group items come first despite appearing later in input. - #[test] - fn display_rows_none_group_first_when_not_first_in_input() { - let items = make_grouped_items(&[ - // app items first (alphabetically before lib) - ("app#build", "app#build", "echo build app", Some("app (packages/app)")), - ("app#lint", "app#lint", "echo lint app", Some("app (packages/app)")), - // lib items (current package, group: None) - ("build", "build", "echo build lib", None), - ("lint", "lint", "echo lint lib", None), - // root items - ("root#check", "root#check", "echo check", Some("root (workspace root)")), - ]); - let rows = build_display_rows(&items, ""); - // None-group (lib) should come first, then app, then root - assert!( - matches!(&rows[0], DisplayRow::Item { item_index: 2 }), - "first item should be lib build (idx 2)" - ); - assert!( - matches!(&rows[1], DisplayRow::Item { item_index: 3 }), - "second item should be lib lint (idx 3)" - ); - assert!(matches!(&rows[2], DisplayRow::Header(h) if h.as_str() == "app (packages/app)")); - assert!(matches!(&rows[3], DisplayRow::Item { item_index: 0 })); - assert!(matches!(&rows[4], DisplayRow::Item { item_index: 1 })); - assert!(matches!(&rows[5], DisplayRow::Header(h) if h.as_str() == "root (workspace root)")); - assert!(matches!(&rows[6], DisplayRow::Item { item_index: 4 })); - } -} diff --git a/crates/vite_select/src/lib.rs b/crates/vite_select/src/lib.rs deleted file mode 100644 index 2685e72bd..000000000 --- a/crates/vite_select/src/lib.rs +++ /dev/null @@ -1,141 +0,0 @@ -mod fuzzy; -mod interactive; - -use std::io::Write; - -pub use fuzzy::fuzzy_match; -use interactive::{RenderParams, build_display_rows, render_items}; -use vite_str::Str; - -/// An item in the selection list. -pub struct SelectItem { - /// Searchable label, e.g. `"build"` or `"app#build"`. Used for fuzzy matching. - pub label: Str, - /// Display name shown in the list, e.g. `"build"` (tree view) or `"app#build"` (flat). - pub display_name: Str, - /// Description shown next to the display name, e.g. `"echo build app"`. - pub description: Str, - /// Group header text. Items sharing the same group render together under a - /// header line. `None` = top-level (no header). - pub group: Option, -} - -/// Selection mode. -pub enum Mode<'a> { - /// Interactive terminal UI with fuzzy search, keyboard navigation, and selection. - /// - /// On Enter, `*selected_index` is set to the index of the chosen item - /// in the original `items` slice. - Interactive { selected_index: &'a mut usize }, - /// Non-interactive: renders the list once and returns. - NonInteractive, -} - -/// Snapshot of the selector's visible state, passed to `after_render`. -pub struct RenderState<'a> { - /// Current search text (empty if no filter typed yet). - pub query: &'a str, - /// Index of the highlighted item in the **filtered** list. - pub selected_index: usize, -} - -/// Parameters for [`select_list`]. -pub struct SelectParams<'a> { - pub items: &'a [SelectItem], - /// Initial search query (pre-filled in interactive, used as filter in non-interactive). - pub query: Option<&'a str>, - /// Header line rendered above the list (e.g. an error message). - pub header: Option<&'a str>, - /// Prompt line rendered above the list in interactive mode. - pub prompt: &'a str, - /// Max visible rows (interactive only). - pub page_size: usize, -} - -/// Result of an interactive selection. -pub enum SelectResult { - /// The user selected an item. - Selected, - /// The user cancelled the selection (e.g. Ctrl+C). - Cancelled, -} - -/// Show a task selection list. -/// -/// In [`Mode::Interactive`], enters a terminal UI with fuzzy search and -/// keyboard navigation. `after_render` is called after every render with the -/// current visible state (useful for emitting test milestones). On Enter, -/// `*selected_index` is set to the chosen item's index in the original -/// `items` slice. Returns [`SelectResult::Cancelled`] if the user presses -/// Ctrl+C. -/// -/// In [`Mode::NonInteractive`], renders the list once to `writer` and -/// returns [`SelectResult::Selected`]. `page_size` and `after_render` are -/// ignored. -/// -/// # Errors -/// -/// Returns an error if terminal I/O fails. -pub fn select_list( - writer: &mut impl Write, - params: &SelectParams<'_>, - mode: Mode<'_>, - after_render: impl FnMut(&RenderState<'_>), -) -> anyhow::Result { - match mode { - Mode::Interactive { selected_index } => interactive::run( - params.items, - params.query, - selected_index, - params.header, - params.prompt, - params.page_size, - after_render, - ), - Mode::NonInteractive => { - non_interactive(writer, params.items, params.query, params.header)?; - Ok(SelectResult::Selected) - } - } -} - -fn non_interactive( - writer: &mut impl Write, - items: &[SelectItem], - query: Option<&str>, - header: Option<&str>, -) -> anyhow::Result<()> { - let display_rows = build_display_rows(items, query.unwrap_or_default()); - - // When there are no matching items, just print the header (if any) and - // return early — avoids showing a redundant "No matching tasks." line - // after a "not found" header. - let has_items = display_rows.iter().any(interactive::DisplayRow::is_item); - if !has_items { - if let Some(h) = header { - writeln!(writer, "{h}")?; - } - return Ok(()); - } - - let row_count = display_rows.len(); - - render_items( - writer, - &RenderParams { - items, - display_rows: &display_rows, - selected: None, - visible_row_range: 0..row_count, - hidden_count: 0, - header, - prompt: "", - query: None, - show_group_headers: false, - line_ending: "\n", - max_line_width: usize::MAX, - }, - )?; - - Ok(()) -} diff --git a/crates/vite_shell/Cargo.toml b/crates/vite_shell/Cargo.toml deleted file mode 100644 index 243a849f1..000000000 --- a/crates/vite_shell/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "vite_shell" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -wincode = { workspace = true, features = ["derive"] } -brush-parser = { workspace = true } -diff-struct = { workspace = true } -serde = { workspace = true, features = ["derive"] } -shell-escape = { workspace = true } -vite_str = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/vite_shell/src/lib.rs b/crates/vite_shell/src/lib.rs deleted file mode 100644 index c6c9629af..000000000 --- a/crates/vite_shell/src/lib.rs +++ /dev/null @@ -1,329 +0,0 @@ -use std::{collections::BTreeMap, fmt::Display, ops::Range}; - -use brush_parser::{ - Parser, ParserImpl, ParserOptions, - ast::{ - AndOr, Assignment, AssignmentName, AssignmentValue, Command, CommandPrefix, - CommandPrefixOrSuffixItem, CommandSuffix, CompoundListItem, Pipeline, Program, - SeparatorOperator, SimpleCommand, SourceLocation, Word, - }, - word::{WordPiece, WordPieceWithSource}, -}; -use diff::Diff; -use serde::{Deserialize, Serialize}; -use vite_str::Str; -use wincode::{SchemaRead, SchemaWrite}; - -/// "FOO=BAR program arg1 arg2" -#[derive(SchemaWrite, SchemaRead, Serialize, Deserialize, Debug, PartialEq, Eq, Diff, Clone)] -#[diff(attr(#[derive(Debug)]))] -pub struct TaskParsedCommand { - pub envs: BTreeMap, - pub program: Str, - pub args: Vec, -} - -impl Display for TaskParsedCommand { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // BTreeMap ensures stable iteration order - for (name, value) in &self.envs { - Display::fmt( - &format_args!("{}={} ", name, shell_escape::escape(value.as_str().into())), - f, - )?; - } - Display::fmt(&shell_escape::escape(self.program.as_str().into()), f)?; - for arg in &self.args { - Display::fmt(" ", f)?; - Display::fmt(&shell_escape::escape(arg.as_str().into()), f)?; - } - - Ok(()) - } -} - -/// Parser options matching those used in [`try_parse_as_and_list`]. -const PARSER_OPTIONS: ParserOptions = ParserOptions { - enable_extended_globbing: false, - posix_mode: true, - sh_mode: true, - tilde_expansion_at_word_start: false, - tilde_expansion_after_colon: false, - parser_impl: ParserImpl::Peg, -}; - -/// Remove shell quoting from a word value, respecting quoting context. -/// -/// Uses `brush_parser::word::parse` to properly handle nested quoting -/// (e.g. single quotes inside double quotes are preserved as literal characters). -/// Returns `None` if the word contains expansions that cannot be statically resolved -/// (parameter expansion, command substitution, arithmetic). -fn unquote(word: &Word) -> Option { - let Word { value, loc: _ } = word; - let pieces = brush_parser::word::parse(value.as_str(), &PARSER_OPTIONS).ok()?; - let mut result = Str::with_capacity(value.len()); - flatten_pieces(&pieces, &mut result)?; - Some(result) -} - -/// Recursively extract literal text from parsed word pieces. -/// -/// Returns `None` if any piece requires runtime expansion. -fn flatten_pieces(pieces: &[WordPieceWithSource], result: &mut Str) -> Option<()> { - for piece in pieces { - match &piece.piece { - WordPiece::Text(s) | WordPiece::SingleQuotedText(s) | WordPiece::AnsiCQuotedText(s) => { - result.push_str(s); - } - // EscapeSequence contains the raw sequence (e.g. `\"` as two chars); - // the escaped character is everything after the leading backslash. - WordPiece::EscapeSequence(s) => { - result.push_str(s.strip_prefix('\\').unwrap_or(s)); - } - WordPiece::DoubleQuotedSequence(inner) - | WordPiece::GettextDoubleQuotedSequence(inner) => { - flatten_pieces(inner, result)?; - } - // Tilde prefix, parameter expansion, command substitution, arithmetic - // cannot be statically resolved — bail out. - _ => return None, - } - } - Some(()) -} - -fn pipeline_to_command(pipeline: &Pipeline) -> Option<(TaskParsedCommand, Range)> { - let location = pipeline.location()?; - let range = location.start.index..location.end.index; - - let Pipeline { timed: None, bang: false, seq } = pipeline else { - return None; - }; - let [Command::Simple(simple_command)] = seq.as_slice() else { - return None; - }; - let SimpleCommand { prefix, word_or_name: Some(program), suffix } = simple_command else { - return None; - }; - let mut envs = BTreeMap::::new(); - if let Some(prefix) = prefix { - let CommandPrefix(items) = prefix; - for item in items { - let CommandPrefixOrSuffixItem::AssignmentWord( - Assignment { name, value, append: false, loc: _ }, - _, - ) = item - else { - return None; - }; - let AssignmentName::VariableName(name) = name else { - return None; - }; - let AssignmentValue::Scalar(value) = value else { - return None; - }; - envs.insert(name.as_str().into(), unquote(value)?); - } - } - let mut args = Vec::::new(); - if let Some(CommandSuffix(suffix_items)) = suffix { - for suffix_item in suffix_items { - let CommandPrefixOrSuffixItem::Word(word) = suffix_item else { - return None; - }; - args.push(unquote(word)?); - } - } - Some((TaskParsedCommand { envs, program: unquote(program)?, args }, range)) -} - -#[must_use] -pub fn try_parse_as_and_list(cmd: &str) -> Option)>> { - let mut parser = Parser::new(cmd.as_bytes(), &PARSER_OPTIONS); - let Program { complete_commands } = parser.parse_program().ok()?; - let [compound_list] = complete_commands.as_slice() else { - return None; - }; - let [CompoundListItem(and_or_list, SeparatorOperator::Sequence)] = compound_list.0.as_slice() - else { - return None; - }; - - let mut commands = Vec::<(TaskParsedCommand, Range)>::new(); - commands.push(pipeline_to_command(&and_or_list.first)?); - for and_or in &and_or_list.additional { - let AndOr::And(pipeline) = and_or else { - return None; - }; - commands.push(pipeline_to_command(pipeline)?); - } - Some(commands) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_single_command() { - let source = r"A=B hello world"; - let list = try_parse_as_and_list(source).unwrap(); - assert_eq!(list.len(), 1); - let (cmd, range) = &list[0]; - assert_eq!(&source[range.clone()], source); - assert_eq!( - cmd, - &TaskParsedCommand { - envs: [("A".into(), "B".into())].into(), - program: "hello".into(), - args: vec!["world".into()], - } - ); - } - - #[test] - fn test_parse_command() { - let source = r#"A=B hello world && FOO="BE\"R" program "arg1" "arg\"2" && zzz"#; - let list = try_parse_as_and_list(source).unwrap(); - - let commands = list.iter().map(|(cmd, _)| cmd).collect::>(); - assert_eq!( - commands, - vec![ - &TaskParsedCommand { - envs: [("A".into(), "B".into())].into(), - program: "hello".into(), - args: vec!["world".into()], - }, - &TaskParsedCommand { - envs: [("FOO".into(), "BE\"R".into())].into(), - program: "program".into(), - args: vec!["arg1".into(), "arg\"2".into()], - }, - &TaskParsedCommand { envs: [].into(), program: "zzz".into(), args: vec![] } - ] - ); - - let substrs = list.iter().map(|(_, range)| &source[range.clone()]).collect::>(); - - assert_eq!( - substrs, - vec!["A=B hello world", r#"FOO="BE\"R" program "arg1" "arg\"2""#, "zzz"] - ); - } - - #[test] - fn test_task_parsed_command_stable_env_ordering() { - // Test that environment variables maintain stable ordering - let cmd = TaskParsedCommand { - envs: [ - ("ZEBRA".into(), "last".into()), - ("ALPHA".into(), "first".into()), - ("MIDDLE".into(), "middle".into()), - ] - .into(), - program: "test".into(), - args: vec![], - }; - - // Convert to string multiple times and verify it's always the same - let str1 = cmd.to_string(); - let str2 = cmd.to_string(); - let str3 = cmd.to_string(); - - assert_eq!(str1, str2); - assert_eq!(str2, str3); - - // Verify the order is alphabetical (BTreeMap sorts by key) - assert!(str1.starts_with("ALPHA=first MIDDLE=middle ZEBRA=last")); - } - - #[test] - fn test_unquote_preserves_nested_quotes() { - // Single quotes inside double quotes are preserved - let cmd = r#"echo "hello 'world'""#; - let list = try_parse_as_and_list(cmd).unwrap(); - assert_eq!(list[0].0.args[0].as_str(), "hello 'world'"); - - // Double quotes inside single quotes are preserved - let cmd = r#"echo 'hello "world"'"#; - let list = try_parse_as_and_list(cmd).unwrap(); - assert_eq!(list[0].0.args[0].as_str(), "hello \"world\""); - - // Backslash escaping in double quotes - let cmd = r#"echo "hello\"world""#; - let list = try_parse_as_and_list(cmd).unwrap(); - assert_eq!(list[0].0.args[0].as_str(), "hello\"world"); - - // Backslash escaping outside quotes - let cmd = r"echo hello\ world"; - let list = try_parse_as_and_list(cmd).unwrap(); - assert_eq!(list[0].0.args[0].as_str(), "hello world"); - } - - #[test] - fn test_flatten_pieces_recursion() { - fn parse_and_flatten(input: &str) -> Option { - let pieces = brush_parser::word::parse(input, &PARSER_OPTIONS).ok()?; - let mut result = Str::default(); - flatten_pieces(&pieces, &mut result)?; - Some(result) - } - - // DoubleQuotedSequence containing Text + EscapeSequence + Text - assert_eq!(parse_and_flatten(r#""hello\"world""#).unwrap(), "hello\"world"); - - // DoubleQuotedSequence with single quotes preserved as literal text - assert_eq!(parse_and_flatten(r#""it's a 'test'""#).unwrap(), "it's a 'test'"); - - // Nested escape sequences inside double quotes - assert_eq!(parse_and_flatten(r#""a\\b""#).unwrap(), "a\\b"); - - // DoubleQuotedSequence bails on parameter expansion inside - assert!(parse_and_flatten(r#""hello $VAR""#).is_none()); - - // DoubleQuotedSequence bails on command substitution inside - assert!(parse_and_flatten(r#""hello $(cmd)""#).is_none()); - } - - #[test] - fn test_parse_urllib_prepare() { - let cmd = r#"node -e "const v = parseInt(process.versions.node, 10); if (v >= 20) require('child_process').execSync('vp config', {stdio: 'inherit'});""#; - let result = try_parse_as_and_list(cmd); - let (parsed, _) = &result.as_ref().unwrap()[0]; - // Single quotes inside double quotes must be preserved as literal characters - assert_eq!( - parsed.args[1].as_str(), - "const v = parseInt(process.versions.node, 10); if (v >= 20) require('child_process').execSync('vp config', {stdio: 'inherit'});" - ); - } - - #[test] - fn test_task_parsed_command_serialization_stability() { - // Create a command with multiple environment variables - let cmd = TaskParsedCommand { - envs: [ - ("VAR_C".into(), "value_c".into()), - ("VAR_A".into(), "value_a".into()), - ("VAR_B".into(), "value_b".into()), - ] - .into(), - program: "program".into(), - args: vec!["arg1".into(), "arg2".into()], - }; - - // Serialize multiple times - let bytes1 = wincode::serialize(&cmd).unwrap(); - let bytes2 = wincode::serialize(&cmd).unwrap(); - - // Verify serialization is stable - assert_eq!(bytes1, bytes2); - - // Verify deserialization works and maintains order - let decoded: TaskParsedCommand = wincode::deserialize(&bytes1).unwrap(); - assert_eq!(decoded, cmd); - - // Verify the decoded command still has stable string representation - assert_eq!(decoded.to_string(), cmd.to_string()); - } -} diff --git a/crates/vite_str/Cargo.toml b/crates/vite_str/Cargo.toml deleted file mode 100644 index e0f50bee4..000000000 --- a/crates/vite_str/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "vite_str" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -wincode = { workspace = true } -compact_str = { workspace = true, features = ["serde"] } -diff-struct = { workspace = true } -serde = { workspace = true, features = ["derive"] } -ts-rs = { workspace = true, optional = true, features = ["no-serde-warnings"] } - -[features] -ts-rs = ["dep:ts-rs"] - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/vite_str/README.md b/crates/vite_str/README.md deleted file mode 100644 index 5226c8b50..000000000 --- a/crates/vite_str/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# vite_str - -`vite_str::Str` is an owned string type that stores smaller strings on the stack. - -Why introduce a new type instead of just using `CompactString`: - -- There are lots of [string type implementations](https://github.com/rosetta-rs/string-rosetta-rs). We want to swap them easily if we found a better implementation for our usage. -- We need a string type that implements `bincode::{Encode, Decode}`. None of existing implementations has that. diff --git a/crates/vite_str/src/lib.rs b/crates/vite_str/src/lib.rs deleted file mode 100644 index 2237ad101..000000000 --- a/crates/vite_str/src/lib.rs +++ /dev/null @@ -1,223 +0,0 @@ -#[expect(clippy::disallowed_types, reason = "vite_str defines Str using std types internally")] -use std::{ - borrow::Borrow, - ffi::OsStr, - fmt::{Debug, Display}, - mem::MaybeUninit, - ops::Deref, - path::Path, - sync::Arc, -}; - -use compact_str::CompactString; -#[doc(hidden)] // for `format` macro only -pub use compact_str::format_compact; -use diff::Diff; -use serde::{Deserialize, Serialize}; -use wincode::{ - SchemaRead, SchemaWrite, - config::Config, - error::{ReadResult, WriteResult}, - io::{Reader, Writer}, -}; - -#[macro_export] -macro_rules! format { - ($($arg:tt)*) => { - $crate::Str::from($crate::format_compact!($($arg)*)) - }; -} - -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash, PartialOrd, Ord)] -#[serde(transparent)] -pub struct Str(CompactString); - -impl Diff for Str { - type Repr = Option; - - fn diff(&self, other: &Self) -> Self::Repr { - if self == other { None } else { Some(other.clone()) } - } - - fn apply(&mut self, diff: &Self::Repr) { - if let Some(diff) = diff { - *self = diff.clone(); - } - } - - fn identity() -> Self { - Self::default() - } -} - -impl Str { - #[must_use] - pub fn with_capacity(capacity: usize) -> Self { - Self(CompactString::with_capacity(capacity)) - } - - #[must_use] - pub fn as_str(&self) -> &str { - self.0.as_str() - } - - pub fn push(&mut self, ch: char) { - self.0.push(ch); - } - - pub fn pop(&mut self) -> Option { - self.0.pop() - } - - pub fn push_str(&mut self, s: &str) { - self.0.push_str(s); - } -} - -impl AsRef for Str { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} -#[expect(clippy::disallowed_types, reason = "vite_str provides Path interop via AsRef")] -impl AsRef for Str { - #[expect(clippy::disallowed_types, reason = "fn signature uses std Path")] - fn as_ref(&self) -> &Path { - self.0.as_ref() - } -} -impl AsRef for Str { - fn as_ref(&self) -> &OsStr { - self.0.as_ref() - } -} -impl Borrow for Str { - fn borrow(&self) -> &str { - self.0.borrow() - } -} -impl Deref for Str { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Display for Str { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.0, f) - } -} -impl Debug for Str { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&self.0, f) - } -} - -// SAFETY: Delegates to `str`'s SchemaWrite impl, preserving its size/write invariants. -unsafe impl SchemaWrite for Str { - type Src = Self; - - fn size_of(src: &Self::Src) -> WriteResult { - >::size_of(src.as_str()) - } - - fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> { - >::write(writer, src.as_str()) - } -} - -// SAFETY: Delegates to `&str`'s SchemaRead impl and wraps the result; dst is initialized on Ok. -unsafe impl<'de, C: Config> SchemaRead<'de, C> for Str { - type Dst = Self; - - fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit) -> ReadResult<()> { - let s: &str = <&str as SchemaRead<'de, C>>::get(&mut reader)?; - dst.write(Self(CompactString::from(s))); - Ok(()) - } -} - -impl From<&str> for Str { - fn from(value: &str) -> Self { - Self(value.into()) - } -} - -#[expect(clippy::disallowed_types, reason = "vite_str provides String conversion via From")] -impl From for Str { - #[expect(clippy::disallowed_types, reason = "fn signature uses std String")] - fn from(value: String) -> Self { - Self(value.into()) - } -} - -impl From for Str { - fn from(value: CompactString) -> Self { - Self(value) - } -} - -impl From for Arc { - fn from(value: Str) -> Self { - Self::from(value.as_str()) - } -} - -impl PartialEq<&str> for Str { - fn eq(&self, other: &&str) -> bool { - self.0 == other - } -} -impl PartialEq for Str { - fn eq(&self, other: &str) -> bool { - self.0 == other - } -} - -#[cfg(feature = "ts-rs")] -mod ts_impl { - use ts_rs::TS; - - use super::Str; - - #[expect(clippy::disallowed_types, reason = "ts_rs::TS trait requires returning String")] - impl TS for Str { - type OptionInnerType = Self; - type WithoutGenerics = Self; - - fn name(_cfg: &ts_rs::Config) -> String { - "string".to_owned() - } - - fn inline(_cfg: &ts_rs::Config) -> String { - "string".to_owned() - } - - fn inline_flattened(_cfg: &ts_rs::Config) -> String { - panic!("Str cannot be flattened") - } - - fn decl(_cfg: &ts_rs::Config) -> String { - panic!("Str is a primitive type") - } - - fn decl_concrete(_cfg: &ts_rs::Config) -> String { - panic!("Str is a primitive type") - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_str_encode_decode() { - let original = Str::from("Hello, World!"); - let encoded = wincode::serialize(&original).unwrap(); - let decoded: Str = wincode::deserialize(&encoded).unwrap(); - assert_eq!(original, decoded); - } -} diff --git a/crates/vite_task/Cargo.toml b/crates/vite_task/Cargo.toml deleted file mode 100644 index 77a9771ef..000000000 --- a/crates/vite_task/Cargo.toml +++ /dev/null @@ -1,81 +0,0 @@ -[package] -name = "vite_task" -version = "0.0.0" -edition.workspace = true -include = ["/src", "/build.rs"] -license.workspace = true -publish = false -readme = "README.md" -rust-version.workspace = true - -[lints] -workspace = true - -[dependencies] -anstream = { workspace = true } -anyhow = { workspace = true } -async-trait = { workspace = true } -wincode = { workspace = true, features = ["derive"] } -clap = { workspace = true, features = ["derive"] } -ctrlc = { workspace = true } -derive_more = { workspace = true, features = ["debug", "from"] } -futures-util = { workspace = true } -once_cell = { workspace = true } -owo-colors = { workspace = true } -petgraph = { workspace = true } -pty_terminal_test_client = { workspace = true } -rayon = { workspace = true } -rusqlite = { workspace = true, features = ["bundled"] } -rustc-hash = { workspace = true } -serde = { workspace = true, features = ["derive", "rc"] } -serde_json = { workspace = true } -supports-color = { workspace = true } -thiserror = { workspace = true } -tar = { workspace = true } -tokio = { workspace = true, features = [ - "rt-multi-thread", - "io-std", - "io-util", - "macros", - "process", - "sync", -] } -tokio-util = { workspace = true } -tracing = { workspace = true } -twox-hash = { workspace = true } -materialized_artifact = { workspace = true } -uuid = { workspace = true, features = ["v4"] } -vite_glob = { workspace = true } -vite_path = { workspace = true } -vite_select = { workspace = true } -vite_str = { workspace = true } -vite_task_graph = { workspace = true } -vite_task_ipc_shared = { workspace = true } -vite_task_plan = { workspace = true } -vite_task_server = { workspace = true } -vite_workspace = { workspace = true } -wax = { workspace = true } -zstd = { workspace = true } - -# Artifact build-deps must be unconditional: cargo's resolver panics when -# `artifact = "cdylib"` deps live under a `[target.cfg.build-dependencies]` -# block on cross-compile. -[build-dependencies] -anyhow = { workspace = true } -materialized_artifact_build = { workspace = true } -vite_task_client_napi = { workspace = true } - -[dev-dependencies] -tempfile = { workspace = true } - -[target.'cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))'.dependencies] -fspy = { workspace = true } - -[target.'cfg(unix)'.dependencies] -nix = { workspace = true } - -[target.'cfg(windows)'.dependencies] -winapi = { workspace = true, features = ["handleapi", "jobapi2", "winnt"] } - -[lib] -doctest = false diff --git a/crates/vite_task/README.md b/crates/vite_task/README.md deleted file mode 100644 index cafc1b6e5..000000000 --- a/crates/vite_task/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# vite_task - -This is the top-level library crate that orchestrates the full task lifecycle: loading the task graph, planning execution, running or replaying tasks , and reporting results. - -It is consumed by two binaries: - -- **[Vite+](https://github.com/voidzero-dev/vite-plus)** — the official product, where it powers `vp run` -- **[`vite_task_bin`](../vite_task_bin)** — internal `vt` CLI for developing and testing this repo without the full Vite+ stack diff --git a/crates/vite_task/build.rs b/crates/vite_task/build.rs deleted file mode 100644 index 4f1fbe191..000000000 --- a/crates/vite_task/build.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![expect( - clippy::disallowed_types, - clippy::disallowed_macros, - reason = "build.rs interfaces with std::path and cargo's env-var API" -)] - -use std::{env, path::Path}; - -use anyhow::Context; - -// Why `cfg(fspy)` instead of matching on `target_os` directly at each use site: -// "fspy is available" is a single semantic predicate, but the underlying reason -// (the `fspy` crate builds on windows/macos/linux) is a three-OS list that -// would otherwise have to be repeated — as `any(target_os = "windows", "macos", -// "linux")` — everywhere `fspy::*` is touched. Naming it `fspy` keeps the -// source self-documenting: code reads `#[cfg(fspy)]` instead of a disjunction -// over OSes. The OS allowlist lives in two spots that must stay in sync: this -// file (for the rustc cfg) and the target-scoped dep block in Cargo.toml -// (which Cargo resolves before build.rs runs, so it can't reuse this cfg). -fn main() -> anyhow::Result<()> { - println!("cargo::rustc-check-cfg=cfg(fspy)"); - println!("cargo::rerun-if-changed=build.rs"); - - let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); - if matches!(target_os.as_str(), "windows" | "macos" | "linux") { - println!("cargo::rustc-cfg=fspy"); - } - - let env_name = "CARGO_CDYLIB_FILE_VITE_TASK_CLIENT_NAPI"; - println!("cargo:rerun-if-env-changed={env_name}"); - let dylib_path = env::var_os(env_name).with_context(|| format!("{env_name} not set"))?; - materialized_artifact_build::register("vite_task_client_napi", Path::new(&dylib_path)); - Ok(()) -} diff --git a/crates/vite_task/docs/boolean-flags.md b/crates/vite_task/docs/boolean-flags.md deleted file mode 100644 index 2c1062e10..000000000 --- a/crates/vite_task/docs/boolean-flags.md +++ /dev/null @@ -1,54 +0,0 @@ -# Boolean Flags in Vite Task - -This document describes how boolean flags work in `vp` commands. - -## Available Boolean Flags - -### Run Command Flags - -- `--recursive` / `-r` — Run task in all packages in the workspace -- `--transitive` / `-t` — Run task in the current package and its transitive dependencies -- `--workspace-root` / `-w` — Run task in the workspace root package -- `--ignore-depends-on` — Skip explicit `dependsOn` dependencies -- `--verbose` / `-v` — Show full detailed summary after execution -- `--cache` / `--no-cache` — Force caching on or off for all tasks and scripts - -### Negation Pattern - -The `--cache` flag supports a `--no-cache` negation form. When `--no-cache` is used, caching is explicitly disabled for all tasks in that run: - -```bash -# Force caching off -vp run build --no-cache - -# Force caching on (even for scripts that default to uncached) -vp run build --cache -``` - -The positive and negative forms are mutually exclusive — you cannot use both `--cache` and `--no-cache` in the same command. - -## Examples - -```bash -# Recursive build (all packages in dependency order) -vp run build -r - -# Current package + transitive dependencies -vp run build -t - -# Run in workspace root -vp run build -w - -# Skip explicit dependsOn edges -vp run build --ignore-depends-on - -# Verbose output -vp run build -v - -# Force caching off for this run -vp run build --no-cache -``` - -## Implementation Details - -The flags use clap's argument parsing. The `--cache`/`--no-cache` pair uses clap's `conflicts_with` attribute to ensure they cannot be used together. diff --git a/crates/vite_task/docs/task-cache.md b/crates/vite_task/docs/task-cache.md deleted file mode 100644 index b05ac6894..000000000 --- a/crates/vite_task/docs/task-cache.md +++ /dev/null @@ -1,583 +0,0 @@ -# Task Cache - -Vite Task implements a caching system to avoid re-running tasks when their inputs haven't changed. This document describes the architecture, design decisions, and implementation details of the task cache system. - -## Overview - -The task cache system enables: - -- **Incremental builds**: Only run tasks when inputs have changed -- **Shared caching**: Multiple tasks with identical commands can share cache entries -- **Content-based hashing**: Cache keys based on actual content, not timestamps -- **Output replay**: Cached stdout/stderr are replayed exactly as originally produced -- **Two-tier caching**: Cache entries shared across tasks, with task-run associations -- **Configurable input**: Control which files are tracked for cache invalidation - -### Shared caching - -For tasks defined as below: - -```jsonc -// package.json -{ - "scripts": { - "build": "echo $foo", - "test": "echo $foo && echo $bar", - }, -} -``` - -the task cache system is able to hit the same cache for the `test` task and for the first subcommand in the `build` task: - -1. user runs `vp run build` -> no cache hit. run `echo $foo` and create cache -2. user runs `vp run test` - 1. `echo $foo` -> **hit cache created in step 1 and replay** - 2. `echo $bar` -> no cache hit. run `echo $bar` and create cache -3. user changes env `$foo` -4. user runs `vp run test` - 1. `echo $foo` - 1. the cache system should be able to **locate the cache that was created in step 1 and hit in step 2.1** - 2. compare the spawn fingerprint and report cache miss because `$foo` is changed. - 3. re-run and replace the cache with a new one. - 2. `echo $bar` -> hit cache created in step 2.2 and replay -5. user runs `vp run build`: **hit the cache created in step 4.1.3 and replay**. - -## Architecture - -``` -┌──────────────────────────────────────────────────────────────────────────────────────┐ -│ Task Execution Flow │ -├──────────────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. Task Request │ -│ ──────────────── │ -│ app#build │ -│ │ │ -│ ▼ │ -│ 2. Cache Key Generation │ -│ ────────────────────── │ -│ • Spawn fingerprint (cwd, program, args, env) │ -│ • Input configuration │ -│ │ │ -│ ▼ │ -│ 3. Cache Lookup (SQLite) │ -│ ──────────────────────── │ -│ ┌─────────────────┬──────────────────────┐──────────────────────────┐ │ -│ │ Cache Hit │ Cache Not Found │ Cache Found but Miss │ │ -│ └────────┬────────┴─────────┬────────────┘──────────────────┬───────┘ │ -│ │ │ │ │ -│ ▼ ▼ ▼ │ -│ 4a. Validate Fingerprint 4b. Execute Task ◀───── 4c. Report what changed │ -│ ──────────────────────── ──────────────── │ -│ • Inputs unchanged? • Run command │ -│ • Spawn config same? • Monitor files (fspy) │ -│ • Capture stdout/stderr │ -│ │ │ │ -│ ▼ ▼ │ -│ 5a. Replay Outputs 5b. Store in Cache │ -│ ────────────────── ────────────────── │ -│ • Write to stdout • Save fingerprint │ -│ • Write to stderr • Save outputs │ -│ • Update database │ -│ │ -└──────────────────────────────────────────────────────────────────────────────────────┘ -``` - -## Cache Key Components - -### 1. Cache Entry Key - -The cache entry key uniquely identifies a command execution context: - -```rust -pub struct CacheEntryKey { - pub spawn_fingerprint: SpawnFingerprint, - pub input_config: ResolvedInputConfig, -} -``` - -### 2. Spawn Fingerprint - -The spawn fingerprint captures the complete execution context: - -```rust -pub struct SpawnFingerprint { - pub cwd: RelativePathBuf, - pub program_fingerprint: ProgramFingerprint, - pub args: Arc<[Str]>, - pub env_fingerprints: EnvFingerprints, -} - -pub struct EnvFingerprints { - pub fingerprinted_envs: BTreeMap, - pub untracked_env_config: Arc<[Str]>, -} - -pub struct EnvValueHash([u8; 32]); - -enum ProgramFingerprint { - OutsideWorkspace { program_name: Str }, - InsideWorkspace { relative_program_path: RelativePathBuf }, -} -``` - -This ensures cache invalidation when: - -- Working directory changes (package location changes) -- Command or arguments change -- Declared environment variables differ (untracked envs don't affect cache) -- Program location changes (inside/outside workspace) - -### 3. Environment Variable Impact on Cache - -The `fingerprinted_envs` field in `EnvFingerprints` is crucial for cache correctness: - -- Only includes env vars explicitly declared in the task's `env` array -- Does NOT include untracked envs (PATH, CI, etc.) -- These env var names and SHA-256 value hashes become part of the cache key - -When a task runs: - -1. All env vars (including untracked) are available to the process -2. Only declared env vars affect the cache key -3. If a declared env var changes value, cache will miss -4. If an untracked env changes, cache will still hit - -The `untracked_env_config` field stores env names (not values) — if the set of untracked env names changes, the cache invalidates, but value changes don't. - -### 4. Execution Cache Key - -The execution cache key associates a task identity with its cache entry: - -```rust -pub enum ExecutionCacheKey { - UserTask { - task_name: Str, - and_item_index: usize, - extra_args: Arc<[Str]>, - package_path: RelativePathBuf, - }, - ExecAPI(Arc<[Str]>), -} -``` - -### 5. Cache Entry Value - -The cached execution result: - -```rust -pub struct CacheEntryValue { - pub post_run_fingerprint: PostRunFingerprint, - pub std_outputs: Arc<[StdOutput]>, - pub duration: Duration, - pub globbed_inputs: BTreeMap, -} -``` - -### 6. Input File Tracking - -Vite Task uses `fspy` to monitor file system access during task execution: - -``` -┌──────────────────────────────────────────────────────────────┐ -│ File System Monitoring │ -├──────────────────────────────────────────────────────────────┤ -│ │ -│ Task Execution: │ -│ ────────────── │ -│ 1. Start fspy monitoring │ -│ 2. Execute task command │ -│ 3. Capture accessed files │ -│ 4. Stop monitoring │ -│ │ │ -│ ▼ │ -│ Fingerprint Generation: │ -│ ────────────────────── │ -│ For each accessed file: │ -│ • Check if file exists │ -│ • If file: Hash contents with xxHash3 │ -│ • If directory: Record structure │ -│ • If missing: Mark as NotFound │ -│ │ │ -│ ▼ │ -│ Path Fingerprint Types: │ -│ ────────────────────── │ -│ enum PathFingerprint { │ -│ NotFound, // File doesn't exist │ -│ FileContentHash(u64), // xxHash3 of content │ -│ Folder(Option>), │ -│ } ▲ │ -│ │ │ -│ This value is `None` when fspy reports that the task is │ -│ opening a folder but not reading its entries. This can │ -│ happen when the opened folder is used as a dirfd for │ -│ `openat(2)`. In such case, the folder's entries don't need │ -│ to be fingerprinted. │ -│ Folders with empty entries fingerprinted are represented as │ -│ `Folder(Some(empty BTreeMap))`. │ -│ │ -└──────────────────────────────────────────────────────────────┘ -``` - -### 7. Inputs Configuration - -The `input` field in `vite-task.json` controls which files are tracked for cache fingerprinting: - -```json -{ - "tasks": { - "build": { - "input": ["src/**", "!dist/**", { "auto": true }] - } - } -} -``` - -- **Omitted** (default): `[{auto: true}]` — automatically tracks which files the task reads via `fspy` -- **`[]`** (empty array): disables file tracking entirely -- **Glob patterns** (e.g. `"src/**"`): select specific files -- **`{auto: true}`**: enables automatic file tracking -- **Negative patterns** (e.g. `"!dist/**"`): exclude matched files - -See [inputs.md](../../../docs/inputs.md) for full details. - -### 8. Fingerprint Validation - -When a cache entry exists, the fingerprint is validated to detect changes: - -```rust -pub enum FingerprintMismatch { - SpawnFingerprint { old: SpawnFingerprint, new: SpawnFingerprint }, - InputConfig, - InputChanged { kind: InputChangeKind, path: RelativePathBuf }, -} - -pub enum InputChangeKind { - ContentModified, - Added, - Removed, -} -``` - -## Cache Storage - -### Storage Backend - -Vite Task uses SQLite with WAL (Write-Ahead Logging) mode for cache storage: - -```rust -// Database initialization -let conn = Connection::open(cache_path)?; -conn.pragma_update(None, "journal_mode", "WAL")?; // Better concurrency -conn.pragma_update(None, "synchronous", "NORMAL")?; // Balance speed/safety -``` - -### Database Schema - -```sql --- Cache entries keyed by spawn fingerprint + input config -CREATE TABLE cache_entries ( - key BLOB PRIMARY KEY, -- Serialized CacheEntryKey - value BLOB -- Serialized CacheEntryValue -); - --- Maps task identity to its cache entry key -CREATE TABLE task_fingerprints ( - key BLOB PRIMARY KEY, -- Serialized ExecutionCacheKey - value BLOB -- Serialized CacheEntryKey -); -``` - -### Serialization - -Cache entries are serialized using `bincode` for efficient storage. - -## Cache Operations - -### Cache Hit Flow - -``` -┌──────────────────────────────────────────────────────────────┐ -│ Cache Hit Process │ -├──────────────────────────────────────────────────────────────┤ -│ │ -│ 1. Generate Cache Keys │ -│ ────────────────────── │ -│ CacheEntryKey { │ -│ spawn_fingerprint: SpawnFingerprint { ... }, │ -│ input_config: ResolvedInputConfig { ... }, │ -│ } │ -│ ExecutionCacheKey::UserTask { │ -│ task_name: "build", │ -│ package_path: "packages/app", │ -│ ... │ -│ } │ -│ │ │ -│ ▼ │ -│ 2. Query Cache │ -│ ────────────── │ -│ SELECT value FROM cache_entries WHERE key = ? │ -│ │ │ -│ ▼ │ -│ 3. Validate Post-Run Fingerprint │ -│ ───────────────────────────────── │ -│ • Check input file hashes │ -│ • Detect file content changes, additions, removals │ -│ │ │ -│ ▼ │ -│ 4. Replay Outputs │ -│ ───────────────── │ -│ • Write to stdout/stderr │ -│ • Preserve original order │ -│ │ -└──────────────────────────────────────────────────────────────┘ -``` - -### Cache Miss and Storage - -``` -┌──────────────────────────────────────────────────────────────┐ -│ Cache Miss Process │ -├──────────────────────────────────────────────────────────────┤ -│ │ -│ 1. Execute Task with Monitoring │ -│ ─────────────────────────────── │ -│ • Start fspy file monitoring │ -│ • Capture stdout/stderr │ -│ • Execute command │ -│ • Stop monitoring │ -│ │ │ -│ ▼ │ -│ 2. Generate Post-Run Fingerprint │ -│ ───────────────────────────────── │ -│ • Hash all accessed files │ -│ • Record file system access patterns │ -│ │ │ -│ ▼ │ -│ 3. Create CacheEntryValue │ -│ ──────────────────────────── │ -│ CacheEntryValue { │ -│ post_run_fingerprint, │ -│ std_outputs, │ -│ duration, │ -│ globbed_inputs, │ -│ } │ -│ │ │ -│ ▼ │ -│ 4. Store in Database │ -│ ──────────────────── │ -│ INSERT/UPDATE cache_entries + task_fingerprints │ -│ │ -└──────────────────────────────────────────────────────────────┘ -``` - -## Cache Invalidation - -### Automatic Invalidation - -Cache entries are automatically invalidated when: - -1. **Command changes**: Different command, arguments, or working directory -2. **Package location changes**: Working directory (`cwd`) in spawn fingerprint changes -3. **Environment changes**: Modified declared environment variables (untracked env values don't affect cache) -4. **Untracked env config changes**: Untracked environment names added/removed from configuration -5. **Input files change**: Content hash differs (detected via xxHash3) -6. **File structure changes**: Files added, removed, or type changed -7. **Input config changes**: The `input` configuration itself changes - -## Configuration - -### Cache Location - -The cache database is stored at `node_modules/.vite/task-cache` in the workspace root. - -### Global Cache Control - -The root `vite-task.json` can configure caching for the entire workspace: - -```json -{ - "cache": true, - "tasks": { ... } -} -``` - -- `true` — enables caching for both scripts and tasks -- `false` — disables all caching -- `{ "scripts": false, "tasks": true }` — default; tasks are cached but package.json scripts are not -- `{ "scripts": true, "tasks": true }` — cache everything - -### Task-Level Cache Control - -Individual tasks can enable or disable caching: - -```json -{ - "tasks": { - "build": { - "command": "tsc && rollup -c", - "cache": true, - "dependsOn": ["lint"] - }, - "deploy": { - "command": "deploy-script.sh", - "cache": false - } - } -} -``` - -### CLI Cache Override - -The `--cache` and `--no-cache` flags override all cache configuration for a single run: - -```bash -vp run build --no-cache # force cache off -vp run build --cache # force cache on (even for scripts) -``` - -## Output Capture and Replay - -### Output Capture During Execution - -Outputs are captured exactly as produced: - -- Preserves order of stdout/stderr interleaving -- Handles binary output (e.g., from tools that output progress bars) -- Maintains ANSI color codes and formatting - -### Output Replay on Cache Hit - -When a task hits cache, outputs are replayed exactly: - -``` -┌──────────────────────────────────────────────────────────────┐ -│ Output Replay │ -├──────────────────────────────────────────────────────────────┤ -│ │ -│ Cached Outputs: │ -│ ────────────── │ -│ [ │ -│ StdOutput { kind: StdOut, "Compiling..." }, │ -│ StdOutput { kind: StdErr, "Warning: ..." }, │ -│ StdOutput { kind: StdOut, "✓ Build complete" } │ -│ ] │ -│ │ │ -│ ▼ │ -│ Replay Process: │ -│ ────────────── │ -│ 1. Write "Compiling..." to stdout │ -│ 2. Write "Warning: ..." to stderr │ -│ 3. Write "✓ Build complete" to stdout │ -│ │ │ -│ ▼ │ -│ Result: Identical output as original execution │ -│ │ -└──────────────────────────────────────────────────────────────┘ -``` - -## Performance Optimizations - -### Fast Hashing with xxHash3 - -Vite Task uses xxHash3 for file content hashing, providing excellent performance (~10GB/s on modern CPUs). - -### File System Monitoring - -Instead of scanning all possible input files, `fspy` monitors actual file access: - -``` -Traditional Approach: - Scan all src/**/*.ts files → Hash everything - Problem: Hashes files never accessed - -Vite Task Approach: - Monitor with fspy → Hash only accessed files - Benefit: Minimal work, accurate dependencies -``` - -### SQLite Optimizations - -- WAL mode for better concurrency -- Balanced durability for performance -- Prepared/cached statements for efficiency - -### Binary Serialization - -Using `bincode` for compact, fast serialization with direct storage without text conversion. - -## Best Practices - -### 1. Deterministic Commands - -Ensure commands produce identical outputs for identical inputs: - -```json -// ✅ Good: Deterministic output -{ - "tasks": { - "build": { - "command": "tsc && echo Build complete" - } - } -} -``` - -### 2. Disable Cache for Side Effects - -```json -{ - "tasks": { - "deploy": { - "command": "deploy-to-production.sh", - "cache": false - } - } -} -``` - -### 3. Use `input` for Precise Cache Control - -```json -{ - "tasks": { - "build": { - "input": ["src/**", "tsconfig.json", "!src/**/*.test.ts"] - } - } -} -``` - -### 4. Compound Commands for Granular Caching - -```json -{ - "scripts": { - "build": "tsc && rollup -c && terser dist/bundle.js" - } -} -``` - -Each `&&` separated command is cached independently. If only terser config changes, TypeScript and rollup will hit cache. - -## Implementation Reference - -### Core Cache Components - -``` -crates/vite_task/src/session/ -├── cache/ -│ ├── mod.rs # ExecutionCache, CacheEntryKey/Value, FingerprintMismatch -│ └── display.rs # Cache status display formatting -├── execute/ -│ ├── mod.rs # execute_spawn, SpawnOutcome -│ ├── fingerprint.rs # PostRunFingerprint, PathFingerprint, DirEntryKind -│ └── spawn.rs # spawn_with_tracking, fspy integration -└── reporter/ - └── mod.rs # Reporter traits for cache hit/miss display - -crates/vite_task_plan/src/ -├── cache_metadata.rs # ExecutionCacheKey, SpawnFingerprint, ProgramFingerprint, CacheMetadata -├── envs.rs # EnvFingerprints -└── plan.rs # Planning logic -``` diff --git a/crates/vite_task/docs/task-query.md b/crates/vite_task/docs/task-query.md deleted file mode 100644 index 3544d5be1..000000000 --- a/crates/vite_task/docs/task-query.md +++ /dev/null @@ -1,263 +0,0 @@ -# Task Query - -How `vp run` decides which tasks to run and in what order. - -## The two things we build - -When `vp` starts, it builds two data structures from the workspace: - -1. **Package graph** — which packages depend on which. Built from `package.json` dependency fields. -2. **Task graph** — which tasks exist and their explicit `dependsOn` relationships. Built from `vite.config.*` and `package.json` scripts. - -Both are built once and reused for every query, including nested `vp run` calls inside task scripts. - -### What goes into the task graph - -The task graph contains a node for every task in every package, and edges only for explicit `dependsOn` declarations. - -```jsonc -// packages/app/vite.config.* -{ - "tasks": { - "build": { - "command": "vite build", - "dependsOn": ["@shared/lib#build"], // ← this becomes an edge - }, - }, -} -``` - -``` -Task graph: - - app#build ──dependsOn──> lib#build - app#test - lib#build - lib#test -``` - -Package dependency ordering (app depends on lib) is NOT stored as edges in the task graph. Why not is explained below. - -Object-form `dependsOn` entries are also explicit task dependencies. At startup, -they are resolved against the declaring package's direct `package.json` -dependency fields and materialized as task graph edges: - -```jsonc -// packages/app/vite.config.* -{ - "tasks": { - "test": { - "command": "vitest run", - "dependsOn": [{ "task": "build", "from": ["dependencies", "devDependencies"] }], - }, - }, -} -``` - -If `app` directly depends on `ui` and `shared`, and both packages have `build`, -the task graph contains: - -``` -app#test ──dependsOn──> ui#build -app#test ──dependsOn──> shared#build -``` - -Dependency packages without the requested task are skipped. Recursive expansion -comes from dependency tasks declaring their own `dependsOn` entries. - -## What happens when you run a query - -Every `vp run` command goes through two stages: - -``` -Stage 1: Which packages? Stage 2: Which tasks? - - package graph task graph - + CLI flags ──> + package subgraph - ───────────── + task name - = package subgraph ───────────────── - = execution plan -``` - -### Stage 1: Package selection - -The CLI flags determine which packages participate: - -| Command | What it selects | -| ---------------------------- | --------------------------------------------- | -| `vp run build` | Just the current package | -| `vp run -r build` | All packages | -| `vp run -t build` | Current package + its transitive dependencies | -| `vp run -w build` | The workspace root package | -| `vp run -F app... build` | `app` + its transitive dependencies | -| `vp run -F '!core' -r build` | All packages except `core` | - -The result is a **package subgraph** — the selected packages plus all the dependency edges between them. This subgraph is a subset of the full package graph. - -### Stage 2: Task mapping - -Given the package subgraph and a task name, we build the execution plan: - -1. Find which selected packages have the requested task. -2. For packages that don't have it, reconnect their predecessors to their successors (skip-intermediate, explained below). -3. Map the remaining package nodes to task nodes — this gives us topological ordering. -4. Follow explicit `dependsOn` edges outward from these tasks (may pull in tasks from outside the selected packages). - -The result is the execution plan: which tasks to run and in what order. - -## Why topological edges aren't stored in the task graph - -Consider this workspace: - -``` -Package graph: Tasks each package has: - - app ──> lib ──> core app: build, test - lib: build, test - core: build, test -``` - -If we pre-computed topological edges for `build`, the task graph would have: - -``` -app#build ──> lib#build ──> core#build -``` - -This looks fine for `vp run -r build`. But what about `vp run --filter app --filter core build` (selecting just app and core, skipping lib)? - -The pre-computed edges say `app#build → lib#build → core#build`. But lib isn't selected — so we'd need `app#build → core#build`. That edge doesn't exist in the pre-computed graph. We'd have to recompute it anyway. - -It gets worse. If lib didn't have a `build` task at all, the pre-computed edges would already skip it: `app#build → core#build`. But if you ran `vp run --filter app --filter lib build`, you'd want `app#build → lib#build` — which conflicts with the pre-computed skip. - -The problem is that "which packages are selected" is a per-query decision, and skip-intermediate reconnection depends on that selection. Pre-computed topological edges encode a single global answer that doesn't work for all queries. - -Instead, we compute topological ordering at query time from the package subgraph. The package subgraph already has the right set of packages and edges for the specific query. We just need to map packages to tasks and handle the ones that lack the requested task. - -## Skip-intermediate reconnection - -When a selected package doesn't have the requested task, we bridge across it. - -### Example: middle package lacks the task - -``` -Package subgraph (from --filter top...): - - top ──> middle ──> bottom - -Tasks: - top: has "build" - middle: no "build" - bottom: has "build" -``` - -Step by step: - -1. `top` has `build` → keep it. -2. `middle` has no `build` → connect its predecessors (`top`) directly to its successors (`bottom`), then remove `middle`. -3. `bottom` has `build` → keep it. - -``` -Before reconnection: After reconnection: - - top ──> middle ──> bottom top ──> bottom - -Task execution plan: - - top#build ──> bottom#build -``` - -`bottom#build` runs first, then `top#build`. - -### Example: entry package lacks the task - -``` -Package subgraph (from --filter middle...): - - middle ──> bottom - -Tasks: - middle: no "build" - bottom: has "build" -``` - -`middle` lacks `build`, so we reconnect. It has no predecessors, so there's nothing to bridge. We just remove it. - -``` -Task execution plan: - - bottom#build -``` - -Only `bottom#build` runs. - -### Mutating the subgraph during reconnection - -The package subgraph is already a lightweight `DiGraphMap` — just node indices and edges, not a copy of the full package graph. But reconnection adds bridge edges and removes nodes, and we need those edits to be visible within the same pass. If two consecutive packages lack the task, the second removal needs to see the bridge edge from the first. - -So we clone the `DiGraphMap` once and mutate the clone. We iterate the original (stable node order) while modifying the clone. - -## Explicit dependency expansion - -After mapping the package subgraph to tasks, we follow explicit `dependsOn` edges from the task graph. This can pull in tasks from packages outside the selected set. - -```jsonc -// packages/app/vite.config.* -{ - "tasks": { - "build": { - "dependsOn": ["codegen#generate"], - }, - }, -} -``` - -If you run `vp run --filter app build`, the package subgraph contains only `app`. But `app#build` has a `dependsOn` pointing to `codegen#generate`. The expansion step follows this edge and adds `codegen#generate` to the execution plan, even though `codegen` wasn't in the filter. - -This is intentional — `dependsOn` is an explicit declaration that a task can't run without its dependency. Ignoring it would break the build. (Users can skip this with `--ignore-depends-on`.) - -The expansion follows explicit `dependsOn` edges, including edges materialized from object-form entries. It does not follow topological package edges. Topological ordering comes from the package subgraph — it's already baked into the task execution graph by Stage 2. - -## Nested `vp run` - -A task script can contain `vp run` calls: - -```jsonc -{ - "tasks": { - "ci": { - "command": "vp run -r build && vp run -r test", - }, - }, -} -``` - -Each nested `vp run` goes through the same two stages. It reuses the same package graph and task graph that were built at startup — no reloading. - -The nested query produces its own execution subgraph, which gets embedded inside the parent task's execution plan as an expanded item. - -## Putting it all together - -``` -Startup (once): - workspace files ──> package graph ──> task graph - (dependencies) (tasks + dependsOn edges) - -Per query: - CLI flags ──> PackageQuery - │ - ▼ - package graph ──> package subgraph (selected packages + edges) - │ - ▼ - task graph ────> task execution graph - (map packages to tasks, - skip-intermediate reconnection, - explicit dep expansion) - │ - ▼ - execution plan - (resolve env vars, commands, cwd, - expand nested vp run calls) -``` - -The package graph and task graph are stable. They don't change between queries. Everything query-specific is derived from them on the fly. diff --git a/crates/vite_task/docs/terminologies.md b/crates/vite_task/docs/terminologies.md deleted file mode 100644 index 6118fc52e..000000000 --- a/crates/vite_task/docs/terminologies.md +++ /dev/null @@ -1,35 +0,0 @@ -# Terminologies - -### Task-Related Names - -```jsonc -// package.json -{ - "name": "app", - "scripts": { - "build": "echo build1 && echo build2", - }, -} -``` - -```jsonc -// vite-task.json -{ - "tasks": { - "lint": { - "command": "echo lint" - } -} -``` - -In the example above, `build` and `lint` are **task group names**. A task group may define one task, or multiple tasks separated by `&&`. - -The two task groups generates 3 tasks: - -- `app#build(subcommand 0)` (runs `echo build1`) -- `app#build` (runs `echo build2`) -- `app#lint` (runs `echo lint`) - -These are **task names**. They are for displaying and filtering. - -The user could execute `vp run build` under the `app` package, or execute `vp run app#build` from anywhere. The parameter `build` and `app#build` after `vp run` are **task requests**. They are used to match against task names to determine what tasks to run. diff --git a/crates/vite_task/docs/wildcard-env-patterns.md b/crates/vite_task/docs/wildcard-env-patterns.md deleted file mode 100644 index e1b9cba16..000000000 --- a/crates/vite_task/docs/wildcard-env-patterns.md +++ /dev/null @@ -1,347 +0,0 @@ -# Common Wildcard Pattern for Task Env - -## Executive Summary - -This design document outlines the implementation of wildcard pattern support for environment variable matching in vite-plus task configurations. This feature allows users to specify environment variables using glob-like patterns (e.g., `NODE_*`, `VITE_*`, `MY_APP_*`, `*_PORT`) instead of listing each variable explicitly. - -## Background - -Currently, vite-plus requires explicit listing of environment variables in task configurations: - -```json -{ - "tasks": { - "build": { - "command": "vite build", - "env": ["NODE_ENV", "NODE_OPTIONS", "VITE_API_URL", "VITE_APP_TITLE", "MY_APP_PORT"] - } - } -} -``` - -This approach becomes cumbersome when dealing with multiple environment variables that follow a naming pattern, especially for frameworks like Vite that use prefixed variables (`VITE_*`). - -## Goals - -1. **Simplify Configuration**: Allow wildcard patterns in the `env` array to match multiple environment variables -2. **Maintain Cache Correctness**: Ensure wildcard-matched variables are properly included in cache fingerprints -3. **Backward Compatibility**: Support both explicit variable names and wildcard patterns -4. **Performance**: Minimal overhead when resolving environment variables -5. **Security**: Automatically mask sensitive environment variables in console output - -## Non-Goals - -1. Full regex support (only glob-style wildcards) -2. Wildcard patterns in `untrackedEnv` (same as `env`) -3. Complex glob patterns like `{VITE,NODE}_*` (supported by wax crate) - -## Proposed Solution - -### Pattern Syntax - -Support standard glob wildcard patterns: - -- `*` - Matches zero or more characters -- `?` - Matches exactly one character - -Examples: - -- `NODE_*` - Matches `NODE_ENV`, `NODE_OPTIONS`, etc. -- `VITE_*` - Matches all Vite environment variables -- `*_PORT` - Matches `API_PORT`, `SERVER_PORT`, etc. -- `REACT_APP_*` - Matches Create React App variables -- `APP?_*` - Matches `APP1_NAME`, `APP2_NAME`, etc., but not `APP_NAME` - -#### No Support for Negated Patterns - -We don't support `!` for negated patterns. If match the negated pattern, will ignore it and show a warning. - -> Explicitness over Convenience - -### Implementation Architecture - -``` -┌─────────────────┐ -│ Task Config │ -│ env: [ │ -│ "NODE_*", │ -│ "VITE_*", │ -│ "CI" │ -│ ] │ -└────────┬────────┘ - │ - ▼ -┌───────────────────┐ -│ Pattern Matcher │ -│ │ -│ - Parse patterns │ -│ - Match env vars │ -└────────┬──────────┘ - │ - ▼ -┌─────────────────┐ -│ Resolved Envs │ -│ │ -│ NODE_ENV │ -│ NODE_OPTIONS │ -│ VITE_API_URL │ -│ VITE_BASE_URL │ -│ CI │ -└────────┬────────┘ - │ - ▼ -┌───────────────────┐ -│ Cache Fingerprint │ -│ │ -│ Stable, sorted │ -│ env list │ -└───────────────────┘ -``` - -#### Cache Fingerprint Stability and Security - -The implementation must ensure: - -1. Environment variables are sorted alphabetically before inclusion in fingerprint -2. The same wildcard pattern always produces the same set of variables (for the same environment) -3. Cache keys remain stable across runs -4. **Fingerprint values are hashed, never stored in plaintext** - -**Security-First Fingerprint Implementation:** - -```rust -use sha2::{Sha256, Digest}; - -impl TaskEnvs { - /// Create a secure fingerprint for environment variables - pub fn create_fingerprint(&self) -> HashMap { - let mut fingerprint = HashMap::new(); - - for (name, value) in &self.envs_without_pass_through { - let mut hasher = Sha256::new(); - hasher.update(value.as_bytes()); - fingerprint.insert(name.clone(), hasher.finalize().into()); - } - - fingerprint - } -} - -// In CommandFingerprint -#[derive(Encode, Decode, Debug, Serialize, PartialEq, Eq, Diff, Clone)] -pub struct CommandFingerprint { - pub cwd: Str, - pub command: TaskCommand, - /// Environment variable fingerprints (names + hashed values). - /// NEVER contains actual env values. - pub envs_fingerprint: HashMap, -} -``` - -**Key Security Principles:** - -- Fingerprinted environment values are ALWAYS hashed before storage -- Use cryptographic hash (SHA-256) for one-way transformation -- Persist fixed-size digests in the cache key; use a `sha256:` prefix only in human-readable output -- Cache entries never contain plaintext secrets -- Fingerprint comparison still works (same value = same hash) - -### Configuration Examples - -#### Before (Current) - -```json -{ - "tasks": { - "build": { - "command": "vite build", - "env": [ - "NODE_ENV", - "NODE_OPTIONS", - "VITE_API_URL", - "VITE_APP_TITLE", - "VITE_PUBLIC_PATH", - "VITE_BASE_URL" - ] - } - } -} -``` - -#### After (With Wildcards) - -```json -{ - "tasks": { - "build": { - "command": "vite build", - "env": ["NODE_*", "VITE_*"] - } - } -} -``` - -### Testing Strategy - -- Test with real Vite projects using `VITE_*` variables -- Test with Node.js projects using `NODE_*` variables -- Test cache hit/miss scenarios with wildcard patterns - -### Performance Considerations - -1. **Pattern Compilation**: Compile wildcard patterns once during task configuration parsing -2. **Caching**: Cache resolved environment variable lists per task to avoid repeated pattern matching -3. **Lazy Evaluation**: Only resolve wildcards when actually needed for task execution - -### Security Considerations - -#### Sensitive Environment Variable Masking - -When displaying environment variables in logs or console output, sensitive values must be automatically masked with `***` to prevent accidental exposure of secrets. - -**Known Sensitive Patterns:** - -| Pattern | Description | Example Variables | -| --------------- | --------------------------- | -------------------------------------------- | -| `*_KEY` | API keys, encryption keys | `API_KEY`, `SECRET_KEY`, `PRIVATE_KEY` | -| `*_SECRET` | Secrets and sensitive data | `APP_SECRET`, `JWT_SECRET` | -| `*_TOKEN` | Authentication tokens | `AUTH_TOKEN`, `ACCESS_TOKEN`, `GITHUB_TOKEN` | -| `*_PASSWORD` | Passwords | `DB_PASSWORD`, `ADMIN_PASSWORD` | -| `*_PASS` | Short form passwords | `MYSQL_PASS`, `REDIS_PASS` | -| `*_PWD` | Alternative password form | `DB_PWD`, `USER_PWD` | -| `*_CREDENTIAL*` | Credentials | `AWS_CREDENTIALS`, `CREDENTIAL_ID` | -| `*_API_KEY` | API keys | `STRIPE_API_KEY`, `GOOGLE_API_KEY` | -| `*_PRIVATE_*` | Private data | `PRIVATE_KEY`, `PRIVATE_TOKEN` | -| `AWS_*` | AWS credentials | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | -| `GITHUB_*` | GitHub tokens | `GITHUB_TOKEN`, `GITHUB_PAT` | -| `NPM_*TOKEN` | NPM tokens | `NPM_TOKEN`, `NPM_AUTH_TOKEN` | -| `DATABASE_URL` | Database connection strings | Contains passwords | -| `MONGODB_URI` | MongoDB connection strings | Contains passwords | -| `REDIS_URL` | Redis connection strings | May contain passwords | -| `*_CERT*` | Certificates | `SSL_CERT`, `TLS_CERT_KEY` | - -**Implementation Example:** - -```rust -fn is_sensitive_env_name(name: &str) -> bool { - const SENSITIVE_PATTERNS: &[&str] = &[ - "*_KEY", - "*_SECRET", - "*_TOKEN", - "*_PASSWORD", - "*_PASS", - "*_PWD", - "*_CREDENTIAL*", - "*_API_KEY", - "*_PRIVATE_*", - "AWS_*", - "GITHUB_*", - "NPM_*TOKEN", - "DATABASE_URL", - "MONGODB_URI", - "REDIS_URL", - "*_CERT*", - ]; - - // Exact matches for known sensitive names - const SENSITIVE_EXACT: &[&str] = &[ - "PASSWORD", - "SECRET", - "TOKEN", - "PRIVATE_KEY", - "PUBLIC_KEY", - ]; - - if SENSITIVE_EXACT.contains(&name) { - return true; - } - - for pattern in SENSITIVE_PATTERNS { - if Glob::new(pattern).is_match(name) { - return true; - } - } - - false -} - -fn display_env_value(name: &str, value: &str) -> String { - if is_sensitive_env_name(name) { - format!("{}=***", name) - } else { - format!("{}={}", name, value) - } -} -``` - -#### Cache Storage Security - -**Principles for Secure Cache Storage:** - -1. **Never Store Env Values in Cache**: - - ```rust - // BAD - Never do this - cache_entry.envs = task.envs_without_pass_through.clone(); - - // GOOD - Store hashed fingerprints - cache_entry.env_fingerprint = task.create_secure_fingerprint(); - ``` - -2. **Use One-Way Hashing**: - - SHA-256 for every fingerprinted env value (irreversible) - - Consistent hashing ensures cache hits work correctly - - Different values produce different hashes - -3. **Cache File Protection**: - - Store cache files with restricted permissions (0600) - - Consider encrypting the entire cache database - - Regular cache cleanup to minimize exposure window - -4. **Example Cache Entry Structure**: - ```json - { - "task_id": "build_abc123", - "env_fingerprint": { - "NODE_ENV": "sha256:ab8e18ef4ebebeddc0b3152ce9c9006e14fc05242e3fc9ce32246ea6a9543074", - "API_KEY": "sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3", - "DATABASE_URL": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae" - }, - "outputs": "...", - "timestamp": "2024-01-01T00:00:00Z" - } - ``` - -#### Additional Security Measures - -1. **Secret Leakage Prevention**: - - Wildcard patterns could inadvertently capture sensitive environment variables - - Mitigation: Warn users when using broad patterns like `*` or `*_*` - - Provide clear documentation about security implications - -2. **Cache Poisoning**: - - Malicious environment variables matching wildcards could affect cache - - Mitigation: Validate environment variable names and values - - Use hashed values in cache keys to prevent manipulation - -3. **Audit Logging**: - - Log when sensitive patterns are detected (without values) - - Track which tasks access sensitive environment variables - - Provide security audit trails for compliance - -4. **Runtime Protection**: - - Clear sensitive values from memory after use - - Use secure string types that zero memory on drop - - Implement rate limiting for cache operations - -## Open Questions - -1. Should we support `?` for single character matching? (yes) -2. Should we warn when wildcards match > 100 variables? (no) -3. Should we support exclusion patterns (e.g., `!SECRET_*`)? (no) - -## References - -- [Turborepo Environment Variable Handling](https://turborepo.com/docs/crafting-your-repository/caching#environment-variables) -- [Vite Environment Variables](https://vite.dev/guide/env-and-mode.html) -- [wax Crate Documentation](https://docs.rs/wax/) diff --git a/crates/vite_task/src/cli/mod.rs b/crates/vite_task/src/cli/mod.rs deleted file mode 100644 index d17e38291..000000000 --- a/crates/vite_task/src/cli/mod.rs +++ /dev/null @@ -1,244 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use vite_path::AbsolutePath; -use vite_str::Str; -use vite_task_graph::{TaskSpecifier, query::TaskQuery}; -use vite_task_plan::plan_request::{CacheOverride, PlanOptions, QueryPlanRequest}; -use vite_workspace::package_filter::{PackageQueryArgs, PackageQueryError}; - -/// Controls how task output is displayed. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::ValueEnum)] -pub enum LogMode { - /// Output streams directly to the terminal as tasks produce it. - #[default] - Interleaved, - /// Each line is prefixed with `[packageName#taskName]`. - Labeled, - /// Output is buffered per task and printed as a block after each task completes. - Grouped, -} - -#[derive(Debug, Clone, clap::Subcommand)] -pub enum CacheSubcommand { - /// Clean up all the cache - Clean, -} - -/// Flags that control how a `run` command selects tasks. -#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] -#[expect(clippy::struct_excessive_bools, reason = "CLI flags are naturally boolean")] -pub struct RunFlags { - #[clap(flatten)] - pub package_query: PackageQueryArgs, - - /// Do not run dependencies specified in `dependsOn` fields. - #[clap(default_value = "false", long)] - pub ignore_depends_on: bool, - - /// Show full detailed summary after execution. - #[clap(default_value = "false", short = 'v', long)] - pub verbose: bool, - - /// Force caching on for all tasks and scripts. - #[clap(long, conflicts_with = "no_cache")] - pub cache: bool, - - /// Force caching off for all tasks and scripts. - #[clap(long, conflicts_with = "cache")] - pub no_cache: bool, - - /// How task output is displayed. - #[clap(long, default_value = "interleaved")] - pub log: LogMode, - - /// Maximum number of tasks to run concurrently. Defaults to 4. - #[clap(long)] - pub concurrency_limit: Option, - - /// Run tasks without dependency ordering. Sets concurrency to unlimited - /// unless `--concurrency-limit` is also specified. - #[clap(long, default_value = "false")] - pub parallel: bool, -} - -impl RunFlags { - #[must_use] - pub const fn cache_override(&self) -> CacheOverride { - if self.cache { - CacheOverride::ForceEnabled - } else if self.no_cache { - CacheOverride::ForceDisabled - } else { - CacheOverride::None - } - } -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Public CLI types (clap-parsed) -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -/// Arguments for the `run` subcommand as parsed by clap. -/// -/// Contains the `--last-details` flag which is resolved into a separate -/// `ResolvedCommand::RunLastDetails` variant internally. -/// -/// `trailing_var_arg` at the command level makes clap stop matching flags once -/// the trailing positional starts being filled. This means all tokens after the -/// task name are passed through to the task verbatim, preventing flags like `-v` -/// from being intercepted. Flags intended for `vp` itself (e.g. `--verbose`, -/// `-r`) must appear **before** the task name. -/// -/// See . -#[derive(Debug, clap::Parser)] -#[command(trailing_var_arg = true)] -pub struct RunCommand { - #[clap(flatten)] - pub(crate) flags: RunFlags, - - /// Display the detailed summary of the last run. - #[clap(long, exclusive = true)] - pub(crate) last_details: bool, - - #[clap( - allow_hyphen_values = true, - value_names = ["TASK_SPECIFIER", "ADDITIONAL_ARGS"], - long_help = "Task to run, as `packageName#taskName` or just `taskName`.\nAny arguments after the task name are forwarded to the task process.\nRunning `vp run` without a task name shows an interactive task selector." - )] - pub(crate) task_and_args: Vec, -} - -/// vite task CLI subcommands as parsed by clap. -/// -/// vite task CLI subcommands as parsed by clap. -/// -/// Pass directly to `Session::main` or `HandledCommand::ViteTaskCommand`. -/// The `--last-details` flag on the `run` subcommand is resolved internally. -#[derive(Debug, Parser)] -pub enum Command { - /// Run tasks - Run(RunCommand), - /// Manage the task cache - Cache { - #[clap(subcommand)] - subcmd: CacheSubcommand, - }, -} - -impl Command { - /// Resolve the clap-parsed command into the dispatched [`ResolvedCommand`] enum. - /// - /// When `--last-details` is set on the `run` subcommand, this produces - /// [`ResolvedCommand::RunLastDetails`] instead of [`ResolvedCommand::Run`], - /// making the exclusivity enforced at the type level. - #[must_use] - pub(crate) fn into_resolved(self) -> ResolvedCommand { - match self { - Self::Run(run) if run.last_details => ResolvedCommand::RunLastDetails, - Self::Run(run) => ResolvedCommand::Run(run.into_resolved()), - Self::Cache { subcmd } => ResolvedCommand::Cache { subcmd }, - } - } -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Internal resolved types (used for dispatch — `--last-details` is a separate variant) -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -/// Resolved CLI command for internal dispatch. -/// -/// Unlike [`Command`], this enum makes `--last-details` a separate variant -/// ([`ResolvedCommand::RunLastDetails`]) so that it is exclusive at the type level — -/// there is no way to combine it with task execution fields. -#[derive(Debug)] -pub enum ResolvedCommand { - /// Run tasks with the given parameters. - Run(ResolvedRunCommand), - /// Display the saved detailed summary of the last run (`--last-details`). - RunLastDetails, - /// Manage the task cache. - Cache { subcmd: CacheSubcommand }, -} - -/// Resolved arguments for executing tasks. -/// -/// Does not contain `last_details` — that case is represented by -/// [`ResolvedCommand::RunLastDetails`] instead. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ResolvedRunCommand { - /// `packageName#taskName` or `taskName`. If omitted, lists all available tasks. - pub task_specifier: Option, - - pub flags: RunFlags, - - /// Additional arguments to pass to the tasks. - pub additional_args: Vec, -} - -impl RunCommand { - /// Convert to the resolved run command, stripping the `last_details` flag. - /// - /// Splits `task_and_args` into `task_specifier` (the first element) and - /// `additional_args` (everything that follows). - #[must_use] - pub(crate) fn into_resolved(self) -> ResolvedRunCommand { - let mut iter = self.task_and_args.into_iter(); - let task_specifier = iter.next(); - let additional_args: Vec = iter.collect(); - ResolvedRunCommand { task_specifier, flags: self.flags, additional_args } - } -} - -#[derive(thiserror::Error, Debug)] -pub enum CLITaskQueryError { - #[error("no task specifier provided")] - MissingTaskSpecifier, - - #[error(transparent)] - PackageQuery(#[from] PackageQueryError), -} - -impl ResolvedRunCommand { - /// Convert to `QueryPlanRequest`, or return an error if invalid. - /// - /// # Errors - /// - /// Returns an error if conflicting flags are set or if a `--filter` expression - /// cannot be parsed. - pub fn into_query_plan_request( - self, - cwd: &Arc, - ) -> Result<(QueryPlanRequest, bool), CLITaskQueryError> { - let raw_specifier = self.task_specifier.ok_or(CLITaskQueryError::MissingTaskSpecifier)?; - let task_specifier = TaskSpecifier::parse_raw(&raw_specifier); - - let cache_override = self.flags.cache_override(); - let include_explicit_deps = !self.flags.ignore_depends_on; - let concurrency_limit = self.flags.concurrency_limit.map(|n| n.max(1)); - let parallel = self.flags.parallel; - // Read before `into_package_query` consumes the args. - let fail_if_no_match = self.flags.package_query.fail_if_no_match; - - let (package_query, is_cwd_only) = - self.flags.package_query.into_package_query(task_specifier.package_name, cwd)?; - - Ok(( - QueryPlanRequest { - query: TaskQuery { - package_query, - task_name: task_specifier.task_name, - include_explicit_deps, - }, - plan_options: PlanOptions { - extra_args: self.additional_args.into(), - cache_override, - concurrency_limit, - parallel, - fail_if_no_match, - }, - }, - is_cwd_only, - )) - } -} diff --git a/crates/vite_task/src/collections.rs b/crates/vite_task/src/collections.rs deleted file mode 100644 index eaad95110..000000000 --- a/crates/vite_task/src/collections.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[expect(clippy::disallowed_types, reason = "std HashMap needed for wincode/serde compatibility")] -pub type HashMap = std::collections::HashMap; diff --git a/crates/vite_task/src/lib.rs b/crates/vite_task/src/lib.rs deleted file mode 100644 index 3c330796c..000000000 --- a/crates/vite_task/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -mod cli; -mod collections; -mod napi_client; -pub mod session; - -// Public exports for vite_task_bin -pub use cli::{CacheSubcommand, Command, RunCommand, RunFlags}; -pub use session::{ - CommandHandler, ExitStatus, HandledCommand, Session, SessionConfig, print_error, -}; -pub use vite_task_graph::{ - config::{ - self, - user::{EnabledCacheConfig, UserCacheConfig, UserTaskConfig, UserTaskOptions}, - }, - loader, -}; -/// Re-exports useful for `CommandHandler` implementations. -pub use vite_task_plan::get_path_env; -pub use vite_task_plan::{plan_request, plan_request::ScriptCommand}; diff --git a/crates/vite_task/src/napi_client.rs b/crates/vite_task/src/napi_client.rs deleted file mode 100644 index dce8328de..000000000 --- a/crates/vite_task/src/napi_client.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! The `vite_task_client_napi` cdylib is embedded into the `vp` binary and -//! materialized to disk on first use so tools can `require()` it at runtime. - -use std::{env, fs, sync::LazyLock}; - -use materialized_artifact::artifact; -use vite_path::{AbsolutePath, AbsolutePathBuf}; - -/// Path to the materialized `vite_task_client_napi` `.node` addon. -/// -/// The file is written to a process-wide temp directory on first call and -/// reused on every subsequent call (content-addressed filename; no re-writes). -/// -/// # Panics -/// -/// Panics if the materialization fails on first call — this mirrors fspy's -/// `SPY_IMPL` and the same reasoning applies: if we can't write into the -/// system temp dir, the runner can't run tasks anyway. -#[must_use] -pub fn napi_client_path() -> &'static AbsolutePath { - static PATH: LazyLock = LazyLock::new(|| { - let dir = env::temp_dir().join("vite_task_client_napi"); - let _ = fs::create_dir(&dir); - let path = artifact!("vite_task_client_napi") - .materialize() - .suffix(".node") - .at(&dir) - .expect("materialize vite_task_client_napi"); - AbsolutePathBuf::new(path).expect("system temp dir yields an absolute path") - }); - PATH.as_absolute_path() -} diff --git a/crates/vite_task/src/session/cache/archive.rs b/crates/vite_task/src/session/cache/archive.rs deleted file mode 100644 index 26b28aca7..000000000 --- a/crates/vite_task/src/session/cache/archive.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Output archive creation and extraction using tar + zstd compression. - -use std::{fs::File, io}; - -use vite_path::{AbsolutePath, RelativePathBuf}; - -/// Create a tar.zst archive from workspace-relative output file paths. -/// -/// Files that no longer exist are silently skipped (the task may delete -/// temporary files during execution). -/// -/// # Errors -/// -/// Returns an error if creating the archive file or adding entries fails. -pub fn create_output_archive( - workspace_root: &AbsolutePath, - output_files: &[RelativePathBuf], - archive_path: &AbsolutePath, -) -> anyhow::Result<()> { - let file = File::create(archive_path.as_path())?; - let encoder = zstd::Encoder::new(file, 0)?.auto_finish(); - let mut builder = tar::Builder::new(encoder); - - for rel_path in output_files { - let abs_path = workspace_root.join(rel_path); - // Skip files that no longer exist (task may delete temp files between - // glob walk and archiving). Any other error is propagated. - let metadata = match std::fs::metadata(abs_path.as_path()) { - Ok(m) => m, - Err(err) if err.kind() == io::ErrorKind::NotFound => continue, - Err(err) => return Err(err.into()), - }; - if metadata.is_file() { - let mut file = File::open(abs_path.as_path())?; - let mut header = tar::Header::new_gnu(); - header.set_metadata(&metadata); - header.set_cksum(); - builder.append_data(&mut header, rel_path.as_str(), &mut file)?; - } - } - - builder.finish()?; - Ok(()) -} - -/// Extract a tar.zst archive, restoring files relative to workspace root. -/// -/// Parent directories are created automatically. Existing files are overwritten. -/// -/// # Errors -/// -/// Returns an error if opening the archive or extracting entries fails. -pub fn extract_output_archive( - workspace_root: &AbsolutePath, - archive_path: &AbsolutePath, -) -> anyhow::Result<()> { - let file = File::open(archive_path.as_path())?; - let decoder = zstd::Decoder::new(file)?; - let mut archive = tar::Archive::new(decoder); - - archive.unpack(workspace_root.as_path())?; - Ok(()) -} diff --git a/crates/vite_task/src/session/cache/display.rs b/crates/vite_task/src/session/cache/display.rs deleted file mode 100644 index f0c68fad0..000000000 --- a/crates/vite_task/src/session/cache/display.rs +++ /dev/null @@ -1,274 +0,0 @@ -//! Human-readable formatting for cache status -//! -//! This module provides plain text formatting for cache status. -//! Coloring is handled by the reporter to respect `NO_COLOR` environment variable. - -use rustc_hash::FxHashSet; -use serde::{Deserialize, Serialize}; -use vite_str::Str; -use vite_task_plan::cache_metadata::SpawnFingerprint; - -use super::{CacheMiss, EnvMismatch, FingerprintMismatch, InputChangeKind, split_path}; -use crate::session::event::CacheStatus; - -/// Describes a single atomic change between two spawn fingerprints. -/// -/// Used both for live cache status display and for persisted summary data. -#[derive(Serialize, Deserialize)] -pub enum SpawnFingerprintChange { - /// A fingerprinted env var was added, removed, or changed value. - Env(EnvMismatch), - - // Untracked env config changes - /// Untracked env pattern added - UntrackedEnvAdded { name: Str }, - /// Untracked env pattern removed - UntrackedEnvRemoved { name: Str }, - - // Command changes - /// Program changed - ProgramChanged, - /// Args changed - ArgsChanged, - - // Working directory change - /// Working directory changed - CwdChanged, -} - -/// Format a single spawn fingerprint change as human-readable text. -/// -/// Used by both the live cache status display and the persisted summary rendering. -pub fn format_spawn_change(change: &SpawnFingerprintChange) -> Str { - match change { - SpawnFingerprintChange::Env(mismatch) => vite_str::format!("{mismatch}"), - SpawnFingerprintChange::UntrackedEnvAdded { name } => { - vite_str::format!("untracked env '{name}' added") - } - SpawnFingerprintChange::UntrackedEnvRemoved { name } => { - vite_str::format!("untracked env '{name}' removed") - } - SpawnFingerprintChange::ProgramChanged => Str::from("program changed"), - SpawnFingerprintChange::ArgsChanged => Str::from("args changed"), - SpawnFingerprintChange::CwdChanged => Str::from("working directory changed"), - } -} - -/// Compare two spawn fingerprints and return all changes. -pub fn detect_spawn_fingerprint_changes( - old: &SpawnFingerprint, - new: &SpawnFingerprint, -) -> Vec { - let mut changes = Vec::new(); - let old_env = old.env_fingerprints(); - let new_env = new.env_fingerprints(); - - // Check for removed or changed envs - for (key, old_value) in &old_env.fingerprinted_envs { - if let Some(new_value) = new_env.fingerprinted_envs.get(key) { - if old_value != new_value { - changes - .push(SpawnFingerprintChange::Env(EnvMismatch::Changed { name: key.clone() })); - } - } else { - changes.push(SpawnFingerprintChange::Env(EnvMismatch::Removed { name: key.clone() })); - } - } - - // Check for added envs - for key in new_env.fingerprinted_envs.keys() { - if !old_env.fingerprinted_envs.contains_key(key) { - changes.push(SpawnFingerprintChange::Env(EnvMismatch::Added { name: key.clone() })); - } - } - - // Check untracked env config changes - let old_untracked: FxHashSet<_> = old_env.untracked_env_config.iter().collect(); - let new_untracked: FxHashSet<_> = new_env.untracked_env_config.iter().collect(); - for name in old_untracked.difference(&new_untracked) { - changes.push(SpawnFingerprintChange::UntrackedEnvRemoved { name: (*name).clone() }); - } - for name in new_untracked.difference(&old_untracked) { - changes.push(SpawnFingerprintChange::UntrackedEnvAdded { name: (*name).clone() }); - } - - // Check program changes - if old.program_fingerprint_debug() != new.program_fingerprint_debug() { - changes.push(SpawnFingerprintChange::ProgramChanged); - } - - // Check args changes - if old.args() != new.args() { - changes.push(SpawnFingerprintChange::ArgsChanged); - } - - // Check cwd changes - if old.cwd() != new.cwd() { - changes.push(SpawnFingerprintChange::CwdChanged); - } - - changes -} - -/// Names of the env vars involved in a set of spawn-fingerprint changes, in the -/// order detected. Only env changes are collected; untracked-env and non-env -/// changes are skipped. -fn env_change_names(changes: &[SpawnFingerprintChange]) -> Vec<&Str> { - changes - .iter() - .filter_map(|change| match change { - SpawnFingerprintChange::Env(mismatch) => Some(mismatch.name()), - _ => None, - }) - .collect() -} - -/// Inline cache-miss reason naming the env var(s) that changed, e.g. -/// `env 'NODE_ENV' changed` or `envs 'A', 'B' changed`. Falls back to the -/// generic `envs changed` when no names are available. -fn format_env_changed_inline(names: &[&Str]) -> Str { - match names { - [] => Str::from("envs changed"), - [name] => vite_str::format!("env '{name}' changed"), - names => { - let quoted: Vec = names.iter().map(|name| vite_str::format!("'{name}'")).collect(); - let joined = quoted.iter().map(Str::as_str).collect::>().join(", "); - vite_str::format!("envs {joined} changed") - } - } -} - -/// Format cache status for inline display (during Start event). -/// -/// Returns `Some(formatted_string)` for Hit, Miss with reason, and Disabled, None for `NotFound`. -/// - Cache Hit: Shows "cache hit" indicator -/// - Cache Miss (NotFound): No inline message (just command) -/// - Cache Miss (with mismatch): Shows "cache miss" with brief reason -/// - Cache Disabled: Shows "cache disabled" with reason -/// -/// Note: Returns plain text without styling. The reporter applies colors. -pub fn format_cache_status_inline(cache_status: &CacheStatus) -> Option { - match cache_status { - CacheStatus::Hit { .. } => { - // Show "cache hit" indicator when replaying from cache - Some(Str::from("◉ cache hit, replaying")) - } - CacheStatus::Miss(CacheMiss::NotFound) => { - // No inline message for "not found" case - just show command - // This keeps the output clean for first-time executions - None - } - CacheStatus::Miss(CacheMiss::FingerprintMismatch(mismatch)) => { - // Show "cache miss" with reason why cache couldn't be used - let reason = match mismatch { - FingerprintMismatch::SpawnFingerprint { old, new } => { - let changes = detect_spawn_fingerprint_changes(old, new); - match changes.first() { - Some(SpawnFingerprintChange::Env(_)) => { - format_env_changed_inline(&env_change_names(&changes)) - } - Some( - SpawnFingerprintChange::UntrackedEnvAdded { .. } - | SpawnFingerprintChange::UntrackedEnvRemoved { .. }, - ) => Str::from("untracked env config changed"), - Some(SpawnFingerprintChange::ProgramChanged) => { - Str::from("program changed") - } - Some(SpawnFingerprintChange::ArgsChanged) => Str::from("args changed"), - Some(SpawnFingerprintChange::CwdChanged) => { - Str::from("working directory changed") - } - None => Str::from("configuration changed"), - } - } - FingerprintMismatch::InputConfig => Str::from("input configuration changed"), - FingerprintMismatch::OutputConfig => Str::from("output configuration changed"), - FingerprintMismatch::InputChanged { kind, path } => { - format_input_change_str(*kind, path.as_str()) - } - FingerprintMismatch::TrackedEnvChanged(mismatch) - | FingerprintMismatch::TrackedEnvQueryChanged { mismatch, .. } => { - vite_str::format!("{mismatch}") - } - }; - Some(vite_str::format!("○ cache miss: {reason}, executing")) - } - CacheStatus::Disabled(_) => Some(Str::from("⊘ cache disabled")), - } -} - -/// Format an input change as a [`Str`] for inline display. -pub fn format_input_change_str(kind: InputChangeKind, path: &str) -> Str { - match kind { - InputChangeKind::ContentModified => vite_str::format!("'{path}' modified"), - InputChangeKind::Added => { - let (dir, filename) = split_path(path); - dir.map_or_else( - || vite_str::format!("'{filename}' added in workspace root"), - |dir| vite_str::format!("'{filename}' added in '{dir}'"), - ) - } - InputChangeKind::Removed => { - let (dir, filename) = split_path(path); - dir.map_or_else( - || vite_str::format!("'{filename}' removed from workspace root"), - |dir| vite_str::format!("'{filename}' removed from '{dir}'"), - ) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn env_spawn_changes_report_names_only() { - let added = SpawnFingerprintChange::Env(EnvMismatch::Added { name: Str::from("MY_ENV") }); - let removed = - SpawnFingerprintChange::Env(EnvMismatch::Removed { name: Str::from("MY_ENV") }); - let changed = - SpawnFingerprintChange::Env(EnvMismatch::Changed { name: Str::from("MY_ENV") }); - - assert_eq!(format_spawn_change(&added).as_str(), "env 'MY_ENV' added"); - assert_eq!(format_spawn_change(&removed).as_str(), "env 'MY_ENV' removed"); - assert_eq!(format_spawn_change(&changed).as_str(), "env 'MY_ENV' changed"); - } - - #[test] - fn inline_env_change_reason_reports_names_only() { - let first = Str::from("API_KEY"); - let second = Str::from("NODE_ENV"); - - assert_eq!( - format_env_changed_inline(&[&first, &second]).as_str(), - "envs 'API_KEY', 'NODE_ENV' changed" - ); - } - - #[test] - fn inline_tracked_env_mismatch_preserves_kind() { - let added = CacheStatus::Miss(CacheMiss::FingerprintMismatch( - FingerprintMismatch::TrackedEnvQueryChanged { - query: crate::session::execute::fingerprint::TrackedEnvQuery::Glob(Str::from( - "PROBE_*", - )), - mismatch: EnvMismatch::Added { name: Str::from("PROBE_C") }, - }, - )); - let removed = CacheStatus::Miss(CacheMiss::FingerprintMismatch( - FingerprintMismatch::TrackedEnvChanged(EnvMismatch::Removed { - name: Str::from("PROBE_A"), - }), - )); - - assert_eq!( - format_cache_status_inline(&added).as_deref(), - Some("○ cache miss: env 'PROBE_C' added, executing") - ); - assert_eq!( - format_cache_status_inline(&removed).as_deref(), - Some("○ cache miss: env 'PROBE_A' removed, executing") - ); - } -} diff --git a/crates/vite_task/src/session/cache/mod.rs b/crates/vite_task/src/session/cache/mod.rs deleted file mode 100644 index b18bccaca..000000000 --- a/crates/vite_task/src/session/cache/mod.rs +++ /dev/null @@ -1,675 +0,0 @@ -//! Execution cache for storing and retrieving cached command results. - -pub mod archive; -pub mod display; - -use std::{collections::BTreeMap, fmt::Display, fs::File, io::Write, sync::Arc, time::Duration}; - -// Re-export display functions for convenience -pub use display::format_cache_status_inline; -pub use display::{ - SpawnFingerprintChange, detect_spawn_fingerprint_changes, format_input_change_str, - format_spawn_change, -}; -use rusqlite::{Connection, OptionalExtension as _}; -use serde::{Deserialize, Serialize}; -use tokio::sync::Mutex; -use vite_path::{AbsolutePath, RelativePathBuf}; -use vite_str::Str; -use vite_task_graph::config::ResolvedGlobConfig; -use vite_task_plan::cache_metadata::{CacheMetadata, ExecutionCacheKey, SpawnFingerprint}; -use wincode::{ - SchemaRead, SchemaReadOwned, SchemaWrite, - config::{ConfigCore, Configuration}, - error::{ReadResult, WriteResult}, - io::{Reader, Writer}, -}; - -use super::execute::{ - fingerprint::{PostRunFingerprint, TrackedEnvQuery}, - pipe::StdOutput, -}; - -const TASK_CACHE_PREALLOCATION_SIZE_LIMIT: usize = 256 * 1024 * 1024; -type TaskCacheConfig = Configuration; -const TASK_CACHE_CONFIG: TaskCacheConfig = - Configuration::default().with_preallocation_size_limit::(); - -fn serialize_cache(value: &T) -> WriteResult> -where - T: SchemaWrite + ?Sized, -{ - wincode::config::serialize(value, TASK_CACHE_CONFIG) -} - -fn deserialize_cache(bytes: &[u8]) -> ReadResult -where - T: SchemaReadOwned, -{ - wincode::config::deserialize_exact(bytes, TASK_CACHE_CONFIG) -} - -/// Cache lookup key identifying a task's execution configuration. -/// -/// # Key vs value design -/// -/// Put a field in the **key** if each distinct value should have its own -/// cache entry (e.g., different env values → different entries, so -/// reverting an env change can still hit the old entry). -/// -/// Put a field in the **value** ([`CacheEntryValue`]) if changes should -/// overwrite the existing entry (e.g., input file hashes — there's no -/// reason to keep the old hash around, and storing them in the value -/// lets us report exactly *which file* changed). -#[derive(Debug, SchemaWrite, SchemaRead, Serialize, PartialEq, Eq, Clone)] -pub struct CacheEntryKey { - /// The spawn fingerprint (command, args, cwd, envs) - pub spawn_fingerprint: SpawnFingerprint, - /// Resolved input configuration that affects cache behavior. - /// Glob patterns are workspace-root-relative. - pub input_config: ResolvedGlobConfig, - /// Resolved output configuration that affects cache restoration. - /// Glob patterns are workspace-root-relative. - pub output_config: ResolvedGlobConfig, -} - -impl CacheEntryKey { - fn from_metadata(cache_metadata: &CacheMetadata) -> Self { - Self { - spawn_fingerprint: cache_metadata.spawn_fingerprint.clone(), - input_config: cache_metadata.input_config.clone(), - output_config: cache_metadata.output_config.clone(), - } - } -} - -/// wincode schema adapter for `Duration`. -struct DurationSchema; - -// SAFETY: Writes exactly `size_of::() + size_of::()` bytes matching size_of. -unsafe impl SchemaWrite for DurationSchema { - type Src = Duration; - - fn size_of(_src: &Self::Src) -> WriteResult { - Ok(size_of::() + size_of::()) - } - - fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> { - >::write(writer.by_ref(), &src.as_secs())?; - >::write(writer.by_ref(), &src.subsec_nanos())?; - Ok(()) - } -} - -// SAFETY: Reads u64 + u32, matching the write format; dst is initialized on Ok. -unsafe impl<'de, C: ConfigCore> SchemaRead<'de, C> for DurationSchema { - type Dst = Duration; - - fn read( - mut reader: impl Reader<'de>, - dst: &mut std::mem::MaybeUninit, - ) -> ReadResult<()> { - let secs = >::get(&mut reader)?; - let nanos = >::get(&mut reader)?; - dst.write(Duration::new(secs, nanos)); - Ok(()) - } -} - -/// Cached execution result for a task. -/// -/// Contains the post-run fingerprint (from fspy), captured outputs, -/// execution duration, and explicit input file hashes. -#[derive(Debug, SchemaWrite, SchemaRead, Serialize)] -pub struct CacheEntryValue { - pub post_run_fingerprint: PostRunFingerprint, - pub std_outputs: Arc<[StdOutput]>, - #[wincode(with = "DurationSchema")] - pub duration: Duration, - /// Hashes of explicit input files computed from positive globs. - /// Files matching negative globs are already filtered out. - /// Path is relative to workspace root, value is `xxHash3_64` of file content. - /// Stored in the value (not the key) so changes can be detected and reported. - pub globbed_inputs: BTreeMap, - /// Filename of the output archive (e.g. `{uuid}.tar.zst`) stored alongside - /// `cache.db` in the cache directory. `None` if no output files were produced. - pub output_archive: Option, -} - -#[derive(Debug)] -pub struct ExecutionCache { - conn: Mutex, -} - -#[derive(Debug, Clone, Serialize)] -#[expect( - clippy::large_enum_variant, - reason = "FingerprintMismatch contains SpawnFingerprint which is intentionally large; boxing would add unnecessary indirection for a short-lived enum" -)] -pub enum CacheMiss { - NotFound, - FingerprintMismatch(FingerprintMismatch), -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum InputChangeKind { - /// File content changed but path is the same - ContentModified, - /// New file or folder added - Added, - /// Existing file or folder removed - Removed, -} - -/// A single env var difference between a stored fingerprint and the current -/// environment. -/// -/// The canonical shape for an env change wherever one is detected and -/// reported. The [`Display`] impl is the single source of the user-facing -/// wording. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum EnvMismatch { - /// Set now, but absent from the stored fingerprint. - Added { name: Str }, - /// In the stored fingerprint, but unset now. - Removed { name: Str }, - /// Present on both sides with different values. - Changed { name: Str }, -} - -impl EnvMismatch { - /// The name of the env var that diverged. - #[must_use] - pub const fn name(&self) -> &Str { - match self { - Self::Added { name } | Self::Removed { name } | Self::Changed { name } => name, - } - } - - /// Compare a stored env value against the current one, returning the - /// mismatch if they differ. `None` on either side means the env is unset - /// there; two unset or two equal values are not a mismatch. - #[must_use] - pub fn compare(name: &Str, stored: Option<&T>, current: Option<&T>) -> Option { - match (stored, current) { - (None, Some(_)) => Some(Self::Added { name: name.clone() }), - (Some(_), None) => Some(Self::Removed { name: name.clone() }), - (Some(old_value), Some(new_value)) if old_value != new_value => { - Some(Self::Changed { name: name.clone() }) - } - _ => None, - } - } -} - -impl Display for EnvMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Added { name } => write!(f, "env '{name}' added"), - Self::Removed { name } => write!(f, "env '{name}' removed"), - Self::Changed { name } => write!(f, "env '{name}' changed"), - } - } -} - -#[derive(Debug, Clone, Serialize)] -pub enum FingerprintMismatch { - /// Found a previous cache entry key for the same task, but the spawn fingerprint differs. - /// This happens when the command itself or an env changes. - SpawnFingerprint { - /// The fingerprint from the cached entry - old: SpawnFingerprint, - /// The fingerprint of the current execution - new: SpawnFingerprint, - }, - /// Found a previous cache entry key for the same task, but `input_config` differs. - InputConfig, - /// Found a previous cache entry key for the same task, but `output_config` differs. - OutputConfig, - - InputChanged { - kind: InputChangeKind, - path: RelativePathBuf, - }, - /// A runner-aware tool-tracked env var changed between runs. - TrackedEnvChanged(EnvMismatch), - /// A runner-aware tool-tracked bulk env query's match-set changed between runs. - TrackedEnvQueryChanged { - query: TrackedEnvQuery, - mismatch: EnvMismatch, - }, -} - -impl From for FingerprintMismatch { - fn from(mismatch: crate::session::execute::fingerprint::PostRunMismatch) -> Self { - use crate::session::execute::fingerprint::PostRunMismatch; - match mismatch { - PostRunMismatch::Input { kind, path } => Self::InputChanged { kind, path }, - PostRunMismatch::TrackedEnv(mismatch) => Self::TrackedEnvChanged(mismatch), - PostRunMismatch::TrackedEnvQuery { query, mismatch } => { - Self::TrackedEnvQueryChanged { query, mismatch } - } - } - } -} - -/// Split a relative path into `(parent_dir, filename)`. -/// Returns `None` for the parent if the path has no `/` separator. -pub fn split_path(path: &str) -> (Option<&str>, &str) { - match path.rsplit_once('/') { - Some((parent, filename)) => (Some(parent), filename), - None => (None, path), - } -} - -/// On-disk schema version of the cache database. -/// -/// Bump this whenever the database layout (table structure, serialization -/// format, or fingerprint semantics) changes in an incompatible way. -/// -/// The version is encoded *only* in the cache directory name (see -/// [`cache_schema_dir_name`], e.g. `v14`); there is no in-database version -/// marker. Keying the storage location on this version means Vite+ builds that -/// pin different schema versions never open each other's database: each keeps -/// its own cache warm across branch switches, and a cache from a different -/// version is simply ignored (it lives in a directory this build never looks -/// at) rather than aborting the run. Bumping the version starts a fresh cache. -const CACHE_SCHEMA_VERSION: u32 = 18; - -/// Name of the per-version subdirectory (e.g. `v14`) under the task-cache -/// directory that holds the database and output archives for the current -/// [`CACHE_SCHEMA_VERSION`]. -pub fn cache_schema_dir_name() -> Str { - vite_str::format!("v{CACHE_SCHEMA_VERSION}") -} - -impl ExecutionCache { - #[tracing::instrument(level = "debug", skip_all)] - pub fn load_from_path(path: &AbsolutePath) -> anyhow::Result { - tracing::info!("Creating task cache directory at {:?}", path); - std::fs::create_dir_all(path)?; - - // Use file lock to prevent race conditions when multiple processes initialize the database - let lock_path = path.join("db_open.lock"); - let lock_file = File::create(lock_path.as_path())?; - lock_file.lock()?; - - let db_path = path.join("cache.db"); - let conn = Connection::open(db_path.as_path())?; - // The schema version is encoded in the directory name (see - // `cache_schema_dir_name`), so any database in this directory already has - // the current schema: there is nothing to migrate or version-check. Set - // WAL mode and ensure the tables exist in a single round-trip. On an - // existing database the `IF NOT EXISTS` creates are near-free no-ops (a - // schema lookup, no write); on a fresh one they create the tables. This - // runs once per process (the cache is `OnceCell`-initialized). - conn.execute_batch( - "PRAGMA journal_mode=WAL; - CREATE TABLE IF NOT EXISTS cache_entries (key BLOB PRIMARY KEY, value BLOB); - CREATE TABLE IF NOT EXISTS task_fingerprints (key BLOB PRIMARY KEY, value BLOB);", - )?; - // Lock is released when lock_file is dropped - Ok(Self { conn: Mutex::new(conn) }) - } - - #[tracing::instrument] - pub async fn save(self) -> anyhow::Result<()> { - // do some cleanup in the future - Ok(()) - } - - /// Try to hit cache by looking up the cache entry key and validating inputs. - /// Returns `Ok(Ok(cache_value))` on cache hit, `Ok(Err(cache_miss))` on miss. - #[tracing::instrument(level = "debug", skip_all)] - pub async fn try_hit( - &self, - cache_metadata: &CacheMetadata, - globbed_inputs: &BTreeMap, - workspace_root: &AbsolutePath, - ) -> anyhow::Result> { - let spawn_fingerprint = &cache_metadata.spawn_fingerprint; - let execution_cache_key = &cache_metadata.execution_cache_key; - - let cache_key = CacheEntryKey::from_metadata(cache_metadata); - - // Try to find the cache entry by key (spawn fingerprint + input config) - if let Some(cache_value) = self.get_by_cache_key(&cache_key).await? { - // Validate explicit globbed inputs against the stored values - if let Some(mismatch) = - detect_globbed_input_change(&cache_value.globbed_inputs, globbed_inputs) - { - return Ok(Err(CacheMiss::FingerprintMismatch(mismatch))); - } - - // Validate post-run fingerprint (inferred inputs + tracked envs) - if let Some(mismatch) = cache_value - .post_run_fingerprint - .validate(workspace_root, &cache_metadata.unfiltered_envs)? - { - return Ok(Err(CacheMiss::FingerprintMismatch(mismatch.into()))); - } - // Associate the execution key to the cache entry key if not already, - // so that next time we can find it and report what changed - self.upsert_task_fingerprint(execution_cache_key, &cache_key).await?; - return Ok(Ok(cache_value)); - } - - // No cache found with the current cache entry key, - // check if execution key maps to a different cache entry key - if let Some(old_cache_key) = - self.get_cache_key_by_execution_key(execution_cache_key).await? - { - // Destructure to ensure we handle all fields when new ones are added. - // `get_by_cache_key` above returned None for the *current* cache key, - // so at least one field on `old_cache_key` must differ from the - // current metadata — checked in priority order (spawn → input → output). - let CacheEntryKey { - spawn_fingerprint: old_spawn_fingerprint, - input_config: old_input_config, - output_config: old_output_config, - } = old_cache_key; - let mismatch = if old_spawn_fingerprint != *spawn_fingerprint { - FingerprintMismatch::SpawnFingerprint { - old: old_spawn_fingerprint, - new: spawn_fingerprint.clone(), - } - } else if old_input_config != cache_metadata.input_config { - FingerprintMismatch::InputConfig - } else { - debug_assert_ne!(old_output_config, cache_metadata.output_config); - FingerprintMismatch::OutputConfig - }; - return Ok(Err(CacheMiss::FingerprintMismatch(mismatch))); - } - - Ok(Err(CacheMiss::NotFound)) - } - - /// Update cache after successful execution. - /// - /// If a previous entry exists for the same cache key with a different - /// `output_archive`, the stale archive file in `cache_dir` is removed - /// (best-effort) so it doesn't accumulate on disk. - #[tracing::instrument(level = "debug", skip_all)] - pub async fn update( - &self, - cache_metadata: &CacheMetadata, - cache_value: CacheEntryValue, - cache_dir: &AbsolutePath, - ) -> anyhow::Result<()> { - let execution_cache_key = &cache_metadata.execution_cache_key; - - let cache_key = CacheEntryKey::from_metadata(cache_metadata); - - // If a previous entry exists with a stale output archive, delete the - // old file so the cache directory doesn't accumulate orphaned archives. - if let Some(old_value) = self.get_by_cache_key(&cache_key).await? - && let Some(old_archive) = old_value.output_archive - && cache_value.output_archive.as_ref() != Some(&old_archive) - { - let old_archive_path = cache_dir.join(old_archive.as_str()); - // Best-effort cleanup: a missing file (e.g. after a crash or manual - // cache clear) is fine, so we ignore the error. - let _ = std::fs::remove_file(old_archive_path.as_path()); - } - - self.upsert_cache_entry(&cache_key, &cache_value).await?; - self.upsert_task_fingerprint(execution_cache_key, &cache_key).await?; - Ok(()) - } -} - -/// Compare stored and current globbed inputs, returning the first changed path. -/// Both maps are `BTreeMap` so we iterate them in sorted lockstep. -fn detect_globbed_input_change( - stored: &BTreeMap, - current: &BTreeMap, -) -> Option { - let mut stored_iter = stored.iter(); - let mut current_iter = current.iter(); - let mut s = stored_iter.next(); - let mut c = current_iter.next(); - - loop { - match (s, c) { - (None, None) => return None, - (Some((sp, _)), None) => { - return Some(FingerprintMismatch::InputChanged { - kind: InputChangeKind::Removed, - path: sp.clone(), - }); - } - (None, Some((cp, _))) => { - return Some(FingerprintMismatch::InputChanged { - kind: InputChangeKind::Added, - path: cp.clone(), - }); - } - (Some((sp, sh)), Some((cp, ch))) => match sp.cmp(cp) { - std::cmp::Ordering::Equal => { - if sh != ch { - return Some(FingerprintMismatch::InputChanged { - kind: InputChangeKind::ContentModified, - path: sp.clone(), - }); - } - s = stored_iter.next(); - c = current_iter.next(); - } - std::cmp::Ordering::Less => { - return Some(FingerprintMismatch::InputChanged { - kind: InputChangeKind::Removed, - path: sp.clone(), - }); - } - std::cmp::Ordering::Greater => { - return Some(FingerprintMismatch::InputChanged { - kind: InputChangeKind::Added, - path: cp.clone(), - }); - } - }, - } - } -} - -// Basic database operations -impl ExecutionCache { - #[expect( - clippy::significant_drop_tightening, - reason = "lock guard cannot be dropped earlier because prepared statement borrows connection" - )] - async fn get_key_by_value< - K: SchemaWrite, - V: SchemaReadOwned, - >( - &self, - table: &str, - key: &K, - ) -> anyhow::Result> { - let key_blob = serialize_cache(key)?; - let value_blob = { - let conn = self.conn.lock().await; - #[expect( - clippy::disallowed_macros, - reason = "SQL query string for rusqlite requires String" - )] - let mut select_stmt = - conn.prepare_cached(&format!("SELECT value FROM {table} WHERE key=?"))?; - let value_blob: Option> = - select_stmt.query_row::, _, _>([key_blob], |row| row.get(0)).optional()?; - value_blob - }; - let Some(value_blob) = value_blob else { - return Ok(None); - }; - let value: V = deserialize_cache(&value_blob)?; - Ok(Some(value)) - } - - async fn get_by_cache_key( - &self, - cache_key: &CacheEntryKey, - ) -> anyhow::Result> { - self.get_key_by_value("cache_entries", cache_key).await - } - - async fn get_cache_key_by_execution_key( - &self, - execution_cache_key: &ExecutionCacheKey, - ) -> anyhow::Result> { - self.get_key_by_value("task_fingerprints", execution_cache_key).await - } - - #[expect( - clippy::significant_drop_tightening, - reason = "lock guard must be held while executing the prepared statement" - )] - async fn upsert< - K: SchemaWrite, - V: SchemaWrite, - >( - &self, - table: &str, - key: &K, - value: &V, - ) -> anyhow::Result<()> { - let key_blob = serialize_cache(key)?; - let value_blob = serialize_cache(value)?; - let conn = self.conn.lock().await; - #[expect(clippy::disallowed_macros, reason = "SQL query string for rusqlite requires String")] - let mut update_stmt = conn.prepare_cached(&format!( - "INSERT INTO {table} (key, value) VALUES (?1, ?2) ON CONFLICT(key) DO UPDATE SET value=?2" - ))?; - update_stmt.execute([key_blob, value_blob])?; - Ok(()) - } - - async fn upsert_cache_entry( - &self, - cache_key: &CacheEntryKey, - cache_value: &CacheEntryValue, - ) -> anyhow::Result<()> { - self.upsert("cache_entries", cache_key, cache_value).await - } - - async fn upsert_task_fingerprint( - &self, - execution_cache_key: &ExecutionCacheKey, - cache_entry_key: &CacheEntryKey, - ) -> anyhow::Result<()> { - self.upsert("task_fingerprints", execution_cache_key, cache_entry_key).await - } - - #[expect( - clippy::significant_drop_tightening, - reason = "lock guard must be held while iterating over query rows" - )] - async fn list_table< - K: SchemaReadOwned + Serialize, - V: SchemaReadOwned + Serialize, - >( - &self, - table: &str, - out: &mut impl Write, - ) -> anyhow::Result<()> { - let conn = self.conn.lock().await; - #[expect( - clippy::disallowed_macros, - reason = "SQL query string for rusqlite requires String" - )] - let mut select_stmt = conn.prepare_cached(&format!("SELECT key, value FROM {table}"))?; - let mut rows = select_stmt.query([])?; - while let Some(row) = rows.next()? { - let key_blob: Vec = row.get(0)?; - let value_blob: Vec = row.get(1)?; - let key: K = deserialize_cache(&key_blob)?; - let value: V = deserialize_cache(&value_blob)?; - writeln!( - out, - "{} => {}", - serde_json::to_string_pretty(&key)?, - serde_json::to_string_pretty(&value)? - )?; - } - Ok(()) - } - - pub async fn list(&self, mut out: impl Write) -> anyhow::Result<()> { - out.write_all(b"------- task_fingerprints -------\n")?; - self.list_table::("task_fingerprints", &mut out).await?; - out.write_all(b"------- cache_entries -------\n")?; - self.list_table::("cache_entries", &mut out).await?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use rusqlite::Connection; - use tempfile::TempDir; - use vite_path::AbsolutePathBuf; - - use super::*; - - fn temp_dir() -> (TempDir, AbsolutePathBuf) { - let tmp = TempDir::new().unwrap(); - let dir = AbsolutePathBuf::new(tmp.path().to_path_buf()).unwrap(); - (tmp, dir) - } - - fn open_raw(db: &AbsolutePath) -> Connection { - Connection::open(db.as_path()).unwrap() - } - - /// Reopening the same cache directory keeps existing entries: the tables are - /// created with `IF NOT EXISTS`, so a second open never wipes the database. - #[test] - fn reopening_preserves_existing_entries() { - let (_tmp, dir) = temp_dir(); - - drop(ExecutionCache::load_from_path(&dir).unwrap()); - { - let conn = open_raw(&dir.join("cache.db")); - conn.execute("INSERT INTO cache_entries (key, value) VALUES (X'01', X'02')", ()) - .unwrap(); - } - - // Reopening must not recreate or clear the tables. - drop(ExecutionCache::load_from_path(&dir).unwrap()); - - let count: u32 = open_raw(&dir.join("cache.db")) - .query_one("SELECT COUNT(*) FROM cache_entries", (), |r| r.get(0)) - .unwrap(); - assert_eq!(count, 1); - } - - /// Regression test for vite-plus#1785: two different schema-version - /// directories under the same cache base are fully independent, so caches - /// from different Vite+ versions never collide (each version reads and - /// writes only its own directory). - #[test] - fn version_directories_are_isolated() { - let (_tmp, base) = temp_dir(); - - let dir_a = base.join("v13"); - let dir_b = base.join("v14"); - - drop(ExecutionCache::load_from_path(&dir_a).unwrap()); - drop(ExecutionCache::load_from_path(&dir_b).unwrap()); - - assert!(dir_a.join("cache.db").as_path().exists()); - assert!(dir_b.join("cache.db").as_path().exists()); - - // A row written into A is invisible to B. - { - let conn = open_raw(&dir_a.join("cache.db")); - conn.execute("INSERT INTO cache_entries (key, value) VALUES (X'01', X'02')", ()) - .unwrap(); - } - let count_b: u32 = open_raw(&dir_b.join("cache.db")) - .query_one("SELECT COUNT(*) FROM cache_entries", (), |r| r.get(0)) - .unwrap(); - assert_eq!(count_b, 0); - } -} diff --git a/crates/vite_task/src/session/event.rs b/crates/vite_task/src/session/event.rs deleted file mode 100644 index 3cdfb8737..000000000 --- a/crates/vite_task/src/session/event.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::{process::ExitStatus, time::Duration}; - -use vite_path::RelativePathBuf; -use vite_task_server::Error as IpcServerError; - -use super::cache::CacheMiss; - -/// The cache operation that failed. -#[derive(Debug)] -pub enum CacheErrorKind { - /// Cache lookup (`try_hit`) failed. - Lookup, - /// Writing the cache entry failed after successful execution. - Update, -} - -impl std::fmt::Display for CacheErrorKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Lookup => f.write_str("lookup"), - Self::Update => f.write_str("update"), - } - } -} - -/// Error that occurred during a leaf execution. -/// -/// Reported through [`super::reporter::LeafExecutionReporter::finish()`] and -/// displayed by the reporter. -#[derive(Debug, thiserror::Error)] -pub enum ExecutionError { - /// A cache operation failed. - #[error("Cache {kind} failed")] - Cache { - kind: CacheErrorKind, - #[source] - source: anyhow::Error, - }, - - /// The OS failed to spawn the child process (e.g., command not found). - #[error("Failed to spawn process")] - Spawn(#[source] anyhow::Error), - - /// The child process started, but the runner failed while forwarding its output. - #[error("Failed to forward task process output")] - ForwardTaskProcessOutput(#[source] anyhow::Error), - - /// The child process started, but the runner failed while waiting for it to exit. - #[error("Failed to wait for task process to exit")] - WaitForTaskProcessExit(#[source] anyhow::Error), - - /// Creating the post-run fingerprint failed after successful execution. - #[error("Failed to create post-run fingerprint")] - PostRunFingerprint(#[source] anyhow::Error), - - /// The runner-aware IPC server failed to bind for this task. Reported - /// instead of silently degrading so that `{ auto: true }` inputs stay - /// observable end-to-end. - #[error("Failed to set up task communication")] - IpcServerBind(#[source] std::io::Error), -} - -#[derive(Debug, Clone)] -pub enum CacheDisabledReason { - InProcessExecution, - NoCacheMetadata, -} - -#[derive(Debug)] -pub enum CacheNotUpdatedReason { - /// Cache was hit - task was replayed from cache, no update needed - CacheHit, - /// Caching was disabled for this task - CacheDisabled, - /// Execution exited with non-zero status - NonZeroExitStatus, - /// Execution was cancelled before the result could be trusted. - /// Two possible causes: - /// - Ctrl-C: the user interrupted execution; the task may have - /// exited successfully but without completing its intended work. - /// - Fast-fail: a sibling task failed, triggering cancellation - /// while this task was still running. - Cancelled, - /// Task modified files it read during execution (read-write overlap detected by fspy). - /// Caching such tasks is unsound because the prerun input hashes become stale. - InputModified { - /// First path that was both read and written during execution. - path: RelativePathBuf, - }, - /// fspy isn't compiled in on this build and the task requires fspy - /// (its `input` config includes auto-inference). Task ran but cannot - /// be cached without tracked path accesses. - FspyUnsupported, - /// The runner's IPC server failed during execution, so the collected - /// reports may be incomplete. Caching such a run would risk stale - /// inputs/outputs on the next hit. Carries the underlying error for - /// user-facing reporting. - IpcServerError(IpcServerError), - /// A runner-aware tool explicitly requested that this run not be cached - /// (e.g. vite dev-server, a watch task). - ToolRequested, -} - -#[derive(Debug)] -pub enum CacheUpdateStatus { - /// Cache was successfully updated with new fingerprint and outputs - Updated, - /// Cache was not updated (with reason). - /// The reason is part of the `LeafExecutionReporter` trait contract — reporters - /// can use it for detailed logging, even if current implementations don't. - NotUpdated(CacheNotUpdatedReason), -} - -#[derive(Debug, Clone)] -#[expect( - clippy::large_enum_variant, - reason = "CacheMiss variant is intentionally large and infrequently cloned" -)] -pub enum CacheStatus { - Disabled(CacheDisabledReason), - Miss(CacheMiss), - Hit { replayed_duration: Duration }, -} - -/// Convert `ExitStatus` to an i32 exit code. -/// On Unix, if terminated by signal, returns 128 + `signal_number`. -pub fn exit_status_to_code(status: ExitStatus) -> i32 { - #[cfg(unix)] - { - use std::os::unix::process::ExitStatusExt; - status.code().unwrap_or_else(|| { - // Process was terminated by signal, use Unix convention: 128 + signal - status.signal().map_or(1, |sig| 128 + sig) - }) - } - #[cfg(not(unix))] - { - // Windows always has an exit code - status.code().unwrap_or(1) - } -} diff --git a/crates/vite_task/src/session/execute/cache_update.rs b/crates/vite_task/src/session/execute/cache_update.rs deleted file mode 100644 index 0cf4df1c5..000000000 --- a/crates/vite_task/src/session/execute/cache_update.rs +++ /dev/null @@ -1,429 +0,0 @@ -//! Post-run cache update: decide whether a finished spawn may be cached and, -//! if so, store its fingerprint, captured output, and output archive. - -use std::{collections::BTreeMap, sync::Arc, time::Duration}; - -use rustc_hash::FxHashSet; -use vite_path::{AbsolutePath, RelativePathBuf}; -use vite_str::Str; -use vite_task_plan::cache_metadata::{CacheMetadata, EnvValueHash}; -use vite_task_server::Reports; - -use super::{ - CacheState, - fingerprint::{PathRead, PostRunFingerprint, TrackedEnvQuery}, - glob, - spawn::ChildOutcome, -}; -use crate::{ - collections::HashMap, - session::{ - cache::{CacheEntryValue, ExecutionCache, archive}, - event::{CacheErrorKind, CacheNotUpdatedReason, CacheUpdateStatus, ExecutionError}, - }, -}; - -/// Post-execution summary of what fspy observed for a single task. Fields are -/// cfg-agnostic so the decision logic below doesn't need `cfg(fspy)` — the -/// value is only ever `Some` when tracking happened (see [`observe_fspy`]). -struct TrackingOutcome { - path_reads: HashMap, - /// Auto-output writes after output exclusions are applied. Empty when - /// `output_config.includes_auto` is false. - path_writes: FxHashSet, - /// First path that was both read and written during execution, if any. - /// A non-empty value means caching this task is unsound. - read_write_overlap: Option, -} - -type TrackedEnvValues = BTreeMap>; -type TrackedEnvQueryValues = BTreeMap>; - -/// Decide whether the finished run may be cached, and store it if so. -/// -/// Every outcome returns a `(status, error)` pair for the caller's single -/// `finish()` call; this function never reports by itself. The guard clauses -/// run in priority order — each names the reason the run is *not* cached, and -/// only a run that passes them all is stored. -#[expect( - clippy::too_many_arguments, - reason = "the run's full context is genuinely needed to decide and store the cache entry" -)] -pub(super) async fn update_cache( - cache: &ExecutionCache, - workspace_root: &Arc, - cache_dir: &AbsolutePath, - state: CacheState<'_>, - outcome: &ChildOutcome, - reports: Option<&Reports>, - duration: Duration, - cancelled: bool, -) -> (CacheUpdateStatus, Option) { - let CacheState { metadata, globbed_inputs, std_outputs, tracking } = state; - let fspy = tracking.fspy.as_ref(); - - if let Some(reports) = reports - && reports.cache_disabled - { - // A runner-aware tool short-circuited caching via `disableCache()` - // (e.g. a dev server with no deterministic output). - return (CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::ToolRequested), None); - } - - // Tool-reported paths to exclude from auto input tracking. Absolute paths - // are normalized to workspace-relative; anything outside is dropped. - let ignored_input_rels: FxHashSet = reports - .map(|r| normalize_ignored_paths(&r.ignored_inputs, workspace_root)) - .unwrap_or_default(); - let ignored_output_rels: FxHashSet = reports - .map(|r| normalize_ignored_paths(&r.ignored_outputs, workspace_root)) - .unwrap_or_default(); - - if cancelled { - // Cancelled (Ctrl-C or sibling failure) — result is untrustworthy. - return (CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::Cancelled), None); - } - - if !outcome.exit_status.success() { - // Execution failed with non-zero exit status — don't update cache. - return (CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::NonZeroExitStatus), None); - } - - let fspy_outcome = observe_fspy( - outcome, - metadata, - fspy, - &ignored_input_rels, - &ignored_output_rels, - workspace_root, - ); - - if let Some(TrackingOutcome { read_write_overlap: Some(path), .. }) = &fspy_outcome { - // fspy-inferred read-write overlap: the task wrote to a file it also - // read, so the prerun input hashes are stale and caching is unsound. - // (We only check fspy-inferred reads, not globbed_inputs. A task that - // writes to a glob-matched file without reading it produces perpetual - // cache misses but not a correctness bug.) - return ( - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::InputModified { - path: path.clone(), - }), - None, - ); - } - - if fspy_outcome.is_none() && fspy.is_some() { - // Task requested fspy auto-inference but this binary was built without - // `cfg(fspy)`. Task ran, but we can't compute a valid cache entry - // without tracked path accesses. - return (CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::FspyUnsupported), None); - } - - // Collect tool-reported tracked envs for the post-run fingerprint. Env - // names that the user already declared are skipped because their values - // are already part of the spawn fingerprint. - let (tracked_envs, tracked_env_queries) = match collect_tracked_reports(reports, metadata) { - Ok(tracked_reports) => tracked_reports, - Err(err) => { - return ( - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - Some(ExecutionError::PostRunFingerprint(err)), - ); - } - }; - - // Paths already in globbed_inputs are skipped: the overlap check above - // guarantees no input modification, so the prerun hash is the correct - // post-exec hash. - let empty_path_reads = HashMap::default(); - let path_reads = fspy_outcome.as_ref().map_or(&empty_path_reads, |o| &o.path_reads); - let post_run_fingerprint = match PostRunFingerprint::create( - path_reads, - workspace_root, - &globbed_inputs, - tracked_envs, - tracked_env_queries, - ) { - Ok(fingerprint) => fingerprint, - Err(err) => { - return ( - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - Some(ExecutionError::PostRunFingerprint(err)), - ); - } - }; - - let output_archive = match collect_and_archive_outputs( - metadata, - fspy_outcome.as_ref(), - workspace_root, - cache_dir, - ) { - Ok(archive) => archive, - Err(err) => { - return ( - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - Some(ExecutionError::Cache { kind: CacheErrorKind::Update, source: err }), - ); - } - }; - - let new_cache_value = CacheEntryValue { - post_run_fingerprint, - std_outputs: std_outputs.into(), - duration, - globbed_inputs, - output_archive, - }; - match cache.update(metadata, new_cache_value, cache_dir).await { - Ok(()) => (CacheUpdateStatus::Updated, None), - Err(err) => ( - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - Some(ExecutionError::Cache { kind: CacheErrorKind::Update, source: err }), - ), - } -} - -/// Summarize the run's fspy observations. `Some` iff tracking was both -/// requested (`tracking.fspy.is_some()`) and compiled in (`cfg(fspy)`). On a -/// `cfg(not(fspy))` build this is always `None`, and [`update_cache`] -/// short-circuits to `FspyUnsupported` when tracking was needed. -/// -/// `path_reads` is gated on `input_config.includes_auto`, filtered by -/// user-configured input negatives, and by tool-reported `ignoreInput` paths. -/// `path_writes` is filtered by user-configured output negatives and -/// tool-reported `ignoreOutput` paths before read-write overlap detection. -fn observe_fspy( - outcome: &ChildOutcome, - metadata: &CacheMetadata, - fspy: Option<&super::FspyTracking<'_>>, - ignored_input_rels: &FxHashSet, - ignored_output_rels: &FxHashSet, - workspace_root: &AbsolutePath, -) -> Option { - #[cfg(fspy)] - { - use super::tracked_accesses::TrackedPathAccesses; - - outcome.path_accesses.as_ref().map(|raw| { - let tracked = TrackedPathAccesses::from_raw(raw, workspace_root); - let filtered_path_reads: HashMap = - // fspy can be attached for auto-output-only tasks. In that - // mode reads must not become inferred inputs. - if metadata.input_config.includes_auto - && let Some(fspy) = fspy - { - tracked - .path_reads - .iter() - .filter(|(path, _)| { - !fspy.input_negative_globs.is_match(path.as_str()) - && !is_ignored(path, ignored_input_rels) - }) - .map(|(path, read)| (path.clone(), *read)) - .collect() - } else { - HashMap::default() - }; - let filtered_path_writes: FxHashSet = - // fspy can also be attached for auto-input-only tasks. In that - // mode writes must not become auto outputs or overlap candidates. - if metadata.output_config.includes_auto - && let Some(fspy) = fspy - { - tracked - .path_writes - .iter() - .filter(|path| { - !fspy.output_negative_globs.is_match(path.as_str()) - && !is_ignored(path, ignored_output_rels) - }) - .cloned() - .collect() - } else { - FxHashSet::default() - }; - let read_write_overlap = - filtered_path_reads.keys().find(|p| filtered_path_writes.contains(*p)).cloned(); - TrackingOutcome { - path_reads: filtered_path_reads, - path_writes: filtered_path_writes, - read_write_overlap, - } - }) - } - #[cfg(not(fspy))] - { - let _ = (outcome, metadata, fspy, ignored_input_rels, ignored_output_rels, workspace_root); - None - } -} - -fn collect_tracked_reports( - reports: Option<&Reports>, - metadata: &CacheMetadata, -) -> anyhow::Result<(TrackedEnvValues, TrackedEnvQueryValues)> { - reports - .map(|reports| { - let tracked_envs = collect_tracked_envs(reports, metadata)?; - let tracked_env_queries = collect_tracked_env_queries(reports)?; - Ok::<_, anyhow::Error>((tracked_envs, tracked_env_queries)) - }) - .transpose() - .map(Option::unwrap_or_default) -} - -/// Normalize tool-reported absolute paths to cleaned workspace-relative paths. -/// Paths outside the workspace are dropped — they can't contribute to inputs -/// or outputs. -fn normalize_ignored_paths( - paths: &FxHashSet>, - workspace_root: &AbsolutePath, -) -> FxHashSet { - paths - .iter() - .filter_map(|p| p.strip_prefix(workspace_root).ok().flatten()?.clean().ok()) - .collect() -} - -/// Whether `path` is covered by any `ignored` entry. An ignored entry matches -/// itself (exact file) and everything under it (directory subtree). -fn is_ignored(path: &RelativePathBuf, ignored: &FxHashSet) -> bool { - if ignored.is_empty() { - return false; - } - ignored.contains(path) || ignored.iter().any(|ig| path.strip_prefix(ig).is_some()) -} - -/// Select tool-reported env records to embed in the post-run fingerprint. -/// Names that the user already declared as fingerprinted are skipped because -/// their values are already in the spawn fingerprint. -fn collect_tracked_envs( - reports: &Reports, - metadata: &CacheMetadata, -) -> anyhow::Result { - let fingerprinted = &metadata.spawn_fingerprint.env_fingerprints().fingerprinted_envs; - let mut tracked_envs = BTreeMap::new(); - - for (name, value) in &reports.tracked_get_env { - let name_str = - name.to_str().ok_or_else(|| anyhow::anyhow!("tracked env name is not valid UTF-8"))?; - if fingerprinted.contains_key(name_str) { - continue; - } - let value = value - .as_ref() - .map(|value| { - let value_str = value.to_str().ok_or_else(|| { - anyhow::anyhow!("tracked env value for {name_str} is not valid UTF-8") - })?; - Ok::<_, anyhow::Error>(EnvValueHash::new(value_str)) - }) - .transpose()?; - tracked_envs.insert(Str::from(name_str), value); - } - - Ok(tracked_envs) -} - -/// Select tool-reported bulk env query records to embed in the post-run -/// fingerprint. The full match-set is stored as value hashes. -fn collect_tracked_env_queries(reports: &Reports) -> anyhow::Result { - let mut tracked_env_queries = BTreeMap::new(); - - for (query, record) in &reports.tracked_get_envs { - let mut matches = BTreeMap::new(); - for (name, value) in &record.matches { - let name_str = name - .to_str() - .ok_or_else(|| anyhow::anyhow!("tracked env match name is not valid UTF-8"))?; - let value_str = value.to_str().ok_or_else(|| { - anyhow::anyhow!("tracked env match value for {name_str} is not valid UTF-8") - })?; - matches.insert(Str::from(name_str), EnvValueHash::new(value_str)); - } - let query = match query { - vite_task_server::EnvQuery::Glob(pattern) => { - TrackedEnvQuery::Glob(Str::from(pattern.as_ref())) - } - vite_task_server::EnvQuery::Prefix(prefix) => { - TrackedEnvQuery::Prefix(Str::from(prefix.as_ref())) - } - }; - tracked_env_queries.insert(query, matches); - } - - Ok(tracked_env_queries) -} - -/// Collect output files and create a tar.zst archive in the cache directory. -/// -/// Output files are determined by: -/// - fspy-tracked writes (already empty when `output_config.includes_auto` is false) -/// - Positive output globs (always, if configured) -/// - Negative output globs and tool-reported `ignoreOutput` paths filter -/// fspy-tracked writes before this function receives them -/// -/// Returns `Some(archive_filename)` if files were archived, `None` if no output files. -fn collect_and_archive_outputs( - cache_metadata: &CacheMetadata, - tracking: Option<&TrackingOutcome>, - workspace_root: &AbsolutePath, - cache_dir: &AbsolutePath, -) -> anyhow::Result> { - let output_config = &cache_metadata.output_config; - - let mut output_files: FxHashSet = FxHashSet::default(); - - if let Some(t) = tracking { - output_files.extend(t.path_writes.iter().cloned()); - } - - if !output_config.positive_globs.is_empty() { - let glob_paths = glob::collect_glob_paths( - workspace_root, - &output_config.positive_globs, - &output_config.negative_globs, - )?; - output_files.extend(glob_paths); - } - - if output_files.is_empty() { - return Ok(None); - } - - let mut sorted_files: Vec = output_files.into_iter().collect(); - sorted_files.sort(); - - let archive_name: Str = vite_str::format!("{}.tar.zst", uuid::Uuid::new_v4()); - let archive_path = cache_dir.join(archive_name.as_str()); - - archive::create_output_archive(workspace_root, &sorted_files, &archive_path)?; - - Ok(Some(archive_name)) -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use rustc_hash::FxHashSet; - use vite_path::{AbsolutePath, RelativePathBuf}; - - use super::normalize_ignored_paths; - - #[test] - fn normalize_ignored_paths_cleans_relative_components() { - let workspace_root = - AbsolutePath::new(if cfg!(windows) { r"C:\repo" } else { "/repo" }).unwrap(); - let ignored = - workspace_root.join(if cfg!(windows) { r"pkg\..\cache" } else { "pkg/../cache" }); - let mut ignored_paths = FxHashSet::default(); - ignored_paths.insert(Arc::::from(ignored)); - - let normalized = normalize_ignored_paths(&ignored_paths, workspace_root); - - let expected = RelativePathBuf::new("cache").unwrap(); - assert!(normalized.contains(&expected)); - } -} diff --git a/crates/vite_task/src/session/execute/fingerprint.rs b/crates/vite_task/src/session/execute/fingerprint.rs deleted file mode 100644 index 11173d40e..000000000 --- a/crates/vite_task/src/session/execute/fingerprint.rs +++ /dev/null @@ -1,631 +0,0 @@ -//! Post-run fingerprinting for execution caching. -//! -//! This module provides types and functions for creating and validating -//! fingerprints of file system state after task execution. - -use std::{ - collections::BTreeMap, - ffi::OsStr, - fs::File, - io::{self, BufRead}, - sync::Arc, -}; - -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use rustc_hash::FxHashMap; -use serde::{Deserialize, Serialize}; -use vite_path::{AbsolutePath, RelativePathBuf}; -use vite_str::Str; -use vite_task_plan::cache_metadata::EnvValueHash; -use wincode::{SchemaRead, SchemaWrite}; - -use crate::{ - collections::HashMap, - session::cache::{EnvMismatch, InputChangeKind}, -}; - -#[derive( - SchemaWrite, SchemaRead, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, -)] -pub enum TrackedEnvQuery { - Glob(Str), - Prefix(Str), -} - -/// Path read access info -#[derive(Debug, Clone, Copy)] -pub struct PathRead { - pub read_dir_entries: bool, -} - -/// Post-run fingerprint capturing file state after execution. -/// Used to validate whether cached outputs are still valid. -#[derive(SchemaWrite, SchemaRead, Debug, Default, Serialize)] -pub struct PostRunFingerprint { - /// Paths inferred from fspy during execution with their content fingerprints. - /// Only populated when `input_config.includes_auto` is true. - pub inferred_inputs: HashMap, - - /// Env vars observed via runner-aware IPC `getEnv` with `tracked: true`. - /// Key is the env name; value is the env value hash at execution time, or - /// `None` if unset. Validated at cache lookup against the same plan env - /// context that served the original request. - pub tracked_envs: BTreeMap>, - - /// Bulk env queries (`getEnvs`) made with `tracked: true`. - /// Outer key is the query, inner map is the match-set at execution time - /// (name -> value hash). Validated at cache lookup by re-matching against - /// the current env context and comparing the resulting set. - /// - /// Non-UTF-8 env names are never matched, saved, or treated as errors: - /// they are not returned to the client, so their existence cannot affect - /// task behavior. Values are stricter. A matched env must have a UTF-8 - /// value; the JS client errors when querying a matched non-UTF-8 value, - /// and cache-hit validation treats a currently matched non-UTF-8 value as - /// a changed mismatch so stale cached output is not replayed. - pub tracked_env_queries: BTreeMap>, -} - -/// A mismatch between the stored post-run fingerprint and the current state. -#[derive(Debug, Clone)] -pub enum PostRunMismatch { - /// An inferred input file or directory changed. - Input { kind: InputChangeKind, path: RelativePathBuf }, - /// A tool-tracked env var changed value, appeared, or disappeared. - TrackedEnv(EnvMismatch), - /// A tool-tracked bulk env query's match-set changed between runs. Carries - /// the first differing entry in env-name order. - TrackedEnvQuery { query: TrackedEnvQuery, mismatch: EnvMismatch }, -} - -/// Fingerprint for a single path (file or directory) -#[derive(SchemaWrite, SchemaRead, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -pub enum PathFingerprint { - /// Path was not found when fingerprinting - NotFound, - /// File content hash using `xxHash3_64` - FileContentHash(u64), - /// Directory with optional entry listing. - /// `Folder(None)` means the directory was opened but entries were not read - /// (e.g., for `openat` calls). - /// `Folder(Some(_))` contains the directory entries sorted by name. - Folder(Option>), -} - -/// Kind of directory entry -#[derive(SchemaWrite, SchemaRead, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -pub enum DirEntryKind { - File, - Dir, - Symlink, -} - -impl PostRunFingerprint { - /// Creates a new fingerprint from path accesses after task execution. - /// - /// Negative glob filtering is done upstream (see - /// [`super::tracked_accesses::TrackedPathAccesses::from_raw`]). - /// Paths already present in `globbed_inputs` are skipped — they are - /// already tracked by the prerun glob fingerprint, and the read-write - /// overlap check in `execute_spawn` guarantees the task did not modify - /// them, so the prerun hash is still correct. - /// - /// # Arguments - /// * `inferred_path_reads` - Map of paths that were read during execution (from fspy) - /// * `base_dir` - Workspace root for resolving relative paths - /// * `globbed_inputs` - Prerun glob fingerprint; paths here are skipped - /// * `tracked_envs` - Tool-requested env vars (name -> value hash), validated on lookup - /// * `tracked_env_queries` - Tool-requested bulk env queries (query -> match-set hashes) - #[tracing::instrument(level = "debug", skip_all, name = "create_post_run_fingerprint")] - pub fn create( - inferred_path_reads: &HashMap, - base_dir: &AbsolutePath, - globbed_inputs: &BTreeMap, - tracked_envs: BTreeMap>, - tracked_env_queries: BTreeMap>, - ) -> anyhow::Result { - let inferred_inputs = inferred_path_reads - .par_iter() - .filter(|(path, _)| !globbed_inputs.contains_key(*path)) - .map(|(relative_path, path_read)| { - let full_path = Arc::::from(base_dir.join(relative_path)); - let fingerprint = fingerprint_path(&full_path, *path_read)?; - Ok((relative_path.clone(), fingerprint)) - }) - .collect::>>()?; - - Ok(Self { inferred_inputs, tracked_envs, tracked_env_queries }) - } - - /// Validates the fingerprint against current filesystem state and the - /// unfiltered env context used by runner-aware IPC. `unfiltered_envs` must - /// be the same plan env context that served the original `getEnv` request, - /// not the filtered env passed to the spawned process. - /// - /// Returns `Some(mismatch)` if anything changed, `None` if all valid. - /// Returns an error if a tracked env is currently present but cannot be - /// represented as UTF-8; treating that value as unset would make cache - /// validation unsound. - #[tracing::instrument(level = "debug", skip_all, name = "validate_post_run_fingerprint")] - pub fn validate( - &self, - base_dir: &AbsolutePath, - unfiltered_envs: &FxHashMap, Arc>, - ) -> anyhow::Result> { - let input_mismatch = self.inferred_inputs.par_iter().find_map_any( - |(input_relative_path, path_fingerprint)| { - let input_full_path = Arc::::from(base_dir.join(input_relative_path)); - let path_read = PathRead { - read_dir_entries: matches!(path_fingerprint, PathFingerprint::Folder(Some(_))), - }; - let current_path_fingerprint = match fingerprint_path(&input_full_path, path_read) { - Ok(ok) => ok, - Err(err) => return Some(Err(err)), - }; - if path_fingerprint == ¤t_path_fingerprint { - None - } else { - let (kind, entry_name) = - determine_change_kind(path_fingerprint, ¤t_path_fingerprint); - let path = if let Some(name) = entry_name { - // For folder changes, build `dir/entry` path - let entry = match RelativePathBuf::new(name.as_str()) { - Ok(p) => p, - Err(e) => return Some(Err(e.into())), - }; - input_relative_path.as_relative_path().join(entry) - } else { - input_relative_path.clone() - }; - Some(Ok(PostRunMismatch::Input { kind, path })) - } - }, - ); - if let Some(result) = input_mismatch { - return result.map(Some); - } - - for (name, stored_value) in &self.tracked_envs { - let current_value = unfiltered_envs - .get(OsStr::new(name.as_str())) - .map(|value| { - let value_str = value.to_str().ok_or_else(|| { - anyhow::anyhow!("tracked env value for {name} is not valid UTF-8") - })?; - Ok::<_, anyhow::Error>(EnvValueHash::new(value_str)) - }) - .transpose()?; - if let Some(mismatch) = - EnvMismatch::compare(name, stored_value.as_ref(), current_value.as_ref()) - { - return Ok(Some(PostRunMismatch::TrackedEnv(mismatch))); - } - } - - for (query, stored_matches) in &self.tracked_env_queries { - let current_matches = match match_env_query(query, unfiltered_envs)? { - EnvQueryValidation::Matches(matches) => matches, - EnvQueryValidation::NonUtf8Value(mismatch) => { - return Ok(Some(PostRunMismatch::TrackedEnvQuery { - query: query.clone(), - mismatch, - })); - } - }; - if let Some(mismatch) = first_env_glob_mismatch(stored_matches, ¤t_matches) { - return Ok(Some(PostRunMismatch::TrackedEnvQuery { - query: query.clone(), - mismatch, - })); - } - } - - Ok(None) - } -} - -/// Build the current match-set for `query` by enumerating the given env -/// snapshot and keeping matching UTF-8 names. If a matching env has a non-UTF-8 -/// value, return a changed mismatch so the stale cache entry is not replayed. -fn match_env_query( - query: &TrackedEnvQuery, - envs: &FxHashMap, Arc>, -) -> anyhow::Result { - Ok(match query { - TrackedEnvQuery::Glob(pattern) => { - let glob = vite_glob::env::EnvGlob::new(pattern.as_str())?; - collect_matching_envs(envs, |name| glob.is_match(name)) - } - TrackedEnvQuery::Prefix(prefix) => { - collect_matching_envs(envs, |name| env_name_starts_with(name, prefix.as_str())) - } - }) -} - -fn collect_matching_envs( - envs: &FxHashMap, Arc>, - is_match: impl Fn(&str) -> bool, -) -> EnvQueryValidation { - let mut matches = BTreeMap::new(); - for (name, value) in envs { - let Some(name_str) = name.to_str() else { - continue; - }; - if !is_match(name_str) { - continue; - } - let Some(value_str) = value.to_str() else { - return EnvQueryValidation::NonUtf8Value(EnvMismatch::Changed { - name: Str::from(name_str), - }); - }; - matches.insert(Str::from(name_str), EnvValueHash::new(value_str)); - } - EnvQueryValidation::Matches(matches) -} - -enum EnvQueryValidation { - Matches(BTreeMap), - NonUtf8Value(EnvMismatch), -} - -#[cfg(not(windows))] -fn env_name_starts_with(name: &str, prefix: &str) -> bool { - name.starts_with(prefix) -} - -#[cfg(windows)] -fn env_name_starts_with(name: &str, prefix: &str) -> bool { - let mut name_chars = name.chars(); - for prefix_char in prefix.chars() { - let Some(name_char) = name_chars.next() else { - return false; - }; - if !name_char.eq_ignore_ascii_case(&prefix_char) { - return false; - } - } - true -} - -/// Find the first deterministic difference between stored and current env -/// glob match-sets. -fn first_env_glob_mismatch( - stored: &BTreeMap, - current: &BTreeMap, -) -> Option { - let mut stored_iter = stored.iter(); - let mut current_iter = current.iter(); - let mut s = stored_iter.next(); - let mut c = current_iter.next(); - - loop { - match (s, c) { - (None, None) => return None, - (Some((name, _)), None) => return Some(EnvMismatch::Removed { name: name.clone() }), - (None, Some((name, _))) => return Some(EnvMismatch::Added { name: name.clone() }), - (Some((sn, sv)), Some((cn, cv))) => match sn.cmp(cn) { - std::cmp::Ordering::Equal => { - if sv != cv { - return Some(EnvMismatch::Changed { name: sn.clone() }); - } - s = stored_iter.next(); - c = current_iter.next(); - } - std::cmp::Ordering::Less => return Some(EnvMismatch::Removed { name: sn.clone() }), - std::cmp::Ordering::Greater => { - return Some(EnvMismatch::Added { name: cn.clone() }); - } - }, - } - } -} - -/// Determine the kind of change between two differing path fingerprints. -/// Caller guarantees `stored != current`. -/// -/// Returns `(kind, entry_name)` where `entry_name` is `Some` for folder changes -/// when a specific added/removed entry can be identified. -fn determine_change_kind<'a>( - stored: &'a PathFingerprint, - current: &'a PathFingerprint, -) -> (InputChangeKind, Option<&'a Str>) { - match (stored, current) { - (PathFingerprint::NotFound, _) => (InputChangeKind::Added, None), - (_, PathFingerprint::NotFound) => (InputChangeKind::Removed, None), - (PathFingerprint::FileContentHash(_), PathFingerprint::FileContentHash(_)) => { - (InputChangeKind::ContentModified, None) - } - (PathFingerprint::Folder(old), PathFingerprint::Folder(new)) => { - determine_folder_change_kind(old.as_ref(), new.as_ref()) - } - // Type changed (file ↔ folder) - _ => (InputChangeKind::Added, None), - } -} - -/// Determine whether a folder change is an addition or removal by comparing entries. -/// Both maps are `BTreeMap` so we iterate them in sorted lockstep. -/// Returns the specific entry name that was added or removed, if identifiable. -fn determine_folder_change_kind<'a>( - old: Option<&'a BTreeMap>, - new: Option<&'a BTreeMap>, -) -> (InputChangeKind, Option<&'a Str>) { - let (Some(old_entries), Some(new_entries)) = (old, new) else { - return (InputChangeKind::Added, None); - }; - - let mut old_iter = old_entries.iter(); - let mut new_iter = new_entries.iter(); - let mut o = old_iter.next(); - let mut n = new_iter.next(); - - loop { - match (o, n) { - (None, None) => return (InputChangeKind::Added, None), - (Some((name, _)), None) => return (InputChangeKind::Removed, Some(name)), - (None, Some((name, _))) => return (InputChangeKind::Added, Some(name)), - (Some((ok, ov)), Some((nk, nv))) => match ok.cmp(nk) { - std::cmp::Ordering::Equal => { - if ov != nv { - return (InputChangeKind::Added, Some(ok)); - } - o = old_iter.next(); - n = new_iter.next(); - } - std::cmp::Ordering::Less => return (InputChangeKind::Removed, Some(ok)), - std::cmp::Ordering::Greater => return (InputChangeKind::Added, Some(nk)), - }, - } - } -} - -/// Check if a directory entry should be ignored in fingerprinting -fn should_ignore_entry(name: &[u8]) -> bool { - matches!(name, b"." | b".." | b".DS_Store") || name.eq_ignore_ascii_case(b"dist") -} - -/// Fingerprint a single path -pub fn fingerprint_path( - path: &Arc, - path_read: PathRead, -) -> anyhow::Result { - let std_path = path.as_path(); - - let file = match File::open(std_path) { - Ok(file) => file, - Err(err) => { - // On Windows, File::open fails specifically for directories with PermissionDenied - #[cfg(windows)] - { - if err.kind() == io::ErrorKind::PermissionDenied { - // This might be a directory - try reading it as such - return process_directory(std_path, path_read); - } - // On Windows, paths with trailing backslash (from joining empty path) - // fail with NotFound (error code 3). Try as directory in this case. - if err.raw_os_error() == Some(3) && std_path.to_string_lossy().ends_with('\\') { - return process_directory(std_path, path_read); - } - } - if err.kind() != io::ErrorKind::NotFound { - tracing::trace!( - "Uncommon error when opening {:?} for fingerprinting: {}", - std_path, - err - ); - } - // Treat all open errors as NotFound for fingerprinting purposes - return Ok(PathFingerprint::NotFound); - } - }; - - let mut reader = io::BufReader::new(file); - if let Err(io_err) = reader.fill_buf() { - if io_err.kind() != io::ErrorKind::IsADirectory { - return Err(io_err.into()); - } - // Is a directory on Unix - use the optimized nix implementation - #[cfg(unix)] - { - return process_directory_unix(reader.get_ref(), path_read); - } - #[cfg(windows)] - { - return process_directory(std_path, path_read); - } - } - Ok(PathFingerprint::FileContentHash(super::hash::hash_content(reader)?)) -} - -/// Process a directory on Windows using `std::fs::read_dir` -#[cfg(windows)] -#[expect(clippy::disallowed_types, reason = "Windows fallback uses std::path::Path directly")] -fn process_directory( - path: &std::path::Path, - path_read: PathRead, -) -> anyhow::Result { - if !path_read.read_dir_entries { - return Ok(PathFingerprint::Folder(None)); - } - - let mut entries = BTreeMap::new(); - for entry in std::fs::read_dir(path)? { - let entry = entry?; - let name = entry.file_name(); - let name_bytes = name.as_encoded_bytes(); - - if should_ignore_entry(name_bytes) { - continue; - } - - let file_type = entry.file_type()?; - let kind = if file_type.is_file() { - DirEntryKind::File - } else if file_type.is_dir() { - DirEntryKind::Dir - } else { - DirEntryKind::Symlink - }; - - let name_str = name.to_string_lossy(); - entries.insert(Str::from(name_str.as_ref()), kind); - } - - Ok(PathFingerprint::Folder(Some(entries))) -} - -/// Process a directory on Unix using nix for efficiency -#[cfg(unix)] -fn process_directory_unix(file: &File, path_read: PathRead) -> anyhow::Result { - use std::os::fd::AsFd; - - if !path_read.read_dir_entries { - return Ok(PathFingerprint::Folder(None)); - } - - let fd = file.as_fd(); - let mut dir = nix::dir::Dir::from_fd(fd.try_clone_to_owned()?)?; - - let mut entries = BTreeMap::new(); - for entry in dir.iter() { - let entry = entry?; - let name = entry.file_name().to_bytes(); - - if should_ignore_entry(name) { - continue; - } - - let kind = match entry.file_type() { - Some(nix::dir::Type::Directory) => DirEntryKind::Dir, - Some(nix::dir::Type::Symlink) => DirEntryKind::Symlink, - // Treat files and other types as files for fingerprinting - _ => DirEntryKind::File, - }; - - #[expect( - clippy::disallowed_types, - reason = "from_utf8_lossy returns Cow referencing String" - )] - let name_str = String::from_utf8_lossy(name); - entries.insert(Str::from(name_str.as_ref()), kind); - } - - Ok(PathFingerprint::Folder(Some(entries))) -} - -#[cfg(test)] -mod tests { - use std::ffi::{OsStr, OsString}; - - use super::*; - - #[cfg(unix)] - fn non_utf8_os_string() -> OsString { - use std::os::unix::ffi::OsStringExt; - - OsString::from_vec(vec![0xFF]) - } - - #[cfg(windows)] - fn non_utf8_os_string() -> OsString { - use std::os::windows::ffi::OsStringExt; - - OsString::from_wide(&[0xD800]) - } - - #[test] - fn validate_errors_on_current_non_utf8_tracked_env_value() { - let mut tracked_envs = BTreeMap::new(); - tracked_envs.insert(Str::from("PROBE_ENV"), None); - let fingerprint = PostRunFingerprint { tracked_envs, ..PostRunFingerprint::default() }; - - let mut unfiltered_envs = FxHashMap::default(); - unfiltered_envs.insert( - Arc::::from(OsStr::new("PROBE_ENV")), - Arc::::from(non_utf8_os_string()), - ); - - let workspace_root = vite_path::current_dir().expect("cwd"); - let err = fingerprint - .validate(&workspace_root, &unfiltered_envs) - .expect_err("non-UTF-8 tracked env values must error"); - - assert!(err.to_string().contains("tracked env value for PROBE_ENV is not valid UTF-8")); - } - - #[test] - fn validate_reports_current_non_utf8_tracked_env_glob_value_as_changed() { - let mut tracked_env_queries = BTreeMap::new(); - tracked_env_queries.insert(TrackedEnvQuery::Glob(Str::from("PROBE_*")), BTreeMap::new()); - let fingerprint = - PostRunFingerprint { tracked_env_queries, ..PostRunFingerprint::default() }; - - let mut unfiltered_envs = FxHashMap::default(); - unfiltered_envs.insert( - Arc::::from(OsStr::new("PROBE_BAD")), - Arc::::from(non_utf8_os_string()), - ); - - let workspace_root = vite_path::current_dir().expect("cwd"); - let mismatch = - fingerprint.validate(&workspace_root, &unfiltered_envs).expect("validation succeeds"); - - match mismatch { - Some(PostRunMismatch::TrackedEnvQuery { - query, - mismatch: EnvMismatch::Changed { name }, - }) => { - assert_eq!(query, TrackedEnvQuery::Glob(Str::from("PROBE_*"))); - assert_eq!(name.as_str(), "PROBE_BAD"); - } - other => panic!("expected changed tracked env query mismatch, got {other:?}"), - } - } - - #[test] - fn validate_tracked_env_prefix_treats_star_literally() { - let mut tracked_env_queries = BTreeMap::new(); - let mut stored_matches = BTreeMap::new(); - stored_matches.insert(Str::from("PROBE_*A"), EnvValueHash::new("literal")); - tracked_env_queries.insert(TrackedEnvQuery::Prefix(Str::from("PROBE_*")), stored_matches); - let fingerprint = - PostRunFingerprint { tracked_env_queries, ..PostRunFingerprint::default() }; - - let mut unfiltered_envs = FxHashMap::default(); - unfiltered_envs.insert( - Arc::::from(OsStr::new("PROBE_*A")), - Arc::::from(OsStr::new("literal")), - ); - unfiltered_envs.insert( - Arc::::from(OsStr::new("PROBE_XA")), - Arc::::from(OsStr::new("wildcard if interpreted as glob")), - ); - - let workspace_root = vite_path::current_dir().expect("cwd"); - let mismatch = - fingerprint.validate(&workspace_root, &unfiltered_envs).expect("validation succeeds"); - - assert!(mismatch.is_none()); - } - - #[test] - fn validate_ignores_non_utf8_tracked_env_glob_names() { - let mut tracked_env_queries = BTreeMap::new(); - tracked_env_queries.insert(TrackedEnvQuery::Glob(Str::from("PROBE_*")), BTreeMap::new()); - let fingerprint = - PostRunFingerprint { tracked_env_queries, ..PostRunFingerprint::default() }; - - let mut unfiltered_envs = FxHashMap::default(); - unfiltered_envs.insert( - Arc::::from(non_utf8_os_string()), - Arc::::from(OsStr::new("value")), - ); - - let workspace_root = vite_path::current_dir().expect("cwd"); - let mismatch = - fingerprint.validate(&workspace_root, &unfiltered_envs).expect("validation succeeds"); - - assert!(mismatch.is_none()); - } -} diff --git a/crates/vite_task/src/session/execute/glob.rs b/crates/vite_task/src/session/execute/glob.rs deleted file mode 100644 index 39c92aa4e..000000000 --- a/crates/vite_task/src/session/execute/glob.rs +++ /dev/null @@ -1,448 +0,0 @@ -//! Glob-based file discovery used by cache input fingerprinting and output -//! archiving. -//! -//! Both rely on the same walker — positive globs collect candidate files, -//! negative globs filter them out. The input path adds per-file content -//! hashing on top; the output path only needs the paths. - -use std::{collections::BTreeMap, fs::File, io, ops::ControlFlow}; - -use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf}; -use vite_str::Str; -use wax::{ - Glob, - walk::{Entry as _, FileIterator as _}, -}; - -/// Walk positive globs (rooted at `root`), filtering with negative globs, and -/// call `for_each_file` with the absolute path of each file entry. Stripping -/// the root prefix (if needed) is the caller's responsibility. -/// -/// Walk errors for non-existent directories are skipped gracefully. Returning -/// `Err` from `for_each_file` aborts the walk with that error; returning -/// `Ok(ControlFlow::Break(()))` stops the walk cleanly. -fn walk_glob_files( - root: &AbsolutePath, - positive_globs: &std::collections::BTreeSet, - negative_globs: &std::collections::BTreeSet, - mut for_each_file: impl FnMut(AbsolutePathBuf) -> anyhow::Result>, -) -> anyhow::Result<()> { - if positive_globs.is_empty() { - return Ok(()); - } - - let negatives: Vec> = negative_globs - .iter() - .map(|p| Ok(Glob::new(p.as_str())?.into_owned())) - .collect::>()?; - let negation = wax::any(negatives)?; - - for pattern in positive_globs { - let glob = Glob::new(pattern.as_str())?.into_owned(); - let walk = glob.walk(root.as_path()); - for entry in walk.not(negation.clone())? { - let entry = match entry { - Ok(entry) => entry, - Err(err) => { - // WalkError -> io::Error preserves the error kind - let io_err: io::Error = err.into(); - if io_err.kind() == io::ErrorKind::NotFound { - continue; - } - return Err(io_err.into()); - } - }; - if !entry.file_type().is_file() { - continue; - } - let Some(absolute) = AbsolutePathBuf::new(entry.path().to_path_buf()) else { - continue; // wax can hand back non-absolute paths in some cases - }; - if for_each_file(absolute)?.is_break() { - return Ok(()); - } - } - } - Ok(()) -} - -/// Compute globbed inputs by walking positive glob patterns and filtering with negative patterns. -/// -/// Globs are rooted at `root` (typically the workspace root, resolved at task -/// graph stage). Returns a sorted map of paths relative to `root` to their -/// content hashes. Only files are included (directories are skipped). -pub fn compute_globbed_inputs( - root: &AbsolutePath, - positive_globs: &std::collections::BTreeSet, - negative_globs: &std::collections::BTreeSet, -) -> anyhow::Result> { - let mut result = BTreeMap::new(); - walk_glob_files(root, positive_globs, negative_globs, |absolute| { - let Some(relative) = strip_root(root, &absolute)? else { - return Ok(ControlFlow::Continue(())); // outside root - }; - let std::collections::btree_map::Entry::Vacant(vacant) = result.entry(relative) else { - return Ok(ControlFlow::Continue(())); // already hashed via earlier pattern - }; - match hash_file_content(&absolute) { - Ok(hash) => { - vacant.insert(hash); - } - Err(err) if err.kind() == io::ErrorKind::NotFound => { - // File was deleted between walk and hash, skip it - } - Err(err) => return Err(err.into()), - } - Ok(ControlFlow::Continue(())) - })?; - Ok(result) -} - -/// Collect file paths matching positive globs, filtered by negative globs. -/// -/// Like [`compute_globbed_inputs`] but only collects paths (no hashing). -/// Used for determining which output files to archive. -pub fn collect_glob_paths( - root: &AbsolutePath, - positive_globs: &std::collections::BTreeSet, - negative_globs: &std::collections::BTreeSet, -) -> anyhow::Result> { - let mut result = Vec::new(); - walk_glob_files(root, positive_globs, negative_globs, |absolute| { - if let Some(relative) = strip_root(root, &absolute)? { - result.push(relative); - } - Ok(ControlFlow::Continue(())) - })?; - result.sort(); - result.dedup(); - Ok(result) -} - -/// Strip `root` from `absolute`, returning `None` when the path is outside it. -/// Wraps [`AbsolutePath::strip_prefix`] so callers can use `?` without -/// borrowing `absolute` past the call. -fn strip_root( - root: &AbsolutePath, - absolute: &AbsolutePath, -) -> anyhow::Result> { - absolute.strip_prefix(root).map_err(|err| anyhow::anyhow!("{err}")) -} - -fn hash_file_content(path: &AbsolutePath) -> io::Result { - super::hash::hash_content(io::BufReader::new(File::open(path.as_path())?)) -} - -#[cfg(test)] -mod tests { - use std::fs; - - use tempfile::TempDir; - - use super::*; - - fn create_test_workspace() -> (TempDir, AbsolutePathBuf) { - let temp_dir = TempDir::new().unwrap(); - let workspace_root = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); - - // Create package directory structure - let package_dir = workspace_root.join("packages/my-pkg"); - fs::create_dir_all(&package_dir).unwrap(); - - // Create source files - fs::create_dir_all(package_dir.join("src")).unwrap(); - fs::write(package_dir.join("src/index.ts"), "export const a = 1;").unwrap(); - fs::write(package_dir.join("src/utils.ts"), "export const b = 2;").unwrap(); - fs::write(package_dir.join("src/utils.test.ts"), "test('a', () => {});").unwrap(); - - // Create nested directory - fs::create_dir_all(package_dir.join("src/lib")).unwrap(); - fs::write(package_dir.join("src/lib/helper.ts"), "export const c = 3;").unwrap(); - fs::write(package_dir.join("src/lib/helper.test.ts"), "test('c', () => {});").unwrap(); - - // Create other files - fs::write(package_dir.join("package.json"), "{}").unwrap(); - fs::write(package_dir.join("README.md"), "# Readme").unwrap(); - - (temp_dir, workspace_root) - } - - #[test] - fn test_empty_positive_globs_returns_empty() { - let (_temp, workspace) = create_test_workspace(); - let positive = std::collections::BTreeSet::new(); - let negative = std::collections::BTreeSet::new(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - assert!(result.is_empty()); - } - - #[test] - fn test_single_positive_glob() { - let (_temp, workspace) = create_test_workspace(); - // Globs are now workspace-root-relative - let positive: std::collections::BTreeSet = - std::iter::once("packages/my-pkg/src/**/*.ts".into()).collect(); - let negative = std::collections::BTreeSet::new(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - - // Should match all .ts files in src/ - assert_eq!(result.len(), 5); - assert!( - result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()) - ); - assert!( - result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.ts").unwrap()) - ); - assert!( - result - .contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.test.ts").unwrap()) - ); - assert!( - result - .contains_key(&RelativePathBuf::new("packages/my-pkg/src/lib/helper.ts").unwrap()) - ); - assert!(result.contains_key( - &RelativePathBuf::new("packages/my-pkg/src/lib/helper.test.ts").unwrap() - )); - } - - #[test] - fn test_positive_with_negative_exclusion() { - let (_temp, workspace) = create_test_workspace(); - let positive: std::collections::BTreeSet = - std::iter::once("packages/my-pkg/src/**/*.ts".into()).collect(); - let negative: std::collections::BTreeSet = - std::iter::once("**/*.test.ts".into()).collect(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - - // Should match only non-test .ts files - assert_eq!(result.len(), 3); - assert!( - result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()) - ); - assert!( - result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.ts").unwrap()) - ); - assert!( - result - .contains_key(&RelativePathBuf::new("packages/my-pkg/src/lib/helper.ts").unwrap()) - ); - // Test files should be excluded - assert!( - !result - .contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.test.ts").unwrap()) - ); - assert!(!result.contains_key( - &RelativePathBuf::new("packages/my-pkg/src/lib/helper.test.ts").unwrap() - )); - } - - #[test] - fn test_multiple_positive_globs() { - let (_temp, workspace) = create_test_workspace(); - let positive: std::collections::BTreeSet = - ["packages/my-pkg/src/**/*.ts".into(), "packages/my-pkg/package.json".into()] - .into_iter() - .collect(); - let negative: std::collections::BTreeSet = - std::iter::once("**/*.test.ts".into()).collect(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - - // Should include .ts files (excluding tests) plus package.json - assert_eq!(result.len(), 4); - assert!( - result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()) - ); - assert!( - result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.ts").unwrap()) - ); - assert!( - result - .contains_key(&RelativePathBuf::new("packages/my-pkg/src/lib/helper.ts").unwrap()) - ); - assert!( - result.contains_key(&RelativePathBuf::new("packages/my-pkg/package.json").unwrap()) - ); - } - - #[test] - fn test_multiple_negative_globs() { - let (_temp, workspace) = create_test_workspace(); - let positive: std::collections::BTreeSet = - ["packages/my-pkg/src/**/*.ts".into(), "packages/my-pkg/*.md".into()] - .into_iter() - .collect(); - let negative: std::collections::BTreeSet = - ["**/*.test.ts".into(), "**/*.md".into()].into_iter().collect(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - - // Should exclude both test files and markdown files - assert_eq!(result.len(), 3); - assert!( - result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()) - ); - assert!( - result.contains_key(&RelativePathBuf::new("packages/my-pkg/src/utils.ts").unwrap()) - ); - assert!( - result - .contains_key(&RelativePathBuf::new("packages/my-pkg/src/lib/helper.ts").unwrap()) - ); - assert!(!result.contains_key(&RelativePathBuf::new("packages/my-pkg/README.md").unwrap())); - } - - #[test] - fn test_negative_only_returns_empty() { - let (_temp, workspace) = create_test_workspace(); - let positive: std::collections::BTreeSet = std::collections::BTreeSet::new(); - let negative: std::collections::BTreeSet = - std::iter::once("**/*.test.ts".into()).collect(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - - // No positive globs means empty result (negative globs alone don't select anything) - assert!(result.is_empty()); - } - - #[test] - fn test_file_hashes_are_consistent() { - let (_temp, workspace) = create_test_workspace(); - let positive: std::collections::BTreeSet = - std::iter::once("packages/my-pkg/src/index.ts".into()).collect(); - let negative = std::collections::BTreeSet::new(); - - // Run twice and compare hashes - let result1 = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - let result2 = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - - assert_eq!(result1, result2); - } - - #[test] - fn test_file_hashes_change_with_content() { - let (temp, workspace) = create_test_workspace(); - let positive: std::collections::BTreeSet = - std::iter::once("packages/my-pkg/src/index.ts".into()).collect(); - let negative = std::collections::BTreeSet::new(); - - // Get initial hash - let result1 = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - let hash1 = - result1.get(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()).unwrap(); - - // Modify file content - let file_path = temp.path().join("packages/my-pkg/src/index.ts"); - fs::write(&file_path, "export const a = 999;").unwrap(); - - // Get new hash - let result2 = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - let hash2 = - result2.get(&RelativePathBuf::new("packages/my-pkg/src/index.ts").unwrap()).unwrap(); - - assert_ne!(hash1, hash2); - } - - #[test] - fn test_skips_directories() { - let (_temp, workspace) = create_test_workspace(); - // This glob could match the `src/lib` directory if not filtered - let positive: std::collections::BTreeSet = - std::iter::once("packages/my-pkg/src/*".into()).collect(); - let negative = std::collections::BTreeSet::new(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - - // Should only have files, not directories - for path in result.keys() { - assert!(!path.as_str().ends_with("/lib")); - assert!(!path.as_str().ends_with("\\lib")); - } - } - - #[test] - fn test_no_matching_files_returns_empty() { - let (_temp, workspace) = create_test_workspace(); - let positive: std::collections::BTreeSet = - std::iter::once("packages/my-pkg/nonexistent/**/*.xyz".into()).collect(); - let negative = std::collections::BTreeSet::new(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - assert!(result.is_empty()); - } - - /// Creates a workspace with sibling packages for testing cross-package globs - fn create_workspace_with_sibling() -> (TempDir, AbsolutePathBuf) { - let temp_dir = TempDir::new().unwrap(); - let workspace_root = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); - - // Create sub-pkg - let sub_pkg = workspace_root.join("packages/sub-pkg"); - fs::create_dir_all(sub_pkg.join("src")).unwrap(); - fs::write(sub_pkg.join("src/main.ts"), "export const sub = 1;").unwrap(); - - // Create sibling shared package - let shared = workspace_root.join("packages/shared"); - fs::create_dir_all(shared.join("src")).unwrap(); - fs::create_dir_all(shared.join("dist")).unwrap(); - fs::write(shared.join("src/utils.ts"), "export const shared = 1;").unwrap(); - fs::write(shared.join("dist/output.js"), "// output").unwrap(); - - (temp_dir, workspace_root) - } - - #[test] - fn test_sibling_positive_glob_matches_sibling_package() { - let (_temp, workspace) = create_workspace_with_sibling(); - // Globs are already workspace-root-relative (resolved at task graph stage) - let positive: std::collections::BTreeSet = - std::iter::once("packages/shared/src/**".into()).collect(); - let negative = std::collections::BTreeSet::new(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - assert!( - result.contains_key(&RelativePathBuf::new("packages/shared/src/utils.ts").unwrap()), - "should find sibling package file via packages/shared/src/**" - ); - } - - #[test] - fn test_sibling_negative_glob_excludes_from_sibling() { - let (_temp, workspace) = create_workspace_with_sibling(); - let positive: std::collections::BTreeSet = - std::iter::once("packages/shared/**".into()).collect(); - let negative: std::collections::BTreeSet = - std::iter::once("packages/shared/dist/**".into()).collect(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - assert!( - result.contains_key(&RelativePathBuf::new("packages/shared/src/utils.ts").unwrap()), - "should include non-excluded sibling file" - ); - assert!( - !result.contains_key(&RelativePathBuf::new("packages/shared/dist/output.js").unwrap()), - "should exclude dist via packages/shared/dist/**" - ); - } - - #[test] - fn test_overlapping_positive_globs_deduplicates() { - let (_temp, workspace) = create_test_workspace(); - // Both patterns match src/index.ts - let positive: std::collections::BTreeSet = - ["packages/my-pkg/src/**/*.ts".into(), "packages/my-pkg/src/index.ts".into()] - .into_iter() - .collect(); - let negative: std::collections::BTreeSet = - std::iter::once("**/*.test.ts".into()).collect(); - - let result = compute_globbed_inputs(&workspace, &positive, &negative).unwrap(); - - // BTreeMap naturally deduplicates by key - assert_eq!(result.len(), 3); - } -} diff --git a/crates/vite_task/src/session/execute/hash.rs b/crates/vite_task/src/session/execute/hash.rs deleted file mode 100644 index 0bbfb0fac..000000000 --- a/crates/vite_task/src/session/execute/hash.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::{hash::Hasher as _, io}; - -/// Hash content using 8 KiB buffered `xxHash3_64`. -pub(super) fn hash_content(mut stream: impl io::Read) -> io::Result { - let mut hasher = twox_hash::XxHash3_64::default(); - let mut buf = [0u8; 8192]; - loop { - let n = stream.read(&mut buf)?; - if n == 0 { - break; - } - hasher.write(&buf[..n]); - } - Ok(hasher.finish()) -} diff --git a/crates/vite_task/src/session/execute/mod.rs b/crates/vite_task/src/session/execute/mod.rs deleted file mode 100644 index 1813051e2..000000000 --- a/crates/vite_task/src/session/execute/mod.rs +++ /dev/null @@ -1,623 +0,0 @@ -mod cache_update; -pub mod fingerprint; -pub mod glob; -mod hash; -pub mod pipe; -mod scheduler; -pub mod spawn; -#[cfg(fspy)] -pub mod tracked_accesses; -#[cfg(windows)] -mod win_job; - -use std::{ - collections::BTreeMap, - ffi::{OsStr, OsString}, - sync::Arc, - time::Instant, -}; - -use futures_util::future::LocalBoxFuture; -use tokio_util::sync::CancellationToken; -use vite_glob::path::PathGlobSet; -use vite_path::{AbsolutePath, RelativePathBuf}; -use vite_task_ipc_shared::NODE_CLIENT_PATH_ENV_NAME; -use vite_task_plan::{SpawnExecution, cache_metadata::CacheMetadata}; -use vite_task_server::{Recorder, Reports, ServerHandle, StopAccepting, serve}; - -use self::{ - glob::compute_globbed_inputs, - pipe::{PipeSinks, StdOutput, pipe_stdio}, - spawn::{ChildHandle, ChildOutcome, SpawnStdio, spawn}, -}; -use super::{ - cache::{CacheEntryValue, CacheMiss, ExecutionCache, archive}, - event::{ - CacheDisabledReason, CacheErrorKind, CacheNotUpdatedReason, CacheStatus, CacheUpdateStatus, - ExecutionError, - }, - reporter::{LeafExecutionReporter, PipeWriters, StdioConfig, StdioSuggestion}, -}; - -/// Outcome of a spawned execution. -/// -/// Returned by [`execute_spawn`] to communicate what happened. Errors are -/// already reported through `LeafExecutionReporter::finish()` before this -/// value is returned — the caller does not need to handle error display. -pub enum SpawnOutcome { - /// Cache hit — no process was spawned. Cached outputs were replayed. - CacheHit, - /// Process was spawned and exited with this status. - Spawned(std::process::ExitStatus), - /// An infrastructure error prevented the process from running - /// (cache lookup failure or spawn failure). - /// Already reported through the leaf reporter. - Failed, -} - -/// All valid runtime configurations for a leaf execution, after the cache-hit -/// early-return has been ruled out. -/// -/// The type shape enforces two invariants statically: -/// - fspy tracking only exists inside [`ExecutionMode::Cached`] (fspy requires -/// `includes_auto`, which only lives on cache metadata). -/// - Cached execution always owns [`PipeWriters`] (piped stdio is forced so -/// that output can be captured for replay). -enum ExecutionMode<'a> { - Cached { - /// Borrowed by [`PipeSinks`] during drain; dropped at end of function. - pipe_writers: PipeWriters, - /// Carried through drain into the cache-update phase. Drain writes - /// into `state.std_outputs` in place via a borrow inside `PipeSinks`. - state: CacheState<'a>, - }, - Uncached { - /// `Some` iff the reporter suggested piped stdio. `None` means the - /// child inherits stdin/stdout/stderr from the parent; the reporter's - /// writers were dropped here so we don't hold `std::io::Stdout` while - /// the child writes to the same FD. - pipe_writers: Option, - }, -} - -/// Cached-only state carried from mode construction through the cache-update -/// phase. `std_outputs` starts empty and is written in place during drain via -/// a borrow inside [`PipeSinks::capture`]. -struct CacheState<'a> { - metadata: &'a CacheMetadata, - globbed_inputs: BTreeMap, - /// Captured stdout/stderr for cache replay. Written in place during drain; - /// always present (possibly empty) once we reach the cache-update phase. - std_outputs: Vec, - /// Runner-aware tracking for cached tasks: an IPC server is always - /// available, and fspy path tracing is attached only when auto input or - /// output inference needs it. Parts are borrowed in place during the - /// wait/join; the struct is never moved out. - tracking: Tracking<'a>, -} - -/// The IPC server's driver future: resolves with the recorded reports after -/// [`StopAccepting::signal`] fires and all in-flight clients drain. -type IpcDriver = LocalBoxFuture<'static, Result>; - -/// fspy path-tracking state, present only when a cached task needs automatic -/// input or output inference. -struct FspyTracking<'a> { - input_negative_globs: PathGlobSet<'a>, - output_negative_globs: PathGlobSet<'a>, -} - -/// Per-task runner-aware tracking: IPC server handle plus optional fspy state. -/// Lifetime-tied to a single `execute_spawn` call. -struct Tracking<'a> { - fspy: Option>, - ipc_envs: Vec<(&'static OsStr, OsString)>, - ipc_server_fut: IpcDriver, - stop_accepting: StopAccepting, -} - -/// The IPC server's handles for the run phase. The two halves are -/// inseparable — whenever the driver is polled, `stop_accepting` must be -/// signalled after the child exits or the driver never resolves — so they -/// travel as one value and a driver-without-signal state is unrepresentable. -struct IpcHandles<'m> { - /// Signalled after the child exits so the server stops accepting and - /// drains. - stop_accepting: &'m StopAccepting, - /// The server's driver, polled alongside the child. - driver: &'m mut IpcDriver, -} - -/// Mode-scoped borrows for the run phase, extracted in one pass so the pipe -/// sinks and the IPC handles can be borrowed simultaneously (disjoint fields -/// of the same mode). -struct RunHandles<'m> { - /// Pipe writers + capture slot. `None` only in the inherited-uncached - /// case, where there are no pipes to drain. - sinks: Option>, - /// The IPC server's handles. `None` iff execution is uncached. - ipc: Option>, -} - -impl<'a> ExecutionMode<'a> { - /// Fold the cache/fspy/stdio decisions and their associated state into a - /// single value whose shape encodes the valid combinations. The - /// uncached-inherited arm drops `stdio_config`'s writers here so we don't - /// hold `std::io::Stdout` while the child writes to the same FD. - /// - /// ───────────────────────────────────────────────────────────────────── - /// Before adding a new local variable alongside the mode: think twice. - /// Does it make sense for every variant, or only for some? If it's - /// variant-specific (only for `Cached`, only when fspy is on, etc.) put - /// it inside the variant (or `CacheState`) so the compiler enforces the - /// invariant at construction. Sibling locals drift out of sync with the - /// mode and force re-derivation (`if let Some(_) = _`, - /// `cache_metadata.is_some_and(_)`) at every downstream use site. - /// ───────────────────────────────────────────────────────────────────── - fn build( - cache_metadata: Option<&'a CacheMetadata>, - stdio_config: StdioConfig, - globbed_inputs: BTreeMap, - ) -> Result { - let Some(metadata) = cache_metadata else { - return Ok(Self::Uncached { - pipe_writers: (stdio_config.suggestion == StdioSuggestion::Piped) - .then_some(stdio_config.writers), - }); - }; - - let fspy = if metadata.input_config.includes_auto || metadata.output_config.includes_auto { - // Resolve negative globs for fspy path filtering (already - // workspace-root-relative). - let input_negative_globs = PathGlobSet::new(&metadata.input_config.negative_globs) - .map_err(|err| ExecutionError::PostRunFingerprint(err.into()))?; - let output_negative_globs = PathGlobSet::new(&metadata.output_config.negative_globs) - .map_err(|err| ExecutionError::PostRunFingerprint(err.into()))?; - Some(FspyTracking { input_negative_globs, output_negative_globs }) - } else { - None - }; - - // Bind runner IPC for every cached task. The merged cache-control API - // (`disableCache`) must work even when a task uses explicit inputs and - // therefore does not need fspy auto-input inference. - let (ipc_envs, ServerHandle { driver, stop_accepting }) = - serve(Recorder::new(Arc::clone(&metadata.unfiltered_envs))) - .map_err(ExecutionError::IpcServerBind)?; - let tracking = - Tracking { fspy, ipc_envs: ipc_envs.collect(), ipc_server_fut: driver, stop_accepting }; - - Ok(Self::Cached { - pipe_writers: stdio_config.writers, - state: CacheState { metadata, globbed_inputs, std_outputs: Vec::new(), tracking }, - }) - } - - /// The extra envs to inject into the child: IPC connection info + the - /// napi addon path runner-aware tools `require()`. Empty when execution - /// is uncached. - fn injected_envs(&self) -> Vec<(&OsStr, &OsStr)> { - match self { - Self::Cached { state: CacheState { tracking: t, .. }, .. } => { - let mut envs: Vec<(&OsStr, &OsStr)> = - t.ipc_envs.iter().map(|(k, v)| (*k, v.as_os_str())).collect(); - envs.push(( - OsStr::new(NODE_CLIENT_PATH_ENV_NAME), - crate::napi_client::napi_client_path().as_path().as_os_str(), - )); - envs - } - Self::Uncached { .. } => Vec::new(), - } - } - - /// The arguments `spawn()` derives from the mode: stdio handling and - /// whether fspy tracking is on. - const fn spawn_config(&self) -> (SpawnStdio, bool) { - match self { - Self::Cached { state, .. } => (SpawnStdio::Piped, state.tracking.fspy.is_some()), - Self::Uncached { pipe_writers: Some(_) } => (SpawnStdio::Piped, false), - Self::Uncached { pipe_writers: None } => (SpawnStdio::Inherited, false), - } - } - - /// Extract all mode-scoped borrows for the run phase in one pass: the - /// pipe sinks plus the IPC server's handles (disjoint field borrows - /// inside the same match arm). - fn run_handles(&mut self) -> RunHandles<'_> { - match self { - Self::Cached { pipe_writers, state } => { - let sinks = Some(PipeSinks { - stdout_writer: &mut pipe_writers.stdout_writer, - stderr_writer: &mut pipe_writers.stderr_writer, - capture: Some(&mut state.std_outputs), - }); - let ipc = Some(IpcHandles { - stop_accepting: &state.tracking.stop_accepting, - driver: &mut state.tracking.ipc_server_fut, - }); - RunHandles { sinks, ipc } - } - Self::Uncached { pipe_writers: Some(pipe_writers) } => RunHandles { - sinks: Some(PipeSinks { - stdout_writer: &mut pipe_writers.stdout_writer, - stderr_writer: &mut pipe_writers.stderr_writer, - capture: None, - }), - ipc: None, - }, - Self::Uncached { pipe_writers: None } => RunHandles { sinks: None, ipc: None }, - } - } -} - -/// Everything the pipeline reports through the single -/// `LeafExecutionReporter::finish()` call at the end of [`execute_spawn`]. -/// -/// Phases construct a `Report` instead of reporting in place, so finishing — -/// which consumes the boxed reporter — happens in exactly one spot. Each -/// variant carries exactly the data its outcome can be accompanied by: -/// a failure always has an error and never an exit status, a cache hit has -/// neither, and a spawned process always has its exit status. Nonsense -/// pairings are unrepresentable. -enum Report { - /// An infrastructure error prevented the process from running (or from - /// being observed to completion). - Failed { cache_update: CacheUpdateStatus, error: ExecutionError }, - /// Cache hit: captured outputs were replayed, no process ran. - CacheHit, - /// The process ran to completion; its exit status is reported regardless - /// of how the cache update went. - Spawned { - exit_status: std::process::ExitStatus, - cache_update: CacheUpdateStatus, - error: Option, - }, -} - -impl Report { - /// An infrastructure failure outside any cache-specific context. - const fn failed(error: ExecutionError) -> Self { - Self::Failed { - cache_update: CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - error, - } - } - - /// Perform the pipeline's single `finish()` call and yield the outcome - /// [`execute_spawn`] returns to its caller. - fn finish(self, reporter: Box) -> SpawnOutcome { - match self { - Self::Failed { cache_update, error } => { - reporter.finish(None, cache_update, Some(error)); - SpawnOutcome::Failed - } - Self::CacheHit => { - reporter.finish( - None, - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheHit), - None, - ); - SpawnOutcome::CacheHit - } - Self::Spawned { exit_status, cache_update, error } => { - reporter.finish(Some(exit_status), cache_update, error); - SpawnOutcome::Spawned(exit_status) - } - } - } -} - -/// Execute a spawned process with cache-aware lifecycle. -/// -/// This is a free function (not tied to the scheduler's context) so it can be -/// reused from both graph-based execution and standalone synthetic execution. -/// -/// The full lifecycle is: -/// 1. Cache lookup (determines cache status) -/// 2. `leaf_reporter.start(cache_status)` → `StdioConfig` -/// 3. If cache hit: replay cached outputs via `StdioConfig` writers -/// 4. Otherwise: `spawn()` with the chosen stdio mode, drain pipes and wait -/// via [`run_child`], then decide the cache update -/// ([`cache_update::update_cache`]) -/// -/// Every path reports through the single `finish()` below — errors (cache -/// lookup failure, spawn failure, cache update failure) do not abort the -/// caller. -#[tracing::instrument(level = "debug", skip_all)] -#[expect( - clippy::too_many_arguments, - reason = "these are the unavoidable inputs for a free-function cache-aware spawn" -)] -pub async fn execute_spawn( - mut leaf_reporter: Box, - spawn_execution: &SpawnExecution, - cache: &ExecutionCache, - workspace_root: &Arc, - cache_dir: &AbsolutePath, - program_name: &str, - fast_fail_token: CancellationToken, - interrupt_token: CancellationToken, -) -> SpawnOutcome { - let pipeline = run( - leaf_reporter.as_mut(), - spawn_execution, - cache, - workspace_root, - cache_dir, - program_name, - fast_fail_token, - interrupt_token, - ); - let report = match pipeline.await { - Ok(report) | Err(report) => report, - }; - report.finish(leaf_reporter) -} - -/// The spawn pipeline. -/// -/// Both sides of the `Result` carry the same [`Report`] on purpose: `Err` is -/// the `?`-short-circuit channel for phases that already determined the final -/// report, `Ok` is the report of a pipeline that ran to the end. The caller -/// unwraps both into the same single `finish()`, so the distinction is pure -/// control flow and a value on either side is equally valid. -#[expect(clippy::too_many_arguments, reason = "forwarded verbatim from `execute_spawn`")] -async fn run( - reporter: &mut dyn LeafExecutionReporter, - spawn_execution: &SpawnExecution, - cache: &ExecutionCache, - workspace_root: &Arc, - cache_dir: &AbsolutePath, - program_name: &str, - fast_fail_token: CancellationToken, - interrupt_token: CancellationToken, -) -> Result { - let cache_metadata = spawn_execution.cache_metadata.as_ref(); - - // 1. Determine cache status FIRST by trying cache hit, so the reporter can - // display cache status immediately when execution begins. On a lookup - // error, `start()` is never called — there is no valid status to show. - let lookup = lookup_cache(cache_metadata, cache, workspace_root).await?; - - // 2. Report execution start with the looked-up cache status (`start()` - // runs exactly once on every arm) and either replay the hit — no need - // to execute the command — or carry the globbed inputs into the run. - let (stdio_config, globbed_inputs) = match lookup { - CacheLookup::Hit(cached) => { - let mut stdio_config = - reporter.start(CacheStatus::Hit { replayed_duration: cached.duration }); - return Ok(replay_cache_hit( - &mut stdio_config, - &cached, - workspace_root, - cache_dir, - program_name, - )); - } - CacheLookup::Miss { miss, globbed_inputs } => { - (reporter.start(CacheStatus::Miss(miss)), globbed_inputs) - } - CacheLookup::Disabled => ( - reporter.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)), - BTreeMap::new(), - ), - }; - - // 4. Fold the cache/fspy/stdio decisions into the typed mode. - let mut mode = ExecutionMode::build(cache_metadata, stdio_config, globbed_inputs) - .map_err(Report::failed)?; - - // Measure end-to-end duration here — spawn() doesn't track time. - let start = Instant::now(); - - // 5. Spawn. Returns pipes (Piped) or `None` (Inherited) plus a - // cancellation-aware wait future. - let (spawn_stdio, fspy_enabled) = mode.spawn_config(); - let child = spawn( - &spawn_execution.spawn_command, - fspy_enabled, - spawn_stdio, - fast_fail_token.clone(), - mode.injected_envs(), - ) - .await - .map_err(|err| Report::failed(ExecutionError::Spawn(err)))?; - - // 6. Drain the pipes and wait for exit, concurrently with the IPC driver. - // The driver must be polled during pipe drain — otherwise a tool doing - // a blocking `getEnv` can deadlock: child stalls on IPC, stdout stays - // open, pipe_stdio waits for EOF, driver never runs. `stop_accepting` - // fires after child.wait so the driver drains any in-flight clients. - // Box::pin keeps the child-and-pipe stack off the enclosing future: - // pipe_stdio alone makes the combined future large enough to trip - // clippy::large_futures in every caller otherwise. - let RunHandles { sinks, ipc } = mode.run_handles(); - let (wait_result, ipc_server_result) = if let Some(IpcHandles { stop_accepting, driver }) = ipc - { - let child_work = - Box::pin(run_child(child, sinks, Some(stop_accepting), fast_fail_token.clone())); - let (wait_result, join_result) = tokio::join!(child_work, driver); - if let Err(e) = &join_result { - tracing::warn!(?e, "IPC server failed; cache will not be updated"); - } - (wait_result, Some(join_result.map(Recorder::into_reports))) - } else { - let child_work = Box::pin(run_child(child, sinks, None, fast_fail_token.clone())); - (child_work.await, None) - }; - let outcome = wait_result.map_err(Report::failed)?; - let duration = start.elapsed(); - - // Extract reports, or short-circuit when the IPC server failed. An Err - // here means reports may be incomplete: caching this run would risk - // stale inputs/outputs, so skip all cache-related computation entirely. - let reports: Option = match ipc_server_result { - Some(Ok(reports)) => { - tracing::debug!(?reports, "runner-aware tools reported"); - Some(reports) - } - None => None, - Some(Err(err)) => { - return Err(Report::Spawned { - exit_status: outcome.exit_status, - cache_update: CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::IpcServerError( - err, - )), - error: None, - }); - } - }; - - // 7. Decide the cache update (only when we were in `Cached` mode). Cache - // update errors are reported but do not affect the exit status we - // return — the process ran, so we return its actual status. - let cancelled = fast_fail_token.is_cancelled() || interrupt_token.is_cancelled(); - let (cache_update, error) = match mode { - ExecutionMode::Cached { state, .. } => { - cache_update::update_cache( - cache, - workspace_root, - cache_dir, - state, - &outcome, - reports.as_ref(), - duration, - cancelled, - ) - .await - } - ExecutionMode::Uncached { .. } => { - // Caching was disabled for this task. - (CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), None) - } - }; - - Ok(Report::Spawned { exit_status: outcome.exit_status, cache_update, error }) -} - -/// Outcome of the cache-lookup phase. Each variant carries exactly what that -/// outcome provides: a hit owns the cached entry to replay, a miss keeps the -/// reason plus the globbed inputs (reused by the cache-update phase after the -/// run), and disabled has neither. -enum CacheLookup { - /// Cache hit — the cached entry to replay. - Hit(CacheEntryValue), - /// Cache miss — the detailed reason (`NotFound` or `FingerprintMismatch`). - Miss { miss: CacheMiss, globbed_inputs: BTreeMap }, - /// Caching is disabled for this task (no cache metadata). - Disabled, -} - -/// Phase 1: compute the globbed inputs and try to hit the cache. -async fn lookup_cache( - cache_metadata: Option<&CacheMetadata>, - cache: &ExecutionCache, - workspace_root: &Arc, -) -> Result { - let Some(cache_metadata) = cache_metadata else { - return Ok(CacheLookup::Disabled); - }; - - // Compute globbed inputs from positive globs at execution time. - // Globs are already workspace-root-relative (resolved at task graph stage). - let globbed_inputs = compute_globbed_inputs( - workspace_root, - &cache_metadata.input_config.positive_globs, - &cache_metadata.input_config.negative_globs, - ) - .map_err(|err| { - Report::failed(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }) - })?; - - match cache.try_hit(cache_metadata, &globbed_inputs, workspace_root).await { - Ok(Ok(cached)) => Ok(CacheLookup::Hit(cached)), - Ok(Err(miss)) => Ok(CacheLookup::Miss { miss, globbed_inputs }), - Err(err) => { - Err(Report::failed(ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err })) - } - } -} - -/// Phase 3 (cache hit): replay the captured stdout/stderr and restore the -/// output archive. -fn replay_cache_hit( - stdio_config: &mut StdioConfig, - cached: &CacheEntryValue, - workspace_root: &Arc, - cache_dir: &AbsolutePath, - program_name: &str, -) -> Report { - for output in cached.std_outputs.iter() { - let writer: &mut dyn std::io::Write = match output.kind { - pipe::OutputKind::StdOut => &mut stdio_config.writers.stdout_writer, - pipe::OutputKind::StdErr => &mut stdio_config.writers.stderr_writer, - }; - let _ = writer.write_all(&output.content); - let _ = writer.flush(); - } - - // Restore output files from the cached archive. Failure here means the - // archive file is missing, truncated, or otherwise unreadable — the - // task can't proceed because the cache promised the outputs would be - // restored. Surface a recovery instruction rather than just the raw - // I/O error so users know to clear the cache. - if let Some(ref archive_name) = cached.output_archive { - let archive_path = cache_dir.join(archive_name.as_str()); - if let Err(err) = archive::extract_output_archive(workspace_root, &archive_path) { - let err = err.context(vite_str::format!( - "failed to restore cached outputs from {}; the archive may have been deleted \ - or corrupted. Run `{program_name} cache clean` to clear the cache.", - archive_path.as_path().display() - )); - return Report::Failed { - cache_update: CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheHit), - error: ExecutionError::Cache { kind: CacheErrorKind::Lookup, source: err }, - }; - } - } - - Report::CacheHit -} - -/// Phase 6: drain the child's pipes (if piped) and wait for exit. A pipe -/// failure cancels the child so the wait future kills it instead of orphaning -/// it. After the child exits (on every path), `stop_accepting` is signalled so -/// the IPC server stops accepting and starts draining. -async fn run_child( - mut child: ChildHandle, - sinks: Option>, - stop_accepting: Option<&StopAccepting>, - fast_fail_token: CancellationToken, -) -> Result { - let pipe_result: Result<(), ExecutionError> = if let Some(sinks) = sinks { - let stdout = child.stdout.take().expect("SpawnStdio::Piped yields a stdout pipe"); - let stderr = child.stderr.take().expect("SpawnStdio::Piped yields a stderr pipe"); - #[expect( - clippy::large_futures, - reason = "pipe_stdio streams child I/O and creates a large future" - )] - let r = pipe_stdio(stdout, stderr, sinks, fast_fail_token.clone()).await; - r.map_err(|err| ExecutionError::ForwardTaskProcessOutput(err.into())) - } else { - Ok(()) - }; - - let wait_result = match pipe_result { - Ok(()) => { - child.wait.await.map_err(|err| ExecutionError::WaitForTaskProcessExit(err.into())) - } - Err(err) => { - // Pipe failed — cancel so `child.wait` kills the child instead of - // orphaning it. Still signal the server below so it can drain. - fast_fail_token.cancel(); - let _ = child.wait.await; - Err(err) - } - }; - - if let Some(stop_accepting) = stop_accepting { - stop_accepting.signal(); - } - wait_result -} diff --git a/crates/vite_task/src/session/execute/pipe.rs b/crates/vite_task/src/session/execute/pipe.rs deleted file mode 100644 index 359d3a03b..000000000 --- a/crates/vite_task/src/session/execute/pipe.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! Drain child stdout/stderr concurrently to writers, with optional capture. - -use std::io::Write; - -use serde::Serialize; -use tokio::{ - io::AsyncReadExt as _, - process::{ChildStderr, ChildStdout}, -}; -use tokio_util::sync::CancellationToken; -use wincode::{SchemaRead, SchemaWrite}; - -/// Output kind for stdout/stderr -#[derive(Debug, PartialEq, Eq, Clone, Copy, SchemaWrite, SchemaRead, Serialize)] -pub enum OutputKind { - StdOut, - StdErr, -} - -/// Output chunk with stream kind -#[derive(Debug, SchemaWrite, SchemaRead, Serialize, Clone)] -pub struct StdOutput { - pub kind: OutputKind, - pub content: Vec, -} - -/// Downstream destinations for bytes read from the child's stdout/stderr: -/// two pass-through writers plus an optional capture buffer (populated in -/// place during drain for cache replay). -pub struct PipeSinks<'a> { - pub stdout_writer: &'a mut dyn Write, - pub stderr_writer: &'a mut dyn Write, - pub capture: Option<&'a mut Vec>, -} - -/// Drain the child's stdout/stderr concurrently into `sinks`. -/// -/// Bytes are written through `sinks.stdout_writer` / `sinks.stderr_writer` in -/// real time and, when `sinks.capture` is `Some`, also appended (with adjacent -/// same-kind chunks coalesced) for cache replay. -/// -/// On cancellation: returns `Ok(())` without killing the child — the caller -/// drives the child's cancellation-aware `wait` future next, which observes the -/// same already-fired token and performs the kill. Dropping `stdout`/`stderr` -/// closes the pipe read ends (EPIPE on Unix, `ERROR_BROKEN_PIPE` on Windows). -#[tracing::instrument(level = "debug", skip_all)] -pub async fn pipe_stdio( - mut stdout: ChildStdout, - mut stderr: ChildStderr, - mut sinks: PipeSinks<'_>, - cancellation_token: CancellationToken, -) -> std::io::Result<()> { - let mut stdout_buf = [0u8; 8192]; - let mut stderr_buf = [0u8; 8192]; - let mut stdout_done = false; - let mut stderr_done = false; - - loop { - if stdout_done && stderr_done { - return Ok(()); - } - tokio::select! { - result = stdout.read(&mut stdout_buf), if !stdout_done => { - match result? { - 0 => stdout_done = true, - n => { - let bytes = &stdout_buf[..n]; - sinks.stdout_writer.write_all(bytes)?; - sinks.stdout_writer.flush()?; - if let Some(capture) = &mut sinks.capture { - append_output_chunk(capture, OutputKind::StdOut, bytes); - } - } - } - } - result = stderr.read(&mut stderr_buf), if !stderr_done => { - match result? { - 0 => stderr_done = true, - n => { - let bytes = &stderr_buf[..n]; - sinks.stderr_writer.write_all(bytes)?; - sinks.stderr_writer.flush()?; - if let Some(capture) = &mut sinks.capture { - append_output_chunk(capture, OutputKind::StdErr, bytes); - } - } - } - } - () = cancellation_token.cancelled() => { - return Ok(()); - } - } - } -} - -fn append_output_chunk(capture: &mut Vec, kind: OutputKind, bytes: &[u8]) { - if let Some(last) = capture.last_mut() - && last.kind == kind - { - last.content.extend_from_slice(bytes); - } else { - capture.push(StdOutput { kind, content: bytes.to_vec() }); - } -} diff --git a/crates/vite_task/src/session/execute/scheduler.rs b/crates/vite_task/src/session/execute/scheduler.rs deleted file mode 100644 index f4034e38a..000000000 --- a/crates/vite_task/src/session/execute/scheduler.rs +++ /dev/null @@ -1,275 +0,0 @@ -//! DAG scheduling for execution graphs: decides *which* leaf runs *when* -//! (dependency order, concurrency limits, fast-fail), and hands each leaf to -//! [`execute_spawn`] which owns *how* a single spawn runs. - -use std::{cell::RefCell, io::Write as _, sync::Arc}; - -use futures_util::{FutureExt, StreamExt, future::LocalBoxFuture, stream::FuturesUnordered}; -use petgraph::Direction; -use rustc_hash::FxHashMap; -use tokio::sync::Semaphore; -use tokio_util::sync::CancellationToken; -use vite_path::AbsolutePath; -use vite_task_plan::{ - ExecutionGraph, ExecutionItemDisplay, ExecutionItemKind, LeafExecutionKind, - execution_graph::ExecutionNodeIndex, -}; - -use super::{SpawnOutcome, execute_spawn}; -use crate::{ - Session, - session::{ - cache::ExecutionCache, - event::{CacheDisabledReason, CacheNotUpdatedReason, CacheStatus, CacheUpdateStatus}, - reporter::{ExitStatus, GraphExecutionReporter, GraphExecutionReporterBuilder}, - }, -}; - -/// Holds shared references needed during graph execution. -/// -/// The `reporter` field is wrapped in `RefCell` because concurrent futures -/// (via `FuturesUnordered`) need shared access to create leaf reporters. -/// Since all futures run on a single thread (no `tokio::spawn`), `RefCell` -/// is sufficient for interior mutability. -/// -/// Cache fields are passed through to [`execute_spawn`] for cache-aware execution. -struct ExecutionContext<'a> { - /// The graph-level reporter, used to create leaf reporters via `new_leaf_execution()`. - /// Wrapped in `RefCell` for shared access from concurrent task futures. - reporter: &'a RefCell>, - /// The execution cache for looking up and storing cached results. - cache: &'a ExecutionCache, - /// Workspace root that relative paths in cache entries (inputs, outputs, - /// archives) are resolved against. - workspace_root: &'a Arc, - /// Directory where cache files (db, archives) are stored. - cache_dir: &'a AbsolutePath, - /// Public-facing program name (e.g. `vp`), used in user-facing error - /// messages that suggest a CLI command (e.g. `cache clean`). - program_name: &'a str, - /// Token cancelled when a task fails. Kills in-flight child processes - /// (via `start_kill` in spawn.rs), prevents scheduling new tasks, and - /// prevents caching results of concurrently-running tasks. - fast_fail_token: CancellationToken, - /// Token cancelled by Ctrl-C. Unlike `fast_fail_token` (which kills - /// children), this only prevents scheduling new tasks and caching - /// results — running processes are left to handle SIGINT naturally. - interrupt_token: CancellationToken, -} - -impl ExecutionContext<'_> { - /// Returns true if execution has been cancelled, either by a task - /// failure (fast-fail) or by Ctrl-C (interrupt). - fn cancelled(&self) -> bool { - self.fast_fail_token.is_cancelled() || self.interrupt_token.is_cancelled() - } - - /// Execute all tasks in an execution graph concurrently, respecting dependencies. - /// - /// Uses a DAG scheduler: tasks whose dependencies have all completed are scheduled - /// onto a `FuturesUnordered`, bounded by a per-graph `Semaphore` with - /// `concurrency_limit` permits. Each recursive `Expanded` graph creates its own - /// semaphore, so nested graphs have independent concurrency limits. - /// - /// Fast-fail: if any task fails, `execute_leaf` cancels the `fast_fail_token` - /// (killing in-flight child processes). Ctrl-C cancels the `interrupt_token`. - /// Either cancellation causes this method to close the semaphore, drain - /// remaining futures, and return. - #[tracing::instrument(level = "debug", skip_all)] - async fn execute_expanded_graph(&self, graph: &ExecutionGraph) { - if graph.graph.node_count() == 0 { - return; - } - - let semaphore = - Arc::new(Semaphore::new(graph.concurrency_limit.min(Semaphore::MAX_PERMITS))); - - // Compute dependency count for each node. - // Edge A→B means "A depends on B", so A's dependency count = outgoing edge count. - let mut dep_count: FxHashMap = FxHashMap::default(); - for node_ix in graph.graph.node_indices() { - dep_count.insert(node_ix, graph.graph.neighbors(node_ix).count()); - } - - let mut futures = FuturesUnordered::new(); - - // Schedule initially ready nodes (no dependencies). - for (&node_ix, &count) in &dep_count { - if count == 0 { - futures.push(self.spawn_node(graph, node_ix, &semaphore)); - } - } - - // Process completions and schedule newly ready dependents. - // On failure, `execute_leaf` cancels the token — we detect it here, close - // the semaphore (so pending acquires fail immediately), and drain. - while let Some(completed_ix) = futures.next().await { - if self.cancelled() { - semaphore.close(); - while futures.next().await.is_some() {} - return; - } - - // Find dependents of the completed node (nodes that depend on it). - // Edge X→completed means "X depends on completed", so X is a predecessor - // in graph direction = neighbor in Incoming direction. - for dependent in graph.graph.neighbors_directed(completed_ix, Direction::Incoming) { - let count = dep_count.get_mut(&dependent).expect("all nodes are in dep_count"); - *count -= 1; - if *count == 0 { - futures.push(self.spawn_node(graph, dependent, &semaphore)); - } - } - } - } - - /// Create a future that acquires a semaphore permit, then executes a graph node. - /// - /// On failure, `execute_node` cancels the `fast_fail_token` — the caller - /// detects this after the future completes. On semaphore closure or prior - /// cancellation, the node is skipped. - fn spawn_node<'a>( - &'a self, - graph: &'a ExecutionGraph, - node_ix: ExecutionNodeIndex, - semaphore: &Arc, - ) -> LocalBoxFuture<'a, ExecutionNodeIndex> { - let sem = semaphore.clone(); - async move { - if let Ok(_permit) = sem.acquire_owned().await - && !self.cancelled() - { - self.execute_node(graph, node_ix).await; - } - node_ix - } - .boxed_local() - } - - /// Execute a single node's items sequentially. - /// - /// A node may have multiple items (from `&&`-split commands). Items are executed - /// in order; if any item fails, `execute_leaf` cancels the `fast_fail_token` - /// and remaining items are skipped (preserving `&&` semantics). - async fn execute_node(&self, graph: &ExecutionGraph, node_ix: ExecutionNodeIndex) { - let task_execution = &graph.graph[node_ix]; - - for item in &task_execution.items { - if self.cancelled() { - return; - } - match &item.kind { - ExecutionItemKind::Leaf(leaf_kind) => { - self.execute_leaf(&item.execution_item_display, leaf_kind).boxed_local().await; - } - ExecutionItemKind::Expanded(nested_graph) => { - self.execute_expanded_graph(nested_graph).boxed_local().await; - } - } - } - } - - /// Execute a single leaf item (in-process command or spawned process). - /// - /// Creates a [`LeafExecutionReporter`](crate::session::reporter::LeafExecutionReporter) - /// from the graph reporter and delegates to the appropriate execution - /// method. On failure (non-zero exit or infrastructure error), cancels the - /// `fast_fail_token`. - #[tracing::instrument(level = "debug", skip_all)] - async fn execute_leaf(&self, display: &ExecutionItemDisplay, leaf_kind: &LeafExecutionKind) { - // Borrow the reporter briefly to create the leaf reporter, then drop - // the RefCell guard before any `.await` point. - let mut leaf_reporter = self.reporter.borrow_mut().new_leaf_execution(display, leaf_kind); - - let failed = match leaf_kind { - LeafExecutionKind::InProcess(in_process_execution) => { - // In-process (built-in) commands: caching is disabled, execute synchronously - let mut stdio_config = leaf_reporter - .start(CacheStatus::Disabled(CacheDisabledReason::InProcessExecution)); - - let execution_output = in_process_execution.execute(); - // Write output to the stdout writer from StdioConfig - let _ = stdio_config.writers.stdout_writer.write_all(&execution_output.stdout); - let _ = stdio_config.writers.stdout_writer.flush(); - - leaf_reporter.finish( - None, - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::CacheDisabled), - None, - ); - false - } - LeafExecutionKind::Spawn(spawn_execution) => { - let outcome = execute_spawn( - leaf_reporter, - spawn_execution, - self.cache, - self.workspace_root, - self.cache_dir, - self.program_name, - self.fast_fail_token.clone(), - self.interrupt_token.clone(), - ) - .await; - match outcome { - SpawnOutcome::CacheHit => false, - SpawnOutcome::Spawned(status) => !status.success(), - SpawnOutcome::Failed => true, - } - } - }; - if failed { - self.fast_fail_token.cancel(); - } - } -} - -impl Session<'_> { - /// Execute an execution graph, reporting events through the provided reporter builder. - /// - /// Cache is initialized only if any leaf execution needs it. The reporter is built - /// after cache initialization, so cache errors are reported directly to stderr - /// without involving the reporter at all. - /// - /// Returns `Err(ExitStatus)` to indicate the caller should exit with the given status code. - /// Returns `Ok(())` when all tasks succeeded. - #[tracing::instrument(level = "debug", skip_all)] - pub(crate) async fn execute_graph( - &self, - execution_graph: ExecutionGraph, - builder: Box, - interrupt_token: CancellationToken, - ) -> Result<(), ExitStatus> { - // Initialize cache before building the reporter. Cache errors are reported - // directly to stderr and cause an early exit, keeping the reporter flow clean - // (the reporter's `finish()` no longer accepts graph-level error messages). - let cache = match self.cache() { - Ok(cache) => cache, - #[expect(clippy::print_stderr, reason = "cache init errors bypass the reporter")] - Err(err) => { - eprintln!("Failed to initialize cache: {err}"); - return Err(ExitStatus::FAILURE); - } - }; - - let reporter = RefCell::new(builder.build()); - - let execution_context = ExecutionContext { - reporter: &reporter, - cache, - workspace_root: &self.workspace_path, - cache_dir: &self.cache_path, - program_name: self.program_name.as_str(), - fast_fail_token: CancellationToken::new(), - interrupt_token, - }; - - // Execute the graph with fast-fail: if any task fails, remaining tasks - // are skipped. Leaf-level errors are reported through the reporter. - execution_context.execute_expanded_graph(&execution_graph).await; - - // Leaf-level errors and non-zero exit statuses are tracked internally - // by the reporter. - reporter.into_inner().finish() - } -} diff --git a/crates/vite_task/src/session/execute/spawn.rs b/crates/vite_task/src/session/execute/spawn.rs deleted file mode 100644 index 2e1dc049b..000000000 --- a/crates/vite_task/src/session/execute/spawn.rs +++ /dev/null @@ -1,245 +0,0 @@ -//! Unified spawn abstraction over fspy and plain tokio processes. -//! -//! [`spawn`] does one thing: hand back the child's stdio pipes plus a -//! cancellation-aware `wait` future. Draining the pipes is [`super::pipe`]'s -//! job; normalizing fspy path accesses is [`super::tracked_accesses`]'s (only -//! compiled when `cfg(fspy)` is on). - -use std::{ffi::OsStr, io, process::Stdio}; - -#[cfg(fspy)] -use fspy::PathAccessIterable; -use futures_util::{FutureExt, future::LocalBoxFuture}; -use tokio::process::{ChildStderr, ChildStdout}; -use tokio_util::sync::CancellationToken; -use vite_task_plan::SpawnCommand; - -/// How the child's stdin/stdout/stderr are configured. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SpawnStdio { - /// All three fds inherited from the parent. On Unix, [`spawn`] also clears - /// `FD_CLOEXEC` on fds 0-2 (libuv workaround — - /// ). - Inherited, - /// stdin is `/dev/null`; stdout and stderr are piped. Drain the pipes with - /// [`super::pipe::pipe_stdio`]. - Piped, -} - -/// Handle to a spawned child. -/// -/// `stdout` and `stderr` are `Some` iff [`SpawnStdio::Piped`] was requested. -/// `wait` resolves when the child exits and handles cancellation internally: -/// when the token fires, the child (and on Windows its descendants via the Job -/// Object) is killed before the future resolves. -pub struct ChildHandle { - pub stdout: Option, - pub stderr: Option, - pub wait: LocalBoxFuture<'static, io::Result>, -} - -/// Result of waiting for a child to exit. -pub struct ChildOutcome { - pub exit_status: std::process::ExitStatus, - /// Raw fspy accesses. `Some` iff `fspy` was `true` at spawn time. - #[cfg(fspy)] - pub path_accesses: Option, -} - -/// Spawn a command with the requested fspy and stdio configuration. -/// -/// `extra_envs` are applied **after** `cmd.spawn_envs`, so runtime-injected -/// entries (e.g. the runner's IPC name + napi addon path) override any -/// same-named key from the plan. -/// -/// Cancellation is unified: whether fspy is enabled or not, the returned `wait` -/// future observes `cancellation_token` and kills the child before resolving. -/// -/// On builds without `cfg(fspy)`, the `fspy` argument is ignored and the tokio -/// path is always taken. -#[tracing::instrument(level = "debug", skip_all)] -pub async fn spawn( - cmd: &SpawnCommand, - fspy: bool, - stdio: SpawnStdio, - cancellation_token: CancellationToken, - extra_envs: E, -) -> anyhow::Result -where - E: IntoIterator, - K: AsRef, - V: AsRef, -{ - #[cfg(fspy)] - if fspy { - return spawn_fspy(cmd, stdio, cancellation_token, extra_envs).await; - } - #[cfg(not(fspy))] - let _ = fspy; - - let mut tokio_cmd = tokio::process::Command::new(cmd.program_path.as_path()); - tokio_cmd.args(cmd.args.iter().map(vite_str::Str::as_str)); - tokio_cmd.env_clear(); - tokio_cmd.envs(cmd.spawn_envs.iter()); - tokio_cmd.envs(extra_envs); - tokio_cmd.current_dir(&*cmd.cwd); - apply_stdio(&mut tokio_cmd, stdio); - spawn_tokio(tokio_cmd, cancellation_token) -} - -#[cfg(fspy)] -async fn spawn_fspy( - cmd: &SpawnCommand, - stdio: SpawnStdio, - cancellation_token: CancellationToken, - extra_envs: E, -) -> anyhow::Result -where - E: IntoIterator, - K: AsRef, - V: AsRef, -{ - let mut fspy_cmd = fspy::Command::new(cmd.program_path.as_path()); - fspy_cmd.args(cmd.args.iter().map(vite_str::Str::as_str)); - fspy_cmd.envs(cmd.spawn_envs.iter()); - fspy_cmd.envs(extra_envs); - fspy_cmd.current_dir(&*cmd.cwd); - - match stdio { - SpawnStdio::Inherited => { - fspy_cmd.stdin(Stdio::inherit()).stdout(Stdio::inherit()).stderr(Stdio::inherit()); - // libuv (used by Node.js) marks stdin/stdout/stderr as close-on-exec; - // without this fix the child reopens fds 0-2 as /dev/null after exec. - // See: https://github.com/libuv/libuv/issues/2062 - // SAFETY: the pre_exec closure only performs fcntl operations on - // stdio fds, which is safe in a post-fork context. - #[cfg(unix)] - unsafe { - fspy_cmd.pre_exec(clear_stdio_cloexec); - } - } - SpawnStdio::Piped => { - fspy_cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped()); - } - } - - let mut tracked = fspy_cmd.spawn(cancellation_token).await?; - - // On Windows, assign the child to a Job Object so that killing the child - // also kills all descendant processes (e.g., node.exe via a .cmd shim). - #[cfg(windows)] - let job = { - use std::os::windows::io::AsRawHandle; - super::win_job::assign_to_kill_on_close_job(tracked.process_handle.as_raw_handle())? - }; - - let stdout = tracked.stdout.take(); - let stderr = tracked.stderr.take(); - let wait_handle = tracked.wait_handle; - - let wait = async move { - let termination = wait_handle.await?; - // Drop order: `job` drops here, KILL_ON_JOB_CLOSE kills any descendants - // still alive. fspy's wait handle already watched the cancellation - // token and killed the direct child. - #[cfg(windows)] - drop(job); - Ok(ChildOutcome { - exit_status: termination.status, - path_accesses: Some(termination.path_accesses), - }) - } - .boxed_local(); - - Ok(ChildHandle { stdout, stderr, wait }) -} - -fn spawn_tokio( - mut cmd: tokio::process::Command, - cancellation_token: CancellationToken, -) -> anyhow::Result { - let mut child = cmd.spawn()?; - - #[cfg(windows)] - let job = { - use std::os::windows::io::{AsRawHandle, BorrowedHandle}; - // Duplicate the process handle so the job outlives tokio's handle. - // SAFETY: The child was just spawned, so its raw handle is valid. - let borrowed = unsafe { BorrowedHandle::borrow_raw(child.raw_handle().unwrap()) }; - let owned = borrowed.try_clone_to_owned()?; - super::win_job::assign_to_kill_on_close_job(owned.as_raw_handle())? - }; - - let stdout = child.stdout.take(); - let stderr = child.stderr.take(); - - let wait = async move { - let exit_status = tokio::select! { - status = child.wait() => status?, - () = cancellation_token.cancelled() => { - child.start_kill()?; - // Eagerly kill descendants; KILL_ON_JOB_CLOSE on drop is a backstop. - #[cfg(windows)] - job.terminate(); - child.wait().await? - } - }; - // `job` drops here on Windows, terminating any stragglers. - #[cfg(windows)] - drop(job); - Ok(ChildOutcome { - exit_status, - #[cfg(fspy)] - path_accesses: None, - }) - } - .boxed_local(); - - Ok(ChildHandle { stdout, stderr, wait }) -} - -fn apply_stdio(cmd: &mut tokio::process::Command, stdio: SpawnStdio) { - match stdio { - SpawnStdio::Inherited => { - cmd.stdin(Stdio::inherit()).stdout(Stdio::inherit()).stderr(Stdio::inherit()); - // libuv (used by Node.js) marks stdin/stdout/stderr as close-on-exec; - // without this fix the child reopens fds 0-2 as /dev/null after exec. - // See: https://github.com/libuv/libuv/issues/2062 - // SAFETY: the pre_exec closure only performs fcntl operations on - // stdio fds, which is safe in a post-fork context. - #[cfg(unix)] - unsafe { - cmd.pre_exec(clear_stdio_cloexec); - } - } - SpawnStdio::Piped => { - cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped()); - } - } -} - -#[cfg(unix)] -#[expect( - clippy::unnecessary_wraps, - reason = "signature matches Command::pre_exec's FnMut() -> io::Result<()> contract" -)] -fn clear_stdio_cloexec() -> io::Result<()> { - use std::os::fd::BorrowedFd; - - use nix::{ - fcntl::{FcntlArg, FdFlag, fcntl}, - libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}, - }; - for fd in [STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO] { - // SAFETY: fds 0-2 are always valid in a post-fork context - let borrowed = unsafe { BorrowedFd::borrow_raw(fd) }; - if let Ok(flags) = fcntl(borrowed, FcntlArg::F_GETFD) { - let mut fd_flags = FdFlag::from_bits_retain(flags); - if fd_flags.contains(FdFlag::FD_CLOEXEC) { - fd_flags.remove(FdFlag::FD_CLOEXEC); - let _ = fcntl(borrowed, FcntlArg::F_SETFD(fd_flags)); - } - } - } - Ok(()) -} diff --git a/crates/vite_task/src/session/execute/tracked_accesses.rs b/crates/vite_task/src/session/execute/tracked_accesses.rs deleted file mode 100644 index 6e3596512..000000000 --- a/crates/vite_task/src/session/execute/tracked_accesses.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Normalize raw fspy path accesses into workspace-relative, filtered form. -//! -//! User-configured negative globs are NOT applied here. They are applied later, -//! separately for reads (input config) and writes (output config), since those -//! two configs are independent. -#![cfg(fspy)] - -use std::collections::hash_map::Entry; - -use fspy::{AccessMode, PathAccessIterable}; -use rustc_hash::FxHashSet; -use vite_path::{AbsolutePath, RelativePathBuf}; - -use super::fingerprint::PathRead; -use crate::collections::HashMap; - -/// Tracked file accesses from fspy, normalized to workspace-relative paths. -#[derive(Default, Debug)] -pub struct TrackedPathAccesses { - /// Tracked path reads - pub path_reads: HashMap, - - /// Tracked path writes - pub path_writes: FxHashSet, -} - -impl TrackedPathAccesses { - /// Build from fspy's raw iterable by stripping the workspace prefix and - /// normalizing `..` components. `.git/*` paths are skipped. User-configured - /// negatives are applied by the caller (see module docs). - pub fn from_raw(raw: &PathAccessIterable, workspace_root: &AbsolutePath) -> Self { - let mut accesses = Self::default(); - for access in raw.iter() { - // Strip workspace root and clean `..` components in one pass. - // fspy may report paths like `packages/sub-pkg/../shared/dist/output.js`. - let relative_path = access.path.strip_path_prefix(workspace_root, |strip_result| { - let Ok(stripped_path) = strip_result else { - return None; - }; - normalize_tracked_workspace_path(stripped_path) - }); - - let Some(relative_path) = relative_path else { - continue; - }; - - if access.mode.contains(AccessMode::READ) { - accesses - .path_reads - .entry(relative_path.clone()) - .or_insert(PathRead { read_dir_entries: false }); - } - if access.mode.contains(AccessMode::WRITE) { - accesses.path_writes.insert(relative_path.clone()); - } - if access.mode.contains(AccessMode::READ_DIR) { - match accesses.path_reads.entry(relative_path) { - Entry::Occupied(mut occupied) => { - occupied.get_mut().read_dir_entries = true; - } - Entry::Vacant(vacant) => { - vacant.insert(PathRead { read_dir_entries: true }); - } - } - } - } - accesses - } -} - -#[expect( - clippy::disallowed_types, - reason = "fspy strip_path_prefix exposes std::path::Path; convert to RelativePathBuf immediately" -)] -fn normalize_tracked_workspace_path(stripped_path: &std::path::Path) -> Option { - // On Windows, paths are possible to be still absolute after stripping the workspace root. - // For example: c:\workspace\subdir\c:\workspace\subdir - // Just ignore those accesses. - let relative = RelativePathBuf::new(stripped_path).ok()?; - - // Clean `..` components — fspy may report paths like - // `packages/sub-pkg/../shared/dist/output.js`. Normalize them for - // consistent behavior across platforms and clean user-facing messages. - let relative = relative.clean().ok()?; - - // Skip .git directory accesses (workaround for tools like oxlint) - if relative.as_path().strip_prefix(".git").is_ok() { - return None; - } - - Some(relative) -} - -#[cfg(test)] -mod tests { - #[cfg(windows)] - use super::*; - - #[cfg(windows)] - #[test] - fn malformed_windows_drive_path_after_workspace_strip_is_ignored() { - #[expect( - clippy::disallowed_types, - reason = "normalize_tracked_workspace_path requires std::path::Path for fspy strip_path_prefix output" - )] - let relative_path = normalize_tracked_workspace_path(std::path::Path::new(r"foo\C:\bar")); - assert!(relative_path.is_none()); - } -} diff --git a/crates/vite_task/src/session/execute/win_job.rs b/crates/vite_task/src/session/execute/win_job.rs deleted file mode 100644 index 659203b1f..000000000 --- a/crates/vite_task/src/session/execute/win_job.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Win32 Job Object utilities for process tree management. -//! -//! On Windows, `TerminateProcess` only kills the direct child process, not its -//! descendants. This module creates a Job Object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, -//! which automatically terminates all processes in the job when the handle is dropped. - -use std::{io, os::windows::io::RawHandle}; - -use winapi::{ - shared::minwindef::FALSE, - um::{ - handleapi::CloseHandle, - jobapi2::{ - AssignProcessToJobObject, CreateJobObjectW, SetInformationJobObject, TerminateJobObject, - }, - winnt::{HANDLE, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOBOBJECT_EXTENDED_LIMIT_INFORMATION}, - }, -}; - -/// RAII wrapper around a Win32 Job Object `HANDLE` that closes it on drop. -pub(super) struct OwnedJobHandle(HANDLE); - -impl OwnedJobHandle { - /// Immediately terminate all processes in the job. - /// - /// This is needed when pipes to a grandchild process must be closed before - /// the job handle is dropped (e.g., to unblock pipe reads in `spawn`). - pub(super) fn terminate(&self) { - // SAFETY: self.0 is a valid job handle from CreateJobObjectW. - unsafe { TerminateJobObject(self.0, 1) }; - } -} - -impl Drop for OwnedJobHandle { - fn drop(&mut self) { - // SAFETY: self.0 is a valid handle obtained from CreateJobObjectW. - unsafe { CloseHandle(self.0) }; - } -} - -/// Create a Job Object with `KILL_ON_JOB_CLOSE` and assign a process to it. -/// -/// Returns the job handle wrapped in an RAII guard. When dropped, all processes -/// in the job (the child and its descendants) are terminated. -pub(super) fn assign_to_kill_on_close_job(process_handle: RawHandle) -> io::Result { - // SAFETY: Creating an anonymous job object with no security attributes. - let job = unsafe { CreateJobObjectW(std::ptr::null_mut(), std::ptr::null()) }; - if job.is_null() { - return Err(io::Error::last_os_error()); - } - let job = OwnedJobHandle(job); - - // Configure the job to kill all processes when the handle is closed. - // SAFETY: JOBOBJECT_EXTENDED_LIMIT_INFORMATION is a plain C struct (no pointers - // in the zeroed fields). Zeroing then setting LimitFlags is the standard pattern. - let mut info = unsafe { - let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed(); - info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - info - }; - - // SAFETY: info is a valid JOBOBJECT_EXTENDED_LIMIT_INFORMATION, job.0 is a valid handle. - let ok = unsafe { - SetInformationJobObject( - job.0, - // JobObjectExtendedLimitInformation = 9 - 9, - std::ptr::from_mut(&mut info).cast(), - std::mem::size_of::().try_into().unwrap(), - ) - }; - if ok == FALSE { - return Err(io::Error::last_os_error()); - } - - // SAFETY: Both handles are valid — job from CreateJobObjectW, process handle - // from the caller. - let ok = unsafe { AssignProcessToJobObject(job.0, process_handle as HANDLE) }; - if ok == FALSE { - return Err(io::Error::last_os_error()); - } - - Ok(job) -} diff --git a/crates/vite_task/src/session/mod.rs b/crates/vite_task/src/session/mod.rs deleted file mode 100644 index b7768f68f..000000000 --- a/crates/vite_task/src/session/mod.rs +++ /dev/null @@ -1,861 +0,0 @@ -mod cache; -mod event; -mod execute; -pub(crate) mod reporter; - -// Re-export types that are part of the public API -use std::{ffi::OsStr, fmt::Debug, io::IsTerminal, sync::Arc}; - -use cache::ExecutionCache; -pub use cache::{CacheMiss, FingerprintMismatch}; -use clap::Parser as _; -use once_cell::sync::OnceCell; -pub use reporter::ExitStatus; -use reporter::{ - ColorSupport, GroupedReporterBuilder, InterleavedReporterBuilder, LabeledReporterBuilder, - SummaryReporterBuilder, - summary::{LastRunSummary, ReadSummaryError, format_full_summary}, -}; -use rustc_hash::FxHashMap; -use vite_path::{AbsolutePath, AbsolutePathBuf}; -use vite_select::SelectItem; -use vite_str::Str; -use vite_task_graph::{ - IndexedTaskGraph, TaskGraph, TaskGraphLoadError, config::user::UserCacheConfig, - loader::UserConfigLoader, query::TaskQuery, -}; -use vite_task_plan::{ - ExecutionGraph, TaskGraphLoader, - plan_request::{ - PlanOptions, PlanRequest, QueryPlanRequest, ScriptCommand, SyntheticPlanRequest, - }, - prepend_path_env, -}; -use vite_workspace::{WorkspaceRoot, find_workspace_root, package_graph::PackageQuery}; - -use crate::cli::{CacheSubcommand, Command, ResolvedCommand, ResolvedRunCommand, RunCommand}; - -/// Error type for [`Session::main`]. -/// -/// `EarlyExit` represents a non-error exit (e.g. printing a task list) and -/// the caller should exit with the contained status without printing an error. -/// It exists only for easier `?` control flow. -enum SessionError { - Anyhow(anyhow::Error), - EarlyExit(ExitStatus), -} - -impl From for SessionError -where - anyhow::Error: From, -{ - fn from(err: T) -> Self { - Self::Anyhow(anyhow::Error::from(err)) - } -} - -#[derive(Debug)] -enum LazyTaskGraph<'a> { - Uninitialized { workspace_root: WorkspaceRoot, config_loader: &'a dyn UserConfigLoader }, - Initialized(IndexedTaskGraph), -} - -#[async_trait::async_trait(?Send)] -impl TaskGraphLoader for LazyTaskGraph<'_> { - async fn load_task_graph( - &mut self, - ) -> Result<&vite_task_graph::IndexedTaskGraph, TaskGraphLoadError> { - let _span = tracing::debug_span!("load_task_graph").entered(); - Ok(match self { - Self::Uninitialized { workspace_root, config_loader } => { - let graph = IndexedTaskGraph::load(workspace_root, *config_loader).await?; - *self = Self::Initialized(graph); - match self { - Self::Initialized(graph) => &*graph, - Self::Uninitialized { .. } => unreachable!(), - } - } - Self::Initialized(graph) => &*graph, - }) - } -} - -pub struct SessionConfig<'a> { - pub command_handler: &'a mut (dyn CommandHandler + 'a), - pub user_config_loader: &'a mut (dyn UserConfigLoader + 'a), - pub program_name: Str, -} - -/// The result of a [`CommandHandler::handle_command`] call. -#[derive(Debug)] -pub enum HandledCommand { - /// The command was synthesized into a task (e.g., `vp lint` → `oxlint`). - Synthesized(SyntheticPlanRequest), - /// The command is a vite task CLI command (e.g., `vp run build`). - ViteTaskCommand(Command), - /// The command should be executed verbatim as an external process. - Verbatim, -} - -/// Handles commands found in task scripts to determine how they should be executed. -/// -/// The implementation should return: -/// - [`HandledCommand::Synthesized`] to replace the command with a synthetic task. -/// - [`HandledCommand::ViteTaskCommand`] when the command is a vite task CLI invocation. -/// - [`HandledCommand::Verbatim`] to execute the command as-is as an external process. -#[async_trait::async_trait(?Send)] -pub trait CommandHandler: Debug { - /// Called for every command in task scripts to determine how it should be handled. - async fn handle_command( - &mut self, - command: &mut ScriptCommand, - ) -> anyhow::Result; -} - -#[derive(derive_more::Debug)] -struct PlanRequestParser<'a> { - command_handler: &'a mut (dyn CommandHandler + 'a), -} - -#[async_trait::async_trait(?Send)] -impl vite_task_plan::PlanRequestParser for PlanRequestParser<'_> { - async fn get_plan_request( - &mut self, - command: &mut ScriptCommand, - ) -> anyhow::Result> { - match self.command_handler.handle_command(command).await? { - HandledCommand::Synthesized(synthetic) => Ok(Some(PlanRequest::Synthetic(synthetic))), - HandledCommand::ViteTaskCommand(cli_command) => match cli_command.into_resolved() { - ResolvedCommand::Cache { .. } | ResolvedCommand::RunLastDetails => { - Ok(Some(PlanRequest::Synthetic( - command.to_synthetic_plan_request(UserCacheConfig::disabled()), - ))) - } - ResolvedCommand::Run(run_command) => { - match run_command.into_query_plan_request(&command.cwd) { - Ok((query_plan_request, _)) => { - Ok(Some(PlanRequest::Query(query_plan_request))) - } - Err(crate::cli::CLITaskQueryError::MissingTaskSpecifier) => { - Ok(Some(PlanRequest::Synthetic( - command.to_synthetic_plan_request(UserCacheConfig::disabled()), - ))) - } - Err(err) => Err(err.into()), - } - } - }, - HandledCommand::Verbatim => Ok(None), - } - } -} - -/// Represents a vite task session for planning and executing tasks. A process typically has one session. -/// -/// A session manages task graph loading internally and provides non-consuming methods to plan and/or execute tasks (allows multiple plans/executions per session). -pub struct Session<'a> { - workspace_path: Arc, - /// A session doesn't necessarily load the task graph immediately. - /// The task graph is loaded on-demand and cached for future use. - lazy_task_graph: LazyTaskGraph<'a>, - - envs: Arc, Arc>>, - cwd: Arc, - - plan_request_parser: PlanRequestParser<'a>, - - program_name: Str, - - /// Cache is lazily initialized to avoid `SQLite` race conditions when multiple - /// processes (e.g., parallel `vt lib` commands) start simultaneously. - cache: OnceCell, - /// Per-schema-version cache directory (e.g. `node_modules/.vite/task-cache/v13`) - /// that holds the database and output archives for this build. - cache_path: AbsolutePathBuf, - /// Root task-cache directory (parent of all `vN` version directories). - /// Used by `cache clean` to remove every version's cache (and any leftover - /// from a pre-versioned layout) in one shot. - cache_root: AbsolutePathBuf, -} - -fn get_cache_path_of_workspace(workspace_root: &AbsolutePath) -> AbsolutePathBuf { - std::env::var("VITE_CACHE_PATH").map_or_else( - |_| workspace_root.join("node_modules/.vite/task-cache"), - |env_cache_path| { - AbsolutePathBuf::new(env_cache_path.into()).expect("Cache path should be absolute") - }, - ) -} - -impl<'a> Session<'a> { - /// Initialize a session with real environment variables and cwd - /// - /// # Errors - /// - /// Returns an error if the current directory cannot be determined or - /// if workspace initialization fails. - #[tracing::instrument(level = "debug", skip_all)] - pub fn init(config: SessionConfig<'a>) -> anyhow::Result { - #[expect( - clippy::disallowed_methods, - reason = "Session::init is the only place that bootstraps the session env snapshot" - )] - let envs = std::env::vars_os() - .map(|(k, v)| (Arc::::from(k.as_os_str()), Arc::::from(v.as_os_str()))) - .collect(); - Self::init_with(envs, vite_path::current_dir()?.into(), config) - } - - /// Ensures the task graph is loaded, loading it if necessary. - /// - /// # Errors - /// - /// Returns an error if the task graph cannot be loaded from the workspace configuration. - #[tracing::instrument(level = "debug", skip_all)] - pub async fn ensure_task_graph_loaded( - &mut self, - ) -> Result<&IndexedTaskGraph, TaskGraphLoadError> { - self.lazy_task_graph.load_task_graph().await - } - - /// Initialize a session with custom cwd, environment variables. Useful for testing. - /// - /// # Errors - /// - /// Returns an error if workspace root cannot be found or PATH env cannot be prepended. - #[tracing::instrument(level = "debug", skip_all)] - pub fn init_with( - mut envs: FxHashMap, Arc>, - cwd: Arc, - config: SessionConfig<'a>, - ) -> anyhow::Result { - let (workspace_root, _) = find_workspace_root(&cwd)?; - let cache_root = get_cache_path_of_workspace(&workspace_root.path); - // Nest the cache in a per-schema-version subdirectory so builds that pin - // different schema versions don't share (and corrupt) one database. - let cache_path = cache_root.join(cache::cache_schema_dir_name().as_str()); - - // Prepend workspace's node_modules/.bin to PATH - let workspace_node_modules_bin = workspace_root.path.join("node_modules").join(".bin"); - prepend_path_env(&mut envs, &workspace_node_modules_bin)?; - - // Cache is lazily initialized on first access to avoid SQLite race conditions - Ok(Self { - workspace_path: Arc::clone(&workspace_root.path), - lazy_task_graph: LazyTaskGraph::Uninitialized { - workspace_root, - config_loader: config.user_config_loader, - }, - envs: Arc::new(envs), - cwd, - plan_request_parser: PlanRequestParser { command_handler: config.command_handler }, - program_name: config.program_name, - cache: OnceCell::new(), - cache_path, - cache_root, - }) - } - - /// Primary entry point for CLI usage. Plans and executes the given command. - /// - /// Any error encountered during planning or execution is printed to stderr - /// with a bold red `error:` prefix, with each level of the error chain on - /// its own `* `-prefixed line. Returns the exit status — callers exit the - /// process with it. - #[tracing::instrument(level = "debug", skip_all)] - pub async fn main(mut self, command: Command) -> ExitStatus { - match self.main_inner(command).await { - Ok(()) => ExitStatus::SUCCESS, - Err(SessionError::EarlyExit(status)) => status, - Err(SessionError::Anyhow(err)) => { - print_error(&err); - ExitStatus::FAILURE - } - } - } - - /// # Panics - /// - /// Panics if parsing a hardcoded bare `RunCommand` fails (should never happen). - async fn main_inner(&mut self, command: Command) -> Result<(), SessionError> { - match command.into_resolved() { - ResolvedCommand::Cache { ref subcmd } => self.handle_cache_command(subcmd), - ResolvedCommand::RunLastDetails => self.show_last_run_details(), - ResolvedCommand::Run(run_command) => { - let is_interactive = - std::io::stdin().is_terminal() && std::io::stdout().is_terminal(); - - let graph = if let Some(ref task_specifier) = run_command.task_specifier { - // Task specifier provided — plan it. - let cwd = Arc::clone(&self.cwd); - let (plan_result, is_cwd_only) = - self.plan_from_cli_run_resolved(cwd, run_command.clone()).await?; - - if plan_result.graph.graph.node_count() == 0 { - // Three empty-graph outcomes, in order of precedence: - // 1. `--filter` selected zero packages — the planner has - // already warned per filter; exit 0 silently. This is the - // pnpm-compatible default; `--fail-if-no-match` opts in - // to strict behaviour and is raised inside the planner. - // 2. Bare `vp run` (cwd-only, no execution flags) — fall - // through to the interactive task selector. - // 3. Otherwise (e.g. typoed task, `-r` with no matching - // package) — surface NoTasksMatched. - if plan_result.no_packages_matched { - return Ok(()); - } - let has_execution_flags = run_command.flags.concurrency_limit.is_some() - || run_command.flags.parallel; - if is_cwd_only && !has_execution_flags { - let qpr = self.handle_no_task(is_interactive, &run_command).await?; - self.plan_from_query(qpr).await? - } else { - return Err(vite_task_plan::Error::NoTasksMatched( - task_specifier.clone(), - ) - .into()); - } - } else { - plan_result.graph - } - } else { - // No task specifier (e.g. `vp run` or `vp run --verbose`). - // Only bare `vp run` enters the selector; with extra flags, error. - let bare = RunCommand::try_parse_from::<_, &str>([]) - .expect("parsing hardcoded bare command should never fail") - .into_resolved(); - - // Normalize the run_command for comparison by ignoring cache flags, which don't affect task selection. - let mut normalized_run_command = run_command.clone(); - normalized_run_command.flags.cache = false; - - if normalized_run_command != bare { - return Err(vite_task_plan::Error::MissingTaskSpecifier.into()); - } - let qpr = self.handle_no_task(is_interactive, &run_command).await?; - self.plan_from_query(qpr).await? - }; - - let workspace_path = self.workspace_path(); - let writer: Box = Box::new(std::io::stdout()); - - // Detect color support once at the point where reporters are - // constructed. The reporters and their pipe writers then strip - // ANSI escapes from cached/replayed output if the terminal - // can't render them. Detect per-stream so a redirected stdout - // doesn't trigger stripping of an interactive stderr. - let color_support = ColorSupport { - stdout: stdout_supports_color(), - stderr: stderr_supports_color(), - }; - - let inner: Box = match run_command - .flags - .log - { - crate::cli::LogMode::Interleaved => Box::new(InterleavedReporterBuilder::new( - Arc::clone(&workspace_path), - writer, - color_support, - )), - crate::cli::LogMode::Labeled => Box::new(LabeledReporterBuilder::new( - Arc::clone(&workspace_path), - writer, - color_support, - )), - crate::cli::LogMode::Grouped => Box::new(GroupedReporterBuilder::new( - Arc::clone(&workspace_path), - writer, - color_support, - )), - }; - - let builder = Box::new(SummaryReporterBuilder::new( - inner, - workspace_path, - Box::new(std::io::stdout()), - run_command.flags.verbose, - Some(self.make_summary_writer()), - self.program_name.clone(), - color_support, - )); - // Don't let SIGINT/CTRL_C kill the runner. Child tasks receive - // the signal directly from the terminal driver and handle it - // themselves. Cancelling the interrupt token prevents scheduling - // new tasks and caching results of in-flight tasks. - // - // On Windows, an ancestor process (e.g. cargo) may have been - // created with CREATE_NEW_PROCESS_GROUP, which sets a per-process - // flag that silently drops CTRL_C_EVENT before it reaches - // registered handlers. Clear it so our handler fires. - // - // SAFETY: Passing (None, FALSE) clears the inherited - // CTRL_C ignore flag. - #[cfg(windows)] - unsafe { - unsafe extern "system" { - fn SetConsoleCtrlHandler( - handler: Option i32>, - add: i32, - ) -> i32; - } - SetConsoleCtrlHandler(None, 0); - } - let interrupt_token = tokio_util::sync::CancellationToken::new(); - let ct = interrupt_token.clone(); - ctrlc::set_handler(move || { - ct.cancel(); - })?; - - self.execute_graph(graph, builder, interrupt_token) - .await - .map_err(SessionError::EarlyExit) - } - } - } - - fn handle_cache_command(&self, subcmd: &CacheSubcommand) -> Result<(), SessionError> { - match subcmd { - CacheSubcommand::Clean => { - // Remove the whole task-cache directory (every version), not just - // the current build's `vN` subdirectory. - if self.cache_root.as_path().exists() { - std::fs::remove_dir_all(&self.cache_root)?; - } - } - } - Ok(()) - } - - /// Show the task selector or list, and return a plan request for the selected task. - /// - /// In interactive mode, shows a fuzzy-searchable selection list. On selection, - /// returns `Ok(QueryPlanRequest)` using the selected entry's filesystem path - /// (not its display name) for package matching. - /// - /// In non-interactive mode, prints the task list (or "did you mean" suggestions) - /// and returns `Err(SessionError::EarlyExit(_))` — no further execution needed. - #[expect( - clippy::too_many_lines, - reason = "builds interactive/non-interactive select items and handles selection" - )] - async fn handle_no_task( - &mut self, - is_interactive: bool, - run_command: &ResolvedRunCommand, - ) -> Result { - let not_found_name = run_command.task_specifier.as_deref(); - let cwd = Arc::clone(&self.cwd); - let task_graph = self.ensure_task_graph_loaded().await?; - let current_package_path = task_graph.get_package_path_from_cwd(&cwd).cloned(); - let mut entries = task_graph.list_tasks(); - entries.sort_unstable_by(|a, b| { - a.task_display - .package_name - .cmp(&b.task_display.package_name) - .then_with(|| a.task_display.task_name.cmp(&b.task_display.task_name)) - }); - - let workspace_path = self.workspace_path(); - - // Build items: current package tasks use unqualified names (no '#'), - // other packages use qualified "package#task" names. - // Interactive mode uses tree view (grouped by package); non-interactive is flat. - let select_items: Vec = entries - .iter() - .map(|entry| { - let is_current = - current_package_path.as_ref() == Some(&entry.task_display.package_path); - let label = if is_current { - entry.task_display.task_name.clone() - } else { - vite_str::format!("{}", entry.task_display) - }; - - let group = if is_current { - None - } else { - let rel_path = entry - .task_display - .package_path - .strip_prefix(&*workspace_path) - .ok() - .flatten() - .map(|p| Str::from(p.as_str())) - .unwrap_or_default(); - let pkg_name = &entry.task_display.package_name; - let display_path = - if rel_path.is_empty() { Str::from("workspace root") } else { rel_path }; - Some(if pkg_name.is_empty() { - display_path - } else { - vite_str::format!("{pkg_name} ({display_path})") - }) - }; - let display_name = if is_interactive { - entry.task_display.task_name.clone() - } else { - label.clone() - }; - SelectItem { label, display_name, description: entry.command.clone(), group } - }) - .collect(); - - // Build header: interactive says "not found.", non-interactive adds - // "Did you mean:" suffix only when there are fuzzy matches to show. - let header = not_found_name.map(|name| { - if is_interactive { - vite_str::format!("Task \"{name}\" not found.") - } else { - let labels: Vec<&str> = - select_items.iter().map(|item| item.label.as_str()).collect(); - let has_suggestions = !vite_select::fuzzy_match(name, &labels).is_empty(); - if has_suggestions { - vite_str::format!("Task \"{name}\" not found. Did you mean:") - } else { - vite_str::format!("Task \"{name}\" not found.") - } - } - }); - - // Build mode-dependent params and call select_list once - let mut selected_index = if is_interactive { Some(0) } else { None }; - let mut stdout = std::io::stdout(); - let mode = - selected_index.as_mut().map_or(vite_select::Mode::NonInteractive, |selected_index| { - vite_select::Mode::Interactive { selected_index } - }); - - let params = vite_select::SelectParams { - items: &select_items, - query: not_found_name, - header: header.as_deref(), - prompt: "Select a task (\u{2191}/\u{2193}, Enter to run, type to search):", - page_size: 12, - }; - - let select_result = vite_select::select_list(&mut stdout, ¶ms, mode, |state| { - let milestone_name = - vite_str::format!("task-select:{}:{}", state.query, state.selected_index); - pty_terminal_test_client::mark_milestone(&milestone_name); - })?; - - if matches!(select_result, vite_select::SelectResult::Cancelled) { - return Err(SessionError::EarlyExit(ExitStatus(130))); - } - - let Some(selected_index) = selected_index else { - // Non-interactive, the list was printed. - return Err(SessionError::EarlyExit(if not_found_name.is_some() { - // For `vp run typo`, return FAILURE status - ExitStatus::FAILURE - } else { - // For bare `vp run`, return SUCCESS status - ExitStatus::SUCCESS - })); - }; - - // Interactive: print selected task and build a QueryPlanRequest using the - // entry's filesystem path (not its display name) for package matching. - let entry = &entries[selected_index]; - let selected_label = &select_items[selected_index].label; - { - use std::io::Write as _; - - use owo_colors::{OwoColorize as _, Stream}; - writeln!( - stdout, - "{}{}", - "Selected task: ".if_supports_color(Stream::Stdout, |s| s.bold()), - selected_label, - )?; - } - - let package_query = - PackageQuery::containing_package(Arc::clone(&entry.task_display.package_path)); - Ok(QueryPlanRequest { - query: TaskQuery { - package_query, - task_name: entry.task_display.task_name.clone(), - include_explicit_deps: !run_command.flags.ignore_depends_on, - }, - plan_options: PlanOptions { - extra_args: run_command.additional_args.clone().into(), - cache_override: run_command.flags.cache_override(), - concurrency_limit: None, - parallel: false, - // The selector path runs whatever the user picked interactively; - // there is no `--filter` in play, so strict-mode does not apply. - fail_if_no_match: false, - }, - }) - } - - /// Lazily initializes and returns the execution cache. - /// The cache is only created when first accessed to avoid `SQLite` race conditions - /// when multiple processes start simultaneously. - /// - /// # Errors - /// - /// Returns an error if the cache database cannot be loaded or created. - pub fn cache(&self) -> anyhow::Result<&ExecutionCache> { - self.cache.get_or_try_init(|| ExecutionCache::load_from_path(&self.cache_path)) - } - - pub fn workspace_path(&self) -> Arc { - Arc::clone(&self.workspace_path) - } - - /// Path to the `last-summary.json` file inside the cache directory. - fn summary_file_path(&self) -> AbsolutePathBuf { - self.cache_path.join("last-summary.json") - } - - /// Create a callback that persists the summary to `last-summary.json`. - /// - /// The returned closure captures the file path and handles errors internally - /// (logging failures without propagating). - fn make_summary_writer(&self) -> Box { - let path = self.summary_file_path(); - Box::new(move |summary: &LastRunSummary| { - if let Err(err) = summary.write_atomic(&path) { - tracing::warn!("Failed to write summary to {path:?}: {err}"); - } - }) - } - - /// Display the saved summary from the last run (`--last-details`). - #[expect( - clippy::print_stderr, - reason = "--last-details error messages are user-facing diagnostics, not debug output" - )] - fn show_last_run_details(&self) -> Result<(), SessionError> { - let path = self.summary_file_path(); - match LastRunSummary::read_from_path(&path) { - Ok(Some(summary)) => { - // `format_full_summary` decides colour vs plain text per - // styled span via `ColorizeExt` (which consults - // `supports-color`), so the buffer already matches the - // terminal's capability and we write it to stdout directly. - let buf = format_full_summary(&summary); - { - use std::io::Write; - let mut stdout = std::io::stdout().lock(); - stdout.write_all(&buf)?; - stdout.flush()?; - } - Err(SessionError::EarlyExit(ExitStatus(summary.exit_code))) - } - Ok(None) => { - eprintln!("No previous run summary found. Run a task first to generate a summary."); - Err(SessionError::EarlyExit(ExitStatus::FAILURE)) - } - Err(ReadSummaryError::IncompatibleVersion) => { - eprintln!( - "Summary data was saved by a different version of vite-task and cannot be read. \ - Run a task to generate a new summary." - ); - Err(SessionError::EarlyExit(ExitStatus::FAILURE)) - } - Err(ReadSummaryError::Io(err)) => Err(err.into()), - } - } - - pub const fn task_graph(&self) -> Option<&TaskGraph> { - match &self.lazy_task_graph { - LazyTaskGraph::Initialized(graph) => Some(graph.task_graph()), - LazyTaskGraph::Uninitialized { .. } => None, - } - } - - pub const fn envs(&self) -> &Arc, Arc>> { - &self.envs - } - - pub const fn cwd(&self) -> &Arc { - &self.cwd - } - - /// Execute a synthetic command with cache support. - /// - /// This is for executing a single command with cache before/without the entrypoint - /// [`Session::main`]. In vite-plus, this is used for auto-install. - /// - /// Unlike `execute_graph` which uses the full graph reporter - /// pipeline, this method uses a `PlainReporter` — a lightweight reporter with no - /// summary, no stats tracking, and no graph awareness. - /// - /// The exit status is determined from the `execute_spawn` return value, not from - /// the reporter. - /// - /// # Errors - /// - /// Returns an error if planning or execution of the synthetic command fails. - #[tracing::instrument(level = "debug", skip_all)] - pub async fn execute_synthetic( - &self, - synthetic_plan_request: SyntheticPlanRequest, - cache_key: Arc<[Str]>, - silent_if_cache_hit: bool, - ) -> anyhow::Result { - // Plan the synthetic execution — returns a SpawnExecution directly - // (synthetic plans are always a single spawned process) - let spawn_execution = vite_task_plan::plan_synthetic( - &self.workspace_path, - &self.cwd, - synthetic_plan_request, - cache_key, - )?; - - // Initialize cache (needed for cache-aware execution) - let cache = self.cache()?; - - // Create a plain (standalone) reporter — no graph awareness, no summary - let plain_reporter = reporter::PlainReporter::new( - silent_if_cache_hit, - Box::new(std::io::stdout()), - ColorSupport { stdout: stdout_supports_color(), stderr: stderr_supports_color() }, - ); - - // Execute the spawn directly using the free function, bypassing the graph pipeline - let outcome = execute::execute_spawn( - Box::new(plain_reporter), - &spawn_execution, - cache, - &self.workspace_path, - &self.cache_path, - self.program_name.as_str(), - tokio_util::sync::CancellationToken::new(), - tokio_util::sync::CancellationToken::new(), - ) - .await; - match outcome { - // Cache hit — no process was spawned, success - execute::SpawnOutcome::CacheHit => Ok(ExitStatus::SUCCESS), - // Process ran successfully - execute::SpawnOutcome::Spawned(status) if status.success() => Ok(ExitStatus::SUCCESS), - // Process ran but exited with non-zero status - execute::SpawnOutcome::Spawned(status) => { - let code = event::exit_status_to_code(status); - #[expect( - clippy::cast_sign_loss, - reason = "value is clamped to 1..=255, always positive" - )] - Ok(ExitStatus(code.clamp(1, 255) as u8)) - } - // Infrastructure error — already reported through the reporter's finish() - execute::SpawnOutcome::Failed => Ok(ExitStatus::FAILURE), - } - } - - /// Plans execution from a CLI run command. - /// - /// # Errors - /// - /// Returns an error if the plan request cannot be parsed or if planning fails. - #[tracing::instrument(level = "debug", skip_all)] - pub async fn plan_from_cli_run( - &mut self, - cwd: Arc, - command: RunCommand, - ) -> Result { - let (plan_result, _) = - self.plan_from_cli_run_resolved(cwd, command.into_resolved()).await?; - Ok(plan_result.graph) - } - - /// Internal: plans execution from a resolved run command. - #[tracing::instrument(level = "debug", skip_all)] - async fn plan_from_cli_run_resolved( - &mut self, - cwd: Arc, - command: crate::cli::ResolvedRunCommand, - ) -> Result<(vite_task_plan::PlanResult, bool), vite_task_plan::Error> { - let (query_plan_request, is_cwd_only) = match command.into_query_plan_request(&cwd) { - Ok(result) => result, - Err(crate::cli::CLITaskQueryError::MissingTaskSpecifier) => { - return Err(vite_task_plan::Error::MissingTaskSpecifier); - } - Err(error) => { - return Err(vite_task_plan::Error::ParsePlanRequest { - error: error.into(), - program: self.program_name.clone(), - args: Arc::default(), - cwd: Arc::clone(&cwd), - }); - } - }; - let plan_result = vite_task_plan::plan_query( - query_plan_request, - &self.workspace_path, - &cwd, - &self.envs, - &mut self.plan_request_parser, - &mut self.lazy_task_graph, - ) - .await?; - Ok((plan_result, is_cwd_only)) - } - - /// Plan execution from a pre-built [`QueryPlanRequest`]. - /// - /// Used by the interactive task selector, which constructs the request - /// directly (bypassing CLI specifier parsing). - async fn plan_from_query( - &mut self, - request: QueryPlanRequest, - ) -> Result { - let cwd = Arc::clone(&self.cwd); - let plan_result = vite_task_plan::plan_query( - request, - &self.workspace_path, - &cwd, - &self.envs, - &mut self.plan_request_parser, - &mut self.lazy_task_graph, - ) - .await?; - Ok(plan_result.graph) - } -} - -/// Print `error` to stderr formatted as the `vp` CLI does: -/// -/// ```text -/// error: -/// * -/// * -/// ``` -/// -/// The `error:` prefix is bold red when stderr supports ANSI colors. -pub fn print_error(error: &anyhow::Error) { - use std::io::Write as _; - - use owo_colors::{OwoColorize as _, Stream, Style}; - - let prefix = "error:".if_supports_color(Stream::Stderr, |s| s.style(Style::new().red().bold())); - let mut stderr = std::io::stderr().lock(); - let _ = write!(stderr, "{prefix} {error}"); - for source in error.chain().skip(1) { - let _ = write!(stderr, "\n* {source}"); - } - let _ = writeln!(stderr); -} - -/// Whether stdout supports ANSI color output for the current process. Honors -/// `NO_COLOR`/`FORCE_COLOR` and detects TTY capability via the `supports-color` -/// crate. Result is cached for the process lifetime. -fn stdout_supports_color() -> bool { - use std::sync::OnceLock; - static CACHE: OnceLock = OnceLock::new(); - *CACHE.get_or_init(|| supports_color::on(supports_color::Stream::Stdout).is_some()) -} - -/// Whether stderr supports ANSI color output. Detected independently from -/// stdout so a redirected stdout (non-TTY) does not strip ANSI from a stderr -/// that is still an interactive terminal. -fn stderr_supports_color() -> bool { - use std::sync::OnceLock; - static CACHE: OnceLock = OnceLock::new(); - *CACHE.get_or_init(|| supports_color::on(supports_color::Stream::Stderr).is_some()) -} diff --git a/crates/vite_task/src/session/reporter/grouped/mod.rs b/crates/vite_task/src/session/reporter/grouped/mod.rs deleted file mode 100644 index d3c508808..000000000 --- a/crates/vite_task/src/session/reporter/grouped/mod.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! Grouped reporter — buffers output per task, prints as a block on completion. - -use std::{cell::RefCell, io::Write, process::ExitStatus as StdExitStatus, rc::Rc, sync::Arc}; - -use owo_colors::Style; -use vite_path::AbsolutePath; -use vite_task_plan::{ExecutionItemDisplay, LeafExecutionKind}; - -use super::{ - ColorSupport, ColorizeExt, ExitStatus, GraphExecutionReporter, GraphExecutionReporterBuilder, - LeafExecutionReporter, PipeWriters, StdioConfig, StdioSuggestion, - format_command_with_cache_status, format_task_label, maybe_strip_writer, - write_leaf_trailing_output, -}; -use crate::session::event::{CacheStatus, CacheUpdateStatus, ExecutionError}; - -mod writer; - -use writer::GroupedWriter; - -pub struct GroupedReporterBuilder { - workspace_path: Arc, - writer: Box, - color_support: ColorSupport, -} - -impl GroupedReporterBuilder { - /// Grouped mode buffers child output and flushes it through `writer` - /// at finish time. The pipe writers themselves (see - /// `LeafExecutionReporter::start`) strip ANSI on the way into the buffer, - /// so by the time the buffer reaches `writer` it already matches the - /// terminal's colour capability. `writer` is therefore stored unwrapped. - pub fn new( - workspace_path: Arc, - writer: Box, - color_support: ColorSupport, - ) -> Self { - Self { workspace_path, writer, color_support } - } -} - -impl GraphExecutionReporterBuilder for GroupedReporterBuilder { - fn build(self: Box) -> Box { - Box::new(GroupedGraphReporter { - writer: Rc::new(RefCell::new(self.writer)), - workspace_path: self.workspace_path, - color_support: self.color_support, - }) - } -} - -struct GroupedGraphReporter { - writer: Rc>>, - workspace_path: Arc, - color_support: ColorSupport, -} - -impl GraphExecutionReporter for GroupedGraphReporter { - fn new_leaf_execution( - &mut self, - display: &ExecutionItemDisplay, - _leaf_kind: &LeafExecutionKind, - ) -> Box { - let label = format_task_label(display); - Box::new(GroupedLeafReporter { - writer: Rc::clone(&self.writer), - display: display.clone(), - workspace_path: Arc::clone(&self.workspace_path), - label, - started: false, - grouped_buffer: None, - color_support: self.color_support, - }) - } - - fn finish(self: Box) -> Result<(), ExitStatus> { - let mut writer = self.writer.borrow_mut(); - let _ = writer.flush(); - Ok(()) - } -} - -struct GroupedLeafReporter { - writer: Rc>>, - display: ExecutionItemDisplay, - workspace_path: Arc, - label: vite_str::Str, - started: bool, - grouped_buffer: Option>>>, - color_support: ColorSupport, -} - -impl LeafExecutionReporter for GroupedLeafReporter { - fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { - let line = - format_command_with_cache_status(&self.display, &self.workspace_path, &cache_status); - - self.started = true; - - // Print labeled command line immediately (before output is buffered). - let labeled_line = vite_str::format!("{} {line}", self.label); - let mut writer = self.writer.borrow_mut(); - let _ = writer.write_all(labeled_line.as_bytes()); - let _ = writer.flush(); - - // Create shared buffer for both stdout and stderr. - let buffer = Rc::new(RefCell::new(Vec::new())); - self.grouped_buffer = Some(Rc::clone(&buffer)); - - StdioConfig { - suggestion: StdioSuggestion::Piped, - writers: PipeWriters { - stdout_writer: maybe_strip_writer( - Box::new(GroupedWriter::new(Rc::clone(&buffer))), - self.color_support.stdout, - ), - stderr_writer: maybe_strip_writer( - Box::new(GroupedWriter::new(buffer)), - self.color_support.stderr, - ), - }, - } - } - - fn finish( - self: Box, - _status: Option, - _cache_update_status: CacheUpdateStatus, - error: Option, - ) { - // Build grouped block: header + buffered output. - let mut extra = Vec::new(); - if let Some(ref grouped_buffer) = self.grouped_buffer { - let content = grouped_buffer.borrow(); - if !content.is_empty() { - let header = vite_str::format!( - "{} {} {}\n", - "──".style(Style::new().bright_black()), - self.label, - "──".style(Style::new().bright_black()) - ); - extra.extend_from_slice(header.as_bytes()); - extra.extend_from_slice(&content); - } - } - - write_leaf_trailing_output(&self.writer, error, self.started, &extra); - } -} - -#[cfg(test)] -mod tests { - use vite_task_plan::ExecutionItemKind; - - use super::*; - use crate::session::{ - event::CacheDisabledReason, - reporter::{ - StdioSuggestion, - test_fixtures::{spawn_task, test_path}, - }, - }; - - fn leaf_kind(item: &vite_task_plan::ExecutionItem) -> &LeafExecutionKind { - match &item.kind { - ExecutionItemKind::Leaf(kind) => kind, - ExecutionItemKind::Expanded(_) => panic!("test fixture item must be a Leaf"), - } - } - - #[test] - fn always_suggests_piped() { - let task = spawn_task("build"); - let item = &task.items[0]; - - let builder = Box::new(GroupedReporterBuilder::new( - test_path(), - Box::new(std::io::sink()), - ColorSupport::uniform(false), - )); - let mut reporter = builder.build(); - let mut leaf = reporter.new_leaf_execution(&item.execution_item_display, leaf_kind(item)); - let stdio_config = leaf.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)); - assert_eq!(stdio_config.suggestion, StdioSuggestion::Piped); - } -} diff --git a/crates/vite_task/src/session/reporter/grouped/writer.rs b/crates/vite_task/src/session/reporter/grouped/writer.rs deleted file mode 100644 index 6ec2acc84..000000000 --- a/crates/vite_task/src/session/reporter/grouped/writer.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! A [`Write`] wrapper that buffers all output for later retrieval. - -use std::{ - cell::RefCell, - io::{self, Write}, - rc::Rc, -}; - -/// Writer that buffers all output into a shared buffer. -/// -/// Both stdout and stderr [`GroupedWriter`]s for a task share the same -/// `Rc>>` buffer, so output is naturally interleaved in -/// arrival order. The buffer is read and flushed as a block when the -/// task completes. -pub struct GroupedWriter { - buffer: Rc>>, -} - -impl GroupedWriter { - pub const fn new(buffer: Rc>>) -> Self { - Self { buffer } - } -} - -impl Write for GroupedWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buffer.borrow_mut().extend_from_slice(buf); - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - // No-op — output is flushed as a block by the reporter on task completion. - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn buffers_output() { - let buffer = Rc::new(RefCell::new(Vec::new())); - let mut writer = GroupedWriter::new(Rc::clone(&buffer)); - writer.write_all(b"hello ").unwrap(); - writer.write_all(b"world").unwrap(); - assert_eq!(&*buffer.borrow(), b"hello world"); - } - - #[test] - fn shared_buffer_interleaves() { - let buffer = Rc::new(RefCell::new(Vec::new())); - let mut stdout = GroupedWriter::new(Rc::clone(&buffer)); - let mut stderr = GroupedWriter::new(Rc::clone(&buffer)); - stdout.write_all(b"out1\n").unwrap(); - stderr.write_all(b"err1\n").unwrap(); - stdout.write_all(b"out2\n").unwrap(); - assert_eq!(&*buffer.borrow(), b"out1\nerr1\nout2\n"); - } -} diff --git a/crates/vite_task/src/session/reporter/interleaved/mod.rs b/crates/vite_task/src/session/reporter/interleaved/mod.rs deleted file mode 100644 index 9722e95e0..000000000 --- a/crates/vite_task/src/session/reporter/interleaved/mod.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! Interleaved reporter — streams output directly as tasks produce it. - -use std::{cell::RefCell, io::Write, process::ExitStatus as StdExitStatus, rc::Rc, sync::Arc}; - -use vite_path::AbsolutePath; -use vite_task_plan::{ExecutionItemDisplay, LeafExecutionKind}; - -use super::{ - ColorSupport, ExitStatus, GraphExecutionReporter, GraphExecutionReporterBuilder, - LeafExecutionReporter, PipeWriters, StdioConfig, StdioSuggestion, - format_command_with_cache_status, maybe_strip_writer, write_leaf_trailing_output, -}; -use crate::session::event::{CacheStatus, CacheUpdateStatus, ExecutionError}; - -pub struct InterleavedReporterBuilder { - workspace_path: Arc, - writer: Box, - color_support: ColorSupport, -} - -impl InterleavedReporterBuilder { - /// The reporter's own writes (command lines, error banners) decide - /// colour-vs-plain at format time via `ColorizeExt`, so `writer` is - /// stored unwrapped. `color_support` is forwarded to the pipe writers - /// in `LeafExecutionReporter::start`, where ANSI emitted by child tasks is stripped - /// for non-terminal sinks. - pub fn new( - workspace_path: Arc, - writer: Box, - color_support: ColorSupport, - ) -> Self { - Self { workspace_path, writer, color_support } - } -} - -impl GraphExecutionReporterBuilder for InterleavedReporterBuilder { - fn build(self: Box) -> Box { - Box::new(InterleavedGraphReporter { - writer: Rc::new(RefCell::new(self.writer)), - workspace_path: self.workspace_path, - color_support: self.color_support, - }) - } -} - -struct InterleavedGraphReporter { - writer: Rc>>, - workspace_path: Arc, - color_support: ColorSupport, -} - -impl GraphExecutionReporter for InterleavedGraphReporter { - fn new_leaf_execution( - &mut self, - display: &ExecutionItemDisplay, - leaf_kind: &LeafExecutionKind, - ) -> Box { - let stdio_suggestion = match leaf_kind { - LeafExecutionKind::Spawn(_) => StdioSuggestion::Inherited, - LeafExecutionKind::InProcess(_) => StdioSuggestion::Piped, - }; - - Box::new(InterleavedLeafReporter { - writer: Rc::clone(&self.writer), - display: display.clone(), - workspace_path: Arc::clone(&self.workspace_path), - stdio_suggestion, - started: false, - color_support: self.color_support, - }) - } - - fn finish(self: Box) -> Result<(), ExitStatus> { - let mut writer = self.writer.borrow_mut(); - let _ = writer.flush(); - Ok(()) - } -} - -struct InterleavedLeafReporter { - writer: Rc>>, - display: ExecutionItemDisplay, - workspace_path: Arc, - stdio_suggestion: StdioSuggestion, - started: bool, - color_support: ColorSupport, -} - -impl LeafExecutionReporter for InterleavedLeafReporter { - fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { - let line = - format_command_with_cache_status(&self.display, &self.workspace_path, &cache_status); - - self.started = true; - - let mut writer = self.writer.borrow_mut(); - let _ = writer.write_all(line.as_bytes()); - let _ = writer.flush(); - - StdioConfig { - suggestion: self.stdio_suggestion, - writers: PipeWriters { - stdout_writer: maybe_strip_writer( - Box::new(std::io::stdout()), - self.color_support.stdout, - ), - stderr_writer: maybe_strip_writer( - Box::new(std::io::stderr()), - self.color_support.stderr, - ), - }, - } - } - - fn finish( - self: Box, - _status: Option, - _cache_update_status: CacheUpdateStatus, - error: Option, - ) { - write_leaf_trailing_output(&self.writer, error, self.started, &[]); - } -} - -#[cfg(test)] -mod tests { - use vite_task_plan::ExecutionItemKind; - - use super::*; - use crate::session::{ - event::CacheDisabledReason, - reporter::{ - StdioSuggestion, - test_fixtures::{in_process_task, spawn_task, test_path}, - }, - }; - - fn leaf_kind(item: &vite_task_plan::ExecutionItem) -> &LeafExecutionKind { - match &item.kind { - ExecutionItemKind::Leaf(kind) => kind, - ExecutionItemKind::Expanded(_) => panic!("test fixture item must be a Leaf"), - } - } - - fn suggestion_for( - display: &ExecutionItemDisplay, - leaf_kind: &LeafExecutionKind, - ) -> StdioSuggestion { - let builder = Box::new(InterleavedReporterBuilder::new( - test_path(), - Box::new(std::io::sink()), - ColorSupport::uniform(false), - )); - let mut reporter = builder.build(); - let mut leaf = reporter.new_leaf_execution(display, leaf_kind); - leaf.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)).suggestion - } - - #[test] - fn spawn_suggests_inherited() { - let task = spawn_task("build"); - let item = &task.items[0]; - assert_eq!( - suggestion_for(&item.execution_item_display, leaf_kind(item)), - StdioSuggestion::Inherited - ); - } - - #[test] - fn in_process_leaf_suggests_piped() { - let task = in_process_task("echo"); - let item = &task.items[0]; - assert_eq!( - suggestion_for(&item.execution_item_display, leaf_kind(item)), - StdioSuggestion::Piped - ); - } -} diff --git a/crates/vite_task/src/session/reporter/labeled/mod.rs b/crates/vite_task/src/session/reporter/labeled/mod.rs deleted file mode 100644 index fa41fa641..000000000 --- a/crates/vite_task/src/session/reporter/labeled/mod.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! Labeled reporter — prefixes each output line with `[pkg#task]`. - -use std::{cell::RefCell, io::Write, process::ExitStatus as StdExitStatus, rc::Rc, sync::Arc}; - -use vite_path::AbsolutePath; -use vite_task_plan::{ExecutionItemDisplay, LeafExecutionKind}; - -use super::{ - ColorSupport, ExitStatus, GraphExecutionReporter, GraphExecutionReporterBuilder, - LeafExecutionReporter, PipeWriters, StdioConfig, StdioSuggestion, - format_command_with_cache_status, format_task_label, maybe_strip_writer, - write_leaf_trailing_output, -}; -use crate::session::event::{CacheStatus, CacheUpdateStatus, ExecutionError}; - -mod writer; - -use writer::LabeledWriter; - -pub struct LabeledReporterBuilder { - workspace_path: Arc, - writer: Box, - color_support: ColorSupport, -} - -impl LabeledReporterBuilder { - /// `writer` is stored unwrapped — the reporter's own writes pick - /// colour-vs-plain at format time via `ColorizeExt`. Child-process - /// pipes are stripped per-stream inside `LeafExecutionReporter::start`. - pub fn new( - workspace_path: Arc, - writer: Box, - color_support: ColorSupport, - ) -> Self { - Self { workspace_path, writer, color_support } - } -} - -impl GraphExecutionReporterBuilder for LabeledReporterBuilder { - fn build(self: Box) -> Box { - Box::new(LabeledGraphReporter { - writer: Rc::new(RefCell::new(self.writer)), - workspace_path: self.workspace_path, - color_support: self.color_support, - }) - } -} - -struct LabeledGraphReporter { - writer: Rc>>, - workspace_path: Arc, - color_support: ColorSupport, -} - -impl GraphExecutionReporter for LabeledGraphReporter { - fn new_leaf_execution( - &mut self, - display: &ExecutionItemDisplay, - _leaf_kind: &LeafExecutionKind, - ) -> Box { - Box::new(LabeledLeafReporter { - writer: Rc::clone(&self.writer), - display: display.clone(), - workspace_path: Arc::clone(&self.workspace_path), - started: false, - color_support: self.color_support, - }) - } - - fn finish(self: Box) -> Result<(), ExitStatus> { - let mut writer = self.writer.borrow_mut(); - let _ = writer.flush(); - Ok(()) - } -} - -struct LabeledLeafReporter { - writer: Rc>>, - display: ExecutionItemDisplay, - workspace_path: Arc, - started: bool, - color_support: ColorSupport, -} - -impl LeafExecutionReporter for LabeledLeafReporter { - fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { - let label = format_task_label(&self.display); - let line = - format_command_with_cache_status(&self.display, &self.workspace_path, &cache_status); - - self.started = true; - - let labeled_line = vite_str::format!("{label} {line}"); - let mut writer = self.writer.borrow_mut(); - let _ = writer.write_all(labeled_line.as_bytes()); - let _ = writer.flush(); - - let prefix = vite_str::format!("{label} "); - - StdioConfig { - suggestion: StdioSuggestion::Piped, - writers: PipeWriters { - stdout_writer: Box::new(LabeledWriter::new( - maybe_strip_writer(Box::new(std::io::stdout()), self.color_support.stdout), - prefix.as_bytes().to_vec(), - )), - stderr_writer: Box::new(LabeledWriter::new( - maybe_strip_writer(Box::new(std::io::stderr()), self.color_support.stderr), - prefix.as_bytes().to_vec(), - )), - }, - } - } - - fn finish( - self: Box, - _status: Option, - _cache_update_status: CacheUpdateStatus, - error: Option, - ) { - write_leaf_trailing_output(&self.writer, error, self.started, &[]); - } -} - -#[cfg(test)] -mod tests { - use vite_task_plan::ExecutionItemKind; - - use super::*; - use crate::session::{ - event::CacheDisabledReason, - reporter::{ - StdioSuggestion, - test_fixtures::{spawn_task, test_path}, - }, - }; - - fn leaf_kind(item: &vite_task_plan::ExecutionItem) -> &LeafExecutionKind { - match &item.kind { - ExecutionItemKind::Leaf(kind) => kind, - ExecutionItemKind::Expanded(_) => panic!("test fixture item must be a Leaf"), - } - } - - #[test] - fn always_suggests_piped() { - let task = spawn_task("build"); - let item = &task.items[0]; - - let builder = Box::new(LabeledReporterBuilder::new( - test_path(), - Box::new(std::io::sink()), - ColorSupport::uniform(false), - )); - let mut reporter = builder.build(); - let mut leaf = reporter.new_leaf_execution(&item.execution_item_display, leaf_kind(item)); - let stdio_config = leaf.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)); - assert_eq!(stdio_config.suggestion, StdioSuggestion::Piped); - } -} diff --git a/crates/vite_task/src/session/reporter/labeled/writer.rs b/crates/vite_task/src/session/reporter/labeled/writer.rs deleted file mode 100644 index 65440fd9a..000000000 --- a/crates/vite_task/src/session/reporter/labeled/writer.rs +++ /dev/null @@ -1,141 +0,0 @@ -//! A [`Write`] wrapper that prefixes each line with a label (e.g., `[pkg#task] `). - -use std::io::{self, Write}; - -/// Writer that prefixes each complete line with a label. -/// -/// Data is buffered internally. On [`flush`](Write::flush), complete lines -/// (terminated by `\n`) are written to the inner writer with the prefix -/// prepended. Any trailing partial line is kept in the buffer until the -/// next flush. -pub struct LabeledWriter { - inner: Box, - prefix: Vec, - buffer: Vec, -} - -impl LabeledWriter { - pub fn new(inner: Box, prefix: Vec) -> Self { - Self { inner, prefix, buffer: Vec::new() } - } -} - -impl Write for LabeledWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buffer.extend_from_slice(buf); - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - // Find the last newline — everything up to (and including) it can be - // split into complete lines and written with prefixes. - let last_nl = self.buffer.iter().rposition(|&b| b == b'\n'); - let Some(last_nl) = last_nl else { - // No complete lines yet — keep buffering. - return Ok(()); - }; - - // Split off the complete portion (0..=last_nl) from any trailing partial line. - let remaining = self.buffer.split_off(last_nl + 1); - let complete = std::mem::replace(&mut self.buffer, remaining); - - // Batch prefix + line into a single write to reduce syscall overhead. - let mut prefixed = Vec::new(); - for line in complete.split_inclusive(|&b| b == b'\n') { - prefixed.extend_from_slice(&self.prefix); - prefixed.extend_from_slice(line); - } - self.inner.write_all(&prefixed)?; - - self.inner.flush() - } -} - -impl Drop for LabeledWriter { - fn drop(&mut self) { - // Flush any remaining partial line on drop. - if !self.buffer.is_empty() { - let buf = std::mem::take(&mut self.buffer); - let _ = self.inner.write_all(&self.prefix); - let _ = self.inner.write_all(&buf); - let _ = self.inner.flush(); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn labeled_output(prefix: &str, chunks: &[&[u8]]) -> Vec { - let output = Vec::new(); - let mut writer = LabeledWriter::new(Box::new(output), prefix.as_bytes().to_vec()); - for chunk in chunks { - writer.write_all(chunk).unwrap(); - writer.flush().unwrap(); - } - drop(writer); - // We need to get the inner writer back — reconstruct by running again - // and capturing output via a shared buffer. - let output = std::rc::Rc::new(std::cell::RefCell::new(Vec::new())); - let output_clone = std::rc::Rc::clone(&output); - { - let mut writer = - LabeledWriter::new(Box::new(RcWriter(output_clone)), prefix.as_bytes().to_vec()); - for chunk in chunks { - writer.write_all(chunk).unwrap(); - writer.flush().unwrap(); - } - } - std::rc::Rc::try_unwrap(output).unwrap().into_inner() - } - - struct RcWriter(std::rc::Rc>>); - - impl Write for RcWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.borrow_mut().extend_from_slice(buf); - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - } - - #[test] - fn single_complete_line() { - let result = labeled_output("[app] ", &[b"hello\n"]); - assert_eq!(result, b"[app] hello\n"); - } - - #[test] - fn multiple_lines_in_one_chunk() { - let result = labeled_output("[a] ", &[b"line1\nline2\n"]); - assert_eq!(result, b"[a] line1\n[a] line2\n"); - } - - #[test] - fn partial_line_flushed_on_drop() { - let result = labeled_output("[x] ", &[b"no newline"]); - assert_eq!(result, b"[x] no newline"); - } - - #[test] - fn split_across_chunks() { - let result = labeled_output("[p] ", &[b"split ", b"line\n"]); - assert_eq!(result, b"[p] split line\n"); - } - - #[test] - fn empty_line() { - let result = labeled_output("[e] ", &[b"\n"]); - assert_eq!(result, b"[e] \n"); - } - - #[test] - fn multiple_flushes_with_partial() { - let result = labeled_output("[m] ", &[b"a\nb", b"c\n"]); - assert_eq!(result, b"[m] a\n[m] bc\n"); - } -} diff --git a/crates/vite_task/src/session/reporter/mod.rs b/crates/vite_task/src/session/reporter/mod.rs deleted file mode 100644 index 06f82c36a..000000000 --- a/crates/vite_task/src/session/reporter/mod.rs +++ /dev/null @@ -1,508 +0,0 @@ -//! Reporter traits and implementations for rendering execution events. -//! -//! This module provides a typestate-based reporter system with three phases: -//! -//! 1. [`GraphExecutionReporterBuilder`] — created before execution begins. -//! Transitions to [`GraphExecutionReporter`] when `build()` is called. -//! -//! 2. [`GraphExecutionReporter`] — creates [`LeafExecutionReporter`] -//! instances for individual leaf executions via `new_leaf_execution()`. Finalized with `finish()`. -//! -//! 3. [`LeafExecutionReporter`] — handles events for a single leaf execution (output streaming, -//! cache status, errors). Finalized with `finish()`. -//! -//! Three output mode reporters are provided: -//! -//! - [`interleaved::InterleavedReporterBuilder`] — streams output directly as tasks produce it. -//! - [`labeled::LabeledReporterBuilder`] — prefixes each output line with `[pkg#task]`. -//! - [`grouped::GroupedReporterBuilder`] — buffers output per task and prints as a block. -//! -//! [`summary_reporter::SummaryReporterBuilder`] wraps any mode reporter to add summary -//! tracking (task results, exit codes, cache stats) and renders the summary at the end. -//! -//! Additionally, [`plain::PlainReporter`] is a standalone [`LeafExecutionReporter`] for -//! single-leaf synthetic executions (e.g., `execute_synthetic`). - -mod grouped; -mod interleaved; -mod labeled; -mod plain; -pub mod summary; -mod summary_reporter; - -use std::{io::Write, process::ExitStatus as StdExitStatus}; - -pub use grouped::GroupedReporterBuilder; -pub use interleaved::InterleavedReporterBuilder; -pub use labeled::LabeledReporterBuilder; -use owo_colors::Style; -pub use plain::PlainReporter; -pub use summary_reporter::SummaryReporterBuilder; -use vite_path::AbsolutePath; -use vite_str::Str; -use vite_task_plan::{ExecutionItemDisplay, LeafExecutionKind}; - -use super::{ - cache::format_cache_status_inline, - event::{CacheStatus, CacheUpdateStatus, ExecutionError}, -}; - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Exit status type -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -/// Exit status code for task execution. -/// -/// Wraps a `u8` exit code. `0` means success, non-zero means failure. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ExitStatus(pub u8); - -impl ExitStatus { - pub const FAILURE: Self = Self(1); - pub const SUCCESS: Self = Self(0); -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Stdio suggestion and configuration -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -/// Suggestion from the reporter about what stdio mode to use for a spawned process. -/// -/// The actual stdio mode is determined by [`execute_spawn`](super::execute::execute_spawn) -/// based on this suggestion AND whether caching is enabled for the task: -/// - `Inherited` is only honoured when caching is disabled (`cache_metadata` is `None`). -/// With caching enabled, the execution engine overrides to `Piped` so that output can -/// be captured for the cache. -/// - `Piped` is always respected as-is. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum StdioSuggestion { - /// stdin is `/dev/null`, stdout and stderr are piped into the reporter's - /// [`Write`] streams. Used when multiple tasks run concurrently and - /// stdio should not be shared. - Piped, - /// All three file descriptors (stdin, stdout, stderr) are inherited from the - /// parent process, allowing interactive input and direct terminal output. - /// Only effective when caching is disabled for the task. - Inherited, -} - -/// Stdio configuration returned by [`LeafExecutionReporter::start`]. -/// -/// Contains the reporter's suggestion for the stdio mode together with two -/// writers that receive the child process's stdout and stderr when the -/// execution engine decides to use piped mode. The writers are always provided -/// because the engine may override the suggestion (e.g. when caching forces -/// piped mode even though the reporter suggested inherited). -pub struct StdioConfig { - /// The reporter's preferred stdio mode. - pub suggestion: StdioSuggestion, - /// Writer for the child process's stderr and stdout (used in piped mode and cache replay). - pub writers: PipeWriters, -} - -pub struct PipeWriters { - pub stdout_writer: Box, - pub stderr_writer: Box, -} - -/// Color-support decision split per output stream. Reporter builders receive -/// one of these so a non-TTY stdout doesn't accidentally strip colours from -/// a TTY stderr (or vice versa). -#[derive(Debug, Clone, Copy)] -pub struct ColorSupport { - /// Whether the reporter's stdout writer (and stdout-bound pipe writers - /// for spawned tasks) supports ANSI escapes. - pub stdout: bool, - /// Whether stderr-bound pipe writers support ANSI escapes. - pub stderr: bool, -} - -#[cfg(test)] -impl ColorSupport { - /// Treat both streams the same — only used in tests to avoid duplicating - /// field assignments. - pub(super) const fn uniform(supported: bool) -> Self { - Self { stdout: supported, stderr: supported } - } -} - -/// Wrap a writer with [`anstream::StripStream`] when `color_support` is -/// `false`. Used by reporter builders to ensure ANSI escape sequences emitted -/// by the reporter or by spawned tasks are stripped at display time when the -/// user's terminal cannot render them. -/// -/// [`anstream::StripStream`] is incremental: a single escape sequence split -/// across multiple `write` calls is still removed correctly. -pub(super) fn maybe_strip_writer(writer: Box, color_support: bool) -> Box { - if color_support { writer } else { Box::new(anstream::StripStream::new(writer)) } -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Typestate traits -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -/// Builder for creating a [`GraphExecutionReporter`]. -/// -/// This is the initial state before the execution graph is known. The `build` method -/// transitions to the [`GraphExecutionReporter`] phase. -pub trait GraphExecutionReporterBuilder { - /// Create a [`GraphExecutionReporter`]. - fn build(self: Box) -> Box; -} - -/// Reporter for an entire graph execution session. -/// -/// Creates [`LeafExecutionReporter`] instances for individual leaf executions -/// and finalizes the session with `finish()`. -pub trait GraphExecutionReporter { - /// Create a new leaf execution reporter for the given leaf. - fn new_leaf_execution( - &mut self, - display: &ExecutionItemDisplay, - leaf_kind: &LeafExecutionKind, - ) -> Box; - - /// Finalize the graph execution session. - /// - /// Leaf-level errors are already tracked internally by the reporter via the - /// leaf reporter's `finish()` method. Graph-level errors (cycle detection) are - /// now caught at plan time and never reach the reporter. - /// - /// Returns `Ok(())` if all tasks succeeded, or `Err(ExitStatus)` to indicate the - /// process should exit with the given status code. - fn finish(self: Box) -> Result<(), ExitStatus>; -} - -/// Reporter for a single leaf execution (one spawned process or in-process command). -/// -/// Lifecycle: `start()` → `finish()`. -/// -/// `start()` may not be called before `finish()` if an error occurs before the cache -/// status is determined (e.g., cache lookup failure). -pub trait LeafExecutionReporter { - /// Report that execution is starting with the given cache status. - /// - /// Called after the cache lookup completes, before any output is produced. - /// Returns a [`StdioConfig`] containing: - /// - The reporter's stdio mode suggestion (inherited or piped). - /// - Two [`Write`] streams for receiving the child's stdout and stderr - /// (used when the execution engine decides on piped mode, or for cache replay). - /// - /// The execution engine decides the actual stdio mode based on the suggestion - /// AND whether caching is enabled — inherited stdio is only used when the - /// suggestion is [`StdioSuggestion::Inherited`] AND the task has no cache - /// metadata (caching disabled). - fn start(&mut self, cache_status: CacheStatus) -> StdioConfig; - - /// Finalize this leaf execution. - /// - /// - `status`: The process exit status, or `None` for cache hits and in-process commands. - /// - `cache_update_status`: Whether the cache was updated after execution. - /// - `error`: If `Some`, an error occurred during this leaf's execution (cache lookup - /// failure, spawn failure, fingerprint creation failure, cache update failure). - /// - /// This method consumes the reporter — no further calls are possible after `finish()`. - fn finish( - self: Box, - status: Option, - cache_update_status: CacheUpdateStatus, - error: Option, - ); -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Shared display helpers -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -const COMMAND_STYLE: Style = Style::new().blue(); -const CACHE_MISS_STYLE: Style = Style::new().bright_black(); - -/// Apply `style` to `self` only when stdout supports ANSI colours -/// (auto-detected via the `supports-color` crate, honouring `NO_COLOR`, -/// `FORCE_COLOR`, and TTY). Used by the format helpers that write to the -/// reporter's main writer / saved-summary buffer; for child-process pipes -/// see [`maybe_strip_writer`] instead, which strips bytes the runner does -/// not control. -trait ColorizeExt: owo_colors::OwoColorize { - fn style(&self, style: Style) -> impl std::fmt::Display + '_; -} - -impl ColorizeExt for T -where - T: owo_colors::OwoColorize + std::fmt::Display, -{ - fn style(&self, style: Style) -> impl std::fmt::Display + '_ { - self.if_supports_color(owo_colors::Stream::Stdout, move |s| { - owo_colors::OwoColorize::style(s, style) - }) - } -} - -/// Format the display's cwd as a string relative to the workspace root. -/// Returns an empty string if the cwd equals the workspace root. -fn format_cwd_relative(display: &ExecutionItemDisplay, workspace_path: &AbsolutePath) -> Str { - let cwd_relative = if let Ok(Some(rel)) = display.cwd.strip_prefix(workspace_path) { - Str::from(rel.as_str()) - } else { - Str::default() - }; - if cwd_relative.is_empty() { Str::default() } else { vite_str::format!("~/{cwd_relative}") } -} - -/// Format the task label for labeled/grouped modes (e.g., `[pkg#task]`). -fn format_task_label(display: &ExecutionItemDisplay) -> Str { - vite_str::format!( - "{}", - vite_str::format!("[{}]", display.task_display).style(Style::new().bright_black()) - ) -} - -/// Format the command string with cwd prefix for display (e.g., `~/packages/lib$ vitest run`). -fn format_command_display(display: &ExecutionItemDisplay, workspace_path: &AbsolutePath) -> Str { - let cwd_str = format_cwd_relative(display, workspace_path); - vite_str::format!("{cwd_str}$ {}", display.command) -} - -/// Format the command line with optional inline cache status. -/// -/// This is called during `start()` to show the user what command is being executed -/// and its cache status. The caller writes the returned string to the async writer. -fn format_command_with_cache_status( - display: &ExecutionItemDisplay, - workspace_path: &AbsolutePath, - cache_status: &CacheStatus, -) -> Str { - let command_str = format_command_display(display, workspace_path); - format_cache_status_inline(cache_status).map_or_else( - || vite_str::format!("{}\n", command_str.style(COMMAND_STYLE)), - |inline_status| { - let styled_status = inline_status.split_once(' ').map_or_else( - || inline_status.style(Style::new().bright_black()).to_string(), - |(symbol, text)| { - let (symbol_style, text_style) = match cache_status { - CacheStatus::Hit { .. } => { - (Style::new().green(), Style::new().bright_black()) - } - CacheStatus::Miss(_) => (CACHE_MISS_STYLE, Style::new().bright_black()), - CacheStatus::Disabled(_) => { - (Style::new().black(), Style::new().bright_black()) - } - }; - - vite_str::format!("{} {}", symbol.style(symbol_style), text.style(text_style)) - .to_string() - }, - ); - - vite_str::format!("{} {styled_status}\n", command_str.style(COMMAND_STYLE)) - }, - ) -} - -/// Format an error message in red with an error icon. -fn format_error_message(message: &str) -> Str { - vite_str::format!( - "{} {}\n", - "✗".style(Style::new().red().bold()), - message.style(Style::new().red()) - ) -} - -/// Write the trailing output for a leaf execution: optional extra content (e.g., grouped -/// output block), error message, and a separating newline. -fn write_leaf_trailing_output( - writer: &std::cell::RefCell>, - error: Option, - started: bool, - extra: &[u8], -) { - let mut buf = Vec::new(); - - buf.extend_from_slice(extra); - - if let Some(error) = error { - let message = vite_str::format!("{:#}", anyhow::Error::from(error)); - buf.extend_from_slice(format_error_message(&message).as_bytes()); - } - - if started { - buf.push(b'\n'); - } - - if !buf.is_empty() { - let mut writer = writer.borrow_mut(); - let _ = writer.write_all(&buf); - let _ = writer.flush(); - } -} - -/// Format the "cache hit, logs replayed" message for synthetic executions without display info. -fn format_cache_hit_message() -> Str { - vite_str::format!("{}\n", "◉ cache hit, logs replayed".style(Style::new().green().dimmed())) -} - -#[cfg(test)] -mod strip_writer_tests { - use std::io::Write; - - use super::maybe_strip_writer; - - /// Collect every byte written to an inner `Vec` via a wrapping writer. - /// Helper used to inspect what `maybe_strip_writer` actually emitted. - struct SharedSink(std::rc::Rc>>); - - impl Write for SharedSink { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.0.borrow_mut().extend_from_slice(buf); - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } - } - - fn captured(color_support: bool, chunks: &[&[u8]]) -> Vec { - let sink: std::rc::Rc>> = std::rc::Rc::default(); - let mut writer = - maybe_strip_writer(Box::new(SharedSink(std::rc::Rc::clone(&sink))), color_support); - for chunk in chunks { - writer.write_all(chunk).unwrap(); - } - writer.flush().unwrap(); - drop(writer); - sink.take() - } - - #[test] - fn keeps_ansi_when_color_supported() { - let bytes = captured(true, &[b"\x1b[31mred\x1b[0m"]); - assert_eq!(bytes, b"\x1b[31mred\x1b[0m"); - } - - #[test] - fn strips_ansi_in_single_write() { - let bytes = captured(false, &[b"\x1b[31mred\x1b[0m plain"]); - assert_eq!(bytes, b"red plain"); - } - - #[test] - fn strips_ansi_across_write_split_at_csi() { - // `\x1b[` arrives, then the rest of the SGR. - let bytes = captured(false, &[b"hello \x1b[", b"31mWORLD\x1b[0m tail"]); - assert_eq!(bytes, b"hello WORLD tail"); - } - - #[test] - fn strips_ansi_across_write_split_inside_params() { - // Split inside the parameter section of a CSI SGR. - let bytes = captured(false, &[b"\x1b[3", b"8;5;208m", b"orange\x1b[0m"]); - assert_eq!(bytes, b"orange"); - } - - #[test] - fn strips_ansi_across_write_split_byte_by_byte() { - // Worst case: one byte per write. - let escape = b"\x1b[31mhi\x1b[0m"; - let chunks: Vec<&[u8]> = escape.iter().map(std::slice::from_ref).collect(); - let bytes = captured(false, &chunks); - assert_eq!(bytes, b"hi"); - } - - #[test] - fn strips_osc_hyperlink_across_writes() { - // OSC 8 hyperlink sequence ESC ] 8 ; ; URL ESC \ TEXT ESC ] 8 ; ; ESC \ - let bytes = - captured(false, &[b"\x1b]8;;https://example.com\x1b\\", b"link", b"\x1b]8;;\x1b\\"]); - assert_eq!(bytes, b"link"); - } - - #[test] - fn leaves_plain_bytes_alone_when_stripping() { - let bytes = captured(false, &[b"plain text\n", b"another line\n"]); - assert_eq!(bytes, b"plain text\nanother line\n"); - } -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Test fixtures (shared by child module tests) -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -#[cfg(test)] -pub mod test_fixtures { - use std::{collections::BTreeMap, sync::Arc}; - - use vite_path::AbsolutePath; - use vite_task_graph::display::TaskDisplay; - use vite_task_plan::{ - ExecutionItem, ExecutionItemDisplay, ExecutionItemKind, InProcessExecution, - LeafExecutionKind, SpawnCommand, SpawnExecution, TaskExecution, - }; - - /// Create a dummy `AbsolutePath` for test fixtures. - pub fn test_path() -> Arc { - #[cfg(unix)] - { - Arc::from(AbsolutePath::new("/test").unwrap()) - } - #[cfg(windows)] - { - Arc::from(AbsolutePath::new("C:\\test").unwrap()) - } - } - - /// Create a dummy `TaskDisplay` for test fixtures. - pub fn test_task_display(name: &str) -> TaskDisplay { - TaskDisplay { - package_name: "pkg".into(), - task_name: name.into(), - package_path: test_path(), - } - } - - /// Create a dummy `ExecutionItemDisplay` for test fixtures. - pub fn test_display(name: &str) -> ExecutionItemDisplay { - ExecutionItemDisplay { - task_display: test_task_display(name), - command: name.into(), - cwd: test_path(), - } - } - - /// Create a `TaskExecution` with a single spawn leaf. - pub fn spawn_task(name: &str) -> TaskExecution { - TaskExecution { - task_display: test_task_display(name), - items: vec![ExecutionItem { - execution_item_display: test_display(name), - kind: ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(SpawnExecution { - cache_metadata: None, - spawn_command: SpawnCommand { - program_path: test_path(), - args: Arc::from([]), - spawn_envs: Arc::new(BTreeMap::new()), - cwd: test_path(), - }, - })), - }], - } - } - - /// Create a `TaskExecution` with a single in-process leaf (echo). - pub fn in_process_task(name: &str) -> TaskExecution { - TaskExecution { - task_display: test_task_display(name), - items: vec![ExecutionItem { - execution_item_display: test_display(name), - kind: ExecutionItemKind::Leaf(LeafExecutionKind::InProcess( - InProcessExecution::get_builtin_execution( - "echo", - ["hello"].iter(), - &test_path(), - ) - .unwrap(), - )), - }], - } - } -} diff --git a/crates/vite_task/src/session/reporter/plain.rs b/crates/vite_task/src/session/reporter/plain.rs deleted file mode 100644 index ad34f409a..000000000 --- a/crates/vite_task/src/session/reporter/plain.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! Plain reporter — a standalone [`LeafExecutionReporter`] for single-leaf execution. -//! -//! Used for synthetic executions (e.g., auto-install) where there is no execution graph -//! and no summary is needed. Writes directly to the provided writer with no shared state. - -use std::io::Write; - -use super::{ - ColorSupport, LeafExecutionReporter, PipeWriters, StdioConfig, StdioSuggestion, - format_cache_hit_message, format_error_message, maybe_strip_writer, -}; -// `maybe_strip_writer` is used for the child-process pipe writers; reporter -// output decides colour-vs-plain at format time via the `ColorizeExt` helpers -// in [`super`]. -use crate::session::event::{CacheStatus, CacheUpdateStatus, ExecutionError}; - -/// A self-contained [`LeafExecutionReporter`] for single-leaf executions -/// (e.g., `execute_synthetic`). -/// -/// This reporter: -/// - Writes display output (errors, cache-hit messages) to the provided writer -/// - Has no display info (synthetic executions have no task display) -/// - Does not track stats or print summaries -/// - Supports `silent_if_cache_hit` to suppress output for cached executions -/// -/// The exit status is determined by the caller from the `execute_spawn` return value, -/// not from the reporter. -pub struct PlainReporter { - /// Writer for reporter display output (errors, cache-hit messages). - writer: Box, - /// When true, suppresses all output (command line, process output, cache hit message) - /// for executions that are cache hits. - silent_if_cache_hit: bool, - /// Whether the current execution is a cache hit, set by `start()`. - is_cache_hit: bool, - /// Per-stream colour support — stdout decides stripping of the reporter's - /// own writes and stdout-bound pipe output; stderr decides stripping of - /// the stderr pipe writer (kept independent so a TTY stderr doesn't get - /// stripped just because stdout is redirected). - color_support: ColorSupport, -} - -impl PlainReporter { - /// Create a new plain reporter. - /// - /// - `silent_if_cache_hit`: If true, suppress all output when the execution is a cache hit. - /// - `writer`: Writer for reporter display output. - /// - `color_support`: Per-stream colour-support decision. - pub fn new( - silent_if_cache_hit: bool, - writer: Box, - color_support: ColorSupport, - ) -> Self { - Self { writer, silent_if_cache_hit, is_cache_hit: false, color_support } - } - - /// Returns true if output should be suppressed for this execution. - const fn is_silent(&self) -> bool { - self.silent_if_cache_hit && self.is_cache_hit - } -} - -impl LeafExecutionReporter for PlainReporter { - fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { - self.is_cache_hit = matches!(cache_status, CacheStatus::Hit { .. }); - // PlainReporter is used for single-leaf synthetic executions (e.g., auto-install). - // Always suggest inherited stdio so the spawned process can be interactive. - // PlainReporter has no display info (synthetic executions), - // so there's no command line to print at start. - // - // When silent_if_cache_hit is enabled and we have a cache hit, return - // sink writers that discard output — the cache replay in execute_spawn - // writes directly to these writers, so this is the reporter's only way - // to suppress replayed output. - if self.silent_if_cache_hit && self.is_cache_hit { - StdioConfig { - suggestion: StdioSuggestion::Inherited, - writers: PipeWriters { - stdout_writer: Box::new(std::io::sink()), - stderr_writer: Box::new(std::io::sink()), - }, - } - } else { - StdioConfig { - suggestion: StdioSuggestion::Inherited, - writers: PipeWriters { - stdout_writer: maybe_strip_writer( - Box::new(std::io::stdout()), - self.color_support.stdout, - ), - stderr_writer: maybe_strip_writer( - Box::new(std::io::stderr()), - self.color_support.stderr, - ), - }, - } - } - } - - fn finish( - mut self: Box, - _status: Option, - _cache_update_status: CacheUpdateStatus, - error: Option, - ) { - // Handle errors — format the full error chain and print inline. - if let Some(error) = error { - let message = vite_str::format!("{:#}", anyhow::Error::from(error)); - let line = format_error_message(&message); - let _ = self.writer.write_all(line.as_bytes()); - let _ = self.writer.flush(); - return; - } - - // For cache hits, print the "cache hit" message (unless silent) - if self.is_cache_hit && !self.is_silent() { - let line = format_cache_hit_message(); - let _ = self.writer.write_all(line.as_bytes()); - let _ = self.writer.flush(); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::session::event::CacheDisabledReason; - - #[test] - fn plain_reporter_always_suggests_inherited() { - let mut reporter = - PlainReporter::new(false, Box::new(std::io::sink()), ColorSupport::uniform(false)); - let stdio_config = - reporter.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)); - assert_eq!(stdio_config.suggestion, StdioSuggestion::Inherited); - } - - #[test] - fn plain_reporter_suggests_inherited_even_when_silent() { - let mut reporter = - PlainReporter::new(true, Box::new(std::io::sink()), ColorSupport::uniform(false)); - let stdio_config = - reporter.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)); - assert_eq!(stdio_config.suggestion, StdioSuggestion::Inherited); - } -} diff --git a/crates/vite_task/src/session/reporter/summary.rs b/crates/vite_task/src/session/reporter/summary.rs deleted file mode 100644 index b6ce11848..000000000 --- a/crates/vite_task/src/session/reporter/summary.rs +++ /dev/null @@ -1,949 +0,0 @@ -//! Structured summary types for persisting and rendering execution results. -//! -//! The [`LastRunSummary`] is built after every graph execution and: -//! 1. Saved atomically to `cache_path/last-summary.json` for `vp run --last-details`. -//! 2. Rendered immediately — either as a compact one-liner or a full detailed summary. -//! -//! Both the live reporter and the `--last-details` display use the same rendering -//! functions, ensuring consistent output. - -use std::{io::Write, num::NonZeroI32, time::Duration}; - -use owo_colors::Style; -use serde::{Deserialize, Serialize}; -use vite_path::AbsolutePath; -use vite_str::Str; - -use super::{CACHE_MISS_STYLE, COMMAND_STYLE, ColorizeExt}; -use crate::session::{ - cache::{ - CacheMiss, EnvMismatch, FingerprintMismatch, InputChangeKind, SpawnFingerprintChange, - detect_spawn_fingerprint_changes, format_input_change_str, format_spawn_change, - }, - event::{ - CacheDisabledReason, CacheErrorKind, CacheNotUpdatedReason, CacheStatus, CacheUpdateStatus, - ExecutionError, - }, - execute::fingerprint::TrackedEnvQuery, -}; - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Structured types (Serialize + Deserialize for JSON persistence) -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -/// Saved summary of a task runner execution. -/// -/// Persisted as `last-summary.json` in the cache directory. -#[derive(Serialize, Deserialize)] -pub struct LastRunSummary { - pub tasks: Vec, - pub exit_code: u8, -} - -/// Summary for a single task execution. -/// -/// All fields are structured data — no pre-formatted display strings. -/// Formatting (including color decisions) happens at render time. -#[derive(Serialize, Deserialize)] -pub struct TaskSummary { - pub package_name: Str, - pub task_name: Str, - /// Raw command text (e.g., "vitest run"). - pub command: Str, - /// Working directory relative to workspace root (e.g., "packages/lib"). - /// Empty string when the cwd is the workspace root. - pub cwd: Str, - /// Combined cache status and execution outcome. - pub result: TaskResult, -} - -/// The complete result of a task execution. -/// -/// Encodes both the cache status and execution outcome in a single enum, -/// making invalid combinations unrepresentable. -#[derive(Serialize, Deserialize)] -pub enum TaskResult { - /// Cache hit — output was replayed from cache. Always successful. - CacheHit { saved_duration_ms: u64 }, - - /// In-process execution (built-in command like echo). Always successful. - InProcess, - - /// A process was spawned. - Spawned { - /// Why the process was spawned (cache miss or cache disabled). - cache_status: SpawnedCacheStatus, - outcome: SpawnOutcome, - }, -} - -/// Cache status for tasks that required spawning a process. -/// -/// Only two cache statuses lead to spawning: -/// - `Miss`: cache lookup found no match or a mismatch. -/// - `Disabled`: no cache configuration for this task. -/// -/// `Hit` and `InProcessExecution` are handled by [`TaskResult::CacheHit`] -/// and [`TaskResult::InProcess`] respectively. -#[derive(Serialize, Deserialize)] -pub enum SpawnedCacheStatus { - Miss(SavedCacheMissReason), - /// No cache configuration for this task. - Disabled, -} - -/// Outcome of a spawned process. -#[derive(Serialize, Deserialize)] -pub enum SpawnOutcome { - /// Process exited successfully (exit code 0). - /// May have a post-execution infrastructure error (cache update or fingerprint failed). - /// These only run after exit 0, so this field only exists on the success path. - Success { - infra_error: Option, - /// First path that was both read and written, causing cache to be skipped. - /// Only set when fspy detected a read-write overlap. - input_modified_path: Option, - /// `true` when the task required fspy auto-inference but the binary was - /// built without `cfg(fspy)` (e.g., cross-compiled to an unsupported OS). - /// Task ran successfully but cache was not updated. - #[serde(default)] - fspy_unsupported: bool, - /// Rendered message of the IPC server error that caused the cache to - /// be skipped, if any. - ipc_server_error: Option, - /// Set when a runner-aware tool called `disableCache()`, skipping - /// cache update. - tool_disabled_cache: bool, - }, - - /// Process exited with non-zero status. - /// [`NonZeroI32`] enforces that exit code 0 is unrepresentable here. - /// No `infra_error` field: cache operations are skipped on non-zero exit. - Failed { exit_code: NonZeroI32 }, - - /// Execution failed without a usable process exit status. - SpawnError(SavedExecutionError), -} - -/// Why a cache miss occurred. -#[derive(Serialize, Deserialize)] -pub enum SavedCacheMissReason { - /// No previous cache entry for this task. - NotFound, - /// Spawn fingerprint changed (command, envs, cwd, etc.). - SpawnFingerprintChanged(Vec), - /// Task configuration changed (`input_config` or `glob_base`). - ConfigChanged, - /// An input file or folder changed. - InputChanged { kind: InputChangeKind, path: Str }, - /// A runner-aware tool reported a tracked env var that changed between runs. - TrackedEnvChanged(EnvMismatch), - /// A runner-aware tool reported a tracked bulk env query whose match-set changed - /// between runs. Carries the first differing entry. - TrackedEnvQueryChanged { query: TrackedEnvQuery, mismatch: EnvMismatch }, -} - -/// An execution error, serializable for persistence. -/// -/// The `message` field contains the raw inner error text (from `anyhow::Error`). -/// The error prefix (e.g., "Cache update failed") is derived from the variant -/// at render time. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum SavedExecutionError { - Cache { kind: SavedCacheErrorKind, message: Str }, - Spawn { message: Str }, - ForwardTaskProcessOutput { message: Str }, - WaitForTaskProcessExit { message: Str }, - PostRunFingerprint { message: Str }, - IpcServerBind { message: Str }, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum SavedCacheErrorKind { - Lookup, - Update, -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Computed stats (derived from tasks, not persisted) -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -struct SummaryStats { - total: usize, - cache_hits: usize, - cache_misses: usize, - cache_disabled: usize, - failed: usize, - total_saved: Duration, - /// Display names of tasks that were not cached due to read-write overlap. - input_modified_task_names: Vec, -} - -impl SummaryStats { - fn compute(tasks: &[TaskSummary]) -> Self { - let mut stats = Self { - total: tasks.len(), - cache_hits: 0, - cache_misses: 0, - cache_disabled: 0, - failed: 0, - total_saved: Duration::ZERO, - input_modified_task_names: Vec::new(), - }; - - for task in tasks { - match &task.result { - TaskResult::CacheHit { saved_duration_ms } => { - stats.cache_hits += 1; - stats.total_saved += Duration::from_millis(*saved_duration_ms); - } - TaskResult::InProcess => { - stats.cache_disabled += 1; - } - TaskResult::Spawned { cache_status, outcome } => { - match cache_status { - SpawnedCacheStatus::Miss(_) => stats.cache_misses += 1, - SpawnedCacheStatus::Disabled => stats.cache_disabled += 1, - } - match outcome { - SpawnOutcome::Success { infra_error: Some(_), .. } - | SpawnOutcome::Failed { .. } - | SpawnOutcome::SpawnError(_) => stats.failed += 1, - SpawnOutcome::Success { input_modified_path: Some(_), .. } => { - stats.input_modified_task_names.push(task.format_task_display()); - } - SpawnOutcome::Success { .. } => {} - } - } - } - } - - stats - } -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Conversion from live execution data -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -impl SavedExecutionError { - /// Convert a live [`ExecutionError`] into a serializable error. - pub fn from_execution_error(error: &ExecutionError) -> Self { - match error { - ExecutionError::Cache { kind, source } => Self::Cache { - kind: match kind { - CacheErrorKind::Lookup => SavedCacheErrorKind::Lookup, - CacheErrorKind::Update => SavedCacheErrorKind::Update, - }, - message: vite_str::format!("{source:#}"), - }, - ExecutionError::Spawn(source) => { - Self::Spawn { message: vite_str::format!("{source:#}") } - } - ExecutionError::ForwardTaskProcessOutput(source) => { - Self::ForwardTaskProcessOutput { message: vite_str::format!("{source:#}") } - } - ExecutionError::WaitForTaskProcessExit(source) => { - Self::WaitForTaskProcessExit { message: vite_str::format!("{source:#}") } - } - ExecutionError::PostRunFingerprint(source) => { - Self::PostRunFingerprint { message: vite_str::format!("{source:#}") } - } - ExecutionError::IpcServerBind(source) => { - Self::IpcServerBind { message: vite_str::format!("{source:#}") } - } - } - } - - /// Format the full error message for display. - fn display_message(&self) -> Str { - match self { - Self::Cache { kind, message } => { - let kind_str = match kind { - SavedCacheErrorKind::Lookup => "lookup", - SavedCacheErrorKind::Update => "update", - }; - vite_str::format!("Cache {kind_str} failed: {message}") - } - Self::Spawn { message } => { - vite_str::format!("Failed to spawn process: {message}") - } - Self::ForwardTaskProcessOutput { message } => { - vite_str::format!("Failed to forward task process output: {message}") - } - Self::WaitForTaskProcessExit { message } => { - vite_str::format!("Failed to wait for task process to exit: {message}") - } - Self::PostRunFingerprint { message } => { - vite_str::format!("Failed to create post-run fingerprint: {message}") - } - Self::IpcServerBind { message } => { - vite_str::format!("Failed to set up task communication: {message}") - } - } - } -} - -impl SavedCacheMissReason { - fn from_cache_miss(cache_miss: &CacheMiss) -> Self { - match cache_miss { - CacheMiss::NotFound => Self::NotFound, - CacheMiss::FingerprintMismatch(mismatch) => match mismatch { - FingerprintMismatch::SpawnFingerprint { old, new } => { - Self::SpawnFingerprintChanged(detect_spawn_fingerprint_changes(old, new)) - } - FingerprintMismatch::InputConfig | FingerprintMismatch::OutputConfig => { - Self::ConfigChanged - } - FingerprintMismatch::InputChanged { kind, path } => { - Self::InputChanged { kind: *kind, path: Str::from(path.as_str()) } - } - FingerprintMismatch::TrackedEnvChanged(mismatch) => { - Self::TrackedEnvChanged(mismatch.clone()) - } - FingerprintMismatch::TrackedEnvQueryChanged { query, mismatch } => { - Self::TrackedEnvQueryChanged { - query: query.clone(), - mismatch: mismatch.clone(), - } - } - }, - } - } -} - -impl TaskResult { - /// Build a [`TaskResult`] from live execution data. - /// - /// `cache_status`: the cache status determined at `start()` time. - /// `exit_status`: the process exit status, or `None` for cache hit / in-process. - /// `saved_error`: an optional pre-converted execution error. - /// `cache_update_status`: the post-execution cache update result. - pub fn from_execution( - cache_status: &CacheStatus, - exit_status: Option, - saved_error: Option<&SavedExecutionError>, - cache_update_status: &CacheUpdateStatus, - ) -> Self { - let input_modified_path = match cache_update_status { - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::InputModified { path }) => { - Some(Str::from(path.as_str())) - } - _ => None, - }; - let fspy_unsupported = matches!( - cache_update_status, - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::FspyUnsupported) - ); - let ipc_server_error = match cache_update_status { - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::IpcServerError(err)) => { - Some(vite_str::format!("{err}")) - } - _ => None, - }; - let tool_disabled_cache = matches!( - cache_update_status, - CacheUpdateStatus::NotUpdated(CacheNotUpdatedReason::ToolRequested) - ); - - match cache_status { - CacheStatus::Hit { replayed_duration } => { - Self::CacheHit { saved_duration_ms: duration_to_ms(*replayed_duration) } - } - CacheStatus::Disabled(CacheDisabledReason::InProcessExecution) => Self::InProcess, - CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata) => Self::Spawned { - cache_status: SpawnedCacheStatus::Disabled, - outcome: spawn_outcome_from_execution( - exit_status, - saved_error, - input_modified_path, - fspy_unsupported, - ipc_server_error, - tool_disabled_cache, - ), - }, - CacheStatus::Miss(cache_miss) => Self::Spawned { - cache_status: SpawnedCacheStatus::Miss(SavedCacheMissReason::from_cache_miss( - cache_miss, - )), - outcome: spawn_outcome_from_execution( - exit_status, - saved_error, - input_modified_path, - fspy_unsupported, - ipc_server_error, - tool_disabled_cache, - ), - }, - } - } -} - -/// Build a [`SpawnOutcome`] from process exit status and optional pre-converted error. -fn spawn_outcome_from_execution( - exit_status: Option, - saved_error: Option<&SavedExecutionError>, - input_modified_path: Option, - fspy_unsupported: bool, - ipc_server_error: Option, - tool_disabled_cache: bool, -) -> SpawnOutcome { - match (exit_status, saved_error) { - // Spawn error — process never ran - (None, Some(err)) => SpawnOutcome::SpawnError(err.clone()), - // Process exited successfully, possible infra error - (Some(status), _) if status.success() => SpawnOutcome::Success { - infra_error: saved_error.cloned(), - input_modified_path, - fspy_unsupported, - ipc_server_error, - tool_disabled_cache, - }, - // Process exited with non-zero code - (Some(status), _) => { - let code = crate::session::event::exit_status_to_code(status); - SpawnOutcome::Failed { - // exit_status_to_code returns 1..=255 for failed processes (see its - // implementation: always positive, non-zero for non-success status). - // NonZeroI32::new returns None only for 0, which cannot happen here. - exit_code: NonZeroI32::new(code).unwrap_or(NonZeroI32::MIN), - } - } - // No exit status, no error — this is the cache hit / in-process path, - // handled by TaskResult::CacheHit / InProcess before reaching here. - // If we somehow get here, treat as success. - (None, None) => SpawnOutcome::Success { - infra_error: None, - input_modified_path: None, - fspy_unsupported: false, - ipc_server_error: None, - tool_disabled_cache: false, - }, - } -} - -#[expect( - clippy::cast_possible_truncation, - reason = "value is clamped to u64::MAX before casting, so no data loss" -)] -fn duration_to_ms(d: Duration) -> u64 { - d.as_millis().min(u128::from(u64::MAX)) as u64 -} - -fn format_summary_duration(d: Duration) -> Str { - let formatted = vite_str::format!("{d:.2?}"); - - for (suffix, unit) in - [(".00ms", "ms"), (".00s", "s"), (".00us", "us"), (".00µs", "µs"), (".00ns", "ns")] - { - if let Some(prefix) = formatted.as_str().strip_suffix(suffix) { - return vite_str::format!("{prefix}{unit}"); - } - } - - formatted -} - -impl LastRunSummary { - // ── Persistence ────────────────────────────────────────────────────── - - /// Write the summary as JSON atomically (write to `.tmp`, then rename). - /// - /// Errors are returned to the caller (the reporter logs them without propagating). - #[expect( - clippy::disallowed_types, - reason = "PathBuf is needed to construct a temporary path by appending .tmp suffix; \ - AbsolutePathBuf cannot be constructed without validation" - )] - pub fn write_atomic(&self, path: &AbsolutePath) -> std::io::Result<()> { - let json = serde_json::to_vec(self).map_err(std::io::Error::other)?; - - let mut tmp_os = path.as_path().as_os_str().to_owned(); - tmp_os.push(".tmp"); - let tmp_path = std::path::PathBuf::from(tmp_os); - std::fs::write(&tmp_path, &json)?; - std::fs::rename(&tmp_path, path)?; - Ok(()) - } - - /// Read a summary from a JSON file. - /// - /// Returns `Ok(None)` if the file does not exist. - /// Returns `Err` on parse or IO errors (caller provides version mismatch message). - pub fn read_from_path(path: &AbsolutePath) -> Result, ReadSummaryError> { - let bytes = match std::fs::read(path) { - Ok(bytes) => bytes, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), - Err(err) => return Err(ReadSummaryError::Io(err)), - }; - let summary = - serde_json::from_slice(&bytes).map_err(|_| ReadSummaryError::IncompatibleVersion)?; - Ok(Some(summary)) - } -} - -/// Error type for [`LastRunSummary::read_from_path`]. -pub enum ReadSummaryError { - Io(std::io::Error), - /// The JSON could not be parsed — likely saved by a different version. - IncompatibleVersion, -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Display helpers for TaskResult -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -impl TaskSummary { - /// Format the task display name (e.g., "package#task" or "task"). - fn format_task_display(&self) -> Str { - if self.package_name.is_empty() { - self.task_name.clone() - } else { - vite_str::format!("{}#{}", self.package_name, self.task_name) - } - } - - /// Format the command with cwd prefix (e.g., "~/packages/lib$ vitest run"). - fn format_command_display(&self) -> Str { - if self.cwd.is_empty() { - vite_str::format!("$ {}", self.command) - } else { - vite_str::format!("~/{cwd}$ {cmd}", cwd = self.cwd, cmd = self.command) - } - } -} - -impl TaskResult { - /// Whether this task succeeded (used for exit icon rendering). - const fn is_success(&self) -> bool { - match self { - Self::CacheHit { .. } | Self::InProcess => true, - Self::Spawned { outcome, .. } => matches!(outcome, SpawnOutcome::Success { .. }), - } - } - - /// Format the cache status detail line for the full summary. - /// - /// Examples: - /// - "→ Cache hit - output replayed - 102.96ms saved" - /// - "→ Cache miss: no previous cache entry found" - /// - "→ Cache disabled in task configuration" - fn format_cache_detail(&self) -> Str { - // Check for IPC server error first — short-circuits before any cache - // computation in `execute_spawn`, so it takes priority. - if let Self::Spawned { - outcome: SpawnOutcome::Success { ipc_server_error: Some(err), .. }, - .. - } = self - { - return vite_str::format!("→ Not cached: task communication failed ({err})"); - } - - // Tool-reported cache disable — the tool said it shouldn't be cached. - if let Self::Spawned { - outcome: SpawnOutcome::Success { tool_disabled_cache: true, .. }, - .. - } = self - { - return Str::from("→ Not cached: the task opted out of caching"); - } - - // Check for input modification next — it overrides the cache miss reason - if let Self::Spawned { - outcome: SpawnOutcome::Success { input_modified_path: Some(path), .. }, - .. - } = self - { - return vite_str::format!("→ Not cached: read and wrote '{path}'"); - } - // fspy-unsupported-on-this-OS message — same overrides precedence as above - if let Self::Spawned { - outcome: SpawnOutcome::Success { fspy_unsupported: true, .. }, .. - } = self - { - return Str::from( - "→ Not cached: `input` auto-inference isn't supported on this OS. Configure `input` manually to enable caching.", - ); - } - - match self { - Self::CacheHit { saved_duration_ms } => { - let d = Duration::from_millis(*saved_duration_ms); - let formatted_duration = format_summary_duration(d); - vite_str::format!("→ Cache hit - output replayed - {formatted_duration} saved") - } - Self::InProcess => Str::from("→ Cache disabled for built-in command"), - Self::Spawned { cache_status, .. } => match cache_status { - SpawnedCacheStatus::Disabled => Str::from("→ Cache disabled in task configuration"), - SpawnedCacheStatus::Miss(reason) => match reason { - SavedCacheMissReason::NotFound => { - Str::from("→ Cache miss: no previous cache entry found") - } - SavedCacheMissReason::SpawnFingerprintChanged(changes) => { - let formatted: Vec = changes.iter().map(format_spawn_change).collect(); - if formatted.is_empty() { - Str::from("→ Cache miss: configuration changed") - } else { - let joined = - formatted.iter().map(Str::as_str).collect::>().join("; "); - vite_str::format!("→ Cache miss: {joined}") - } - } - SavedCacheMissReason::ConfigChanged => { - Str::from("→ Cache miss: input configuration changed") - } - SavedCacheMissReason::InputChanged { kind, path } => { - let desc = format_input_change_str(*kind, path.as_str()); - vite_str::format!("→ Cache miss: {desc}") - } - SavedCacheMissReason::TrackedEnvChanged(mismatch) - | SavedCacheMissReason::TrackedEnvQueryChanged { mismatch, .. } => { - vite_str::format!("→ Cache miss: {mismatch}") - } - }, - }, - } - } - - /// The [`Style`] for the cache detail line. - const fn cache_detail_style(&self) -> Style { - match self { - Self::CacheHit { .. } => Style::new().green(), - Self::InProcess => Style::new().bright_black(), - Self::Spawned { cache_status: SpawnedCacheStatus::Disabled, .. } => { - Style::new().bright_black() - } - Self::Spawned { cache_status: SpawnedCacheStatus::Miss(_), .. } => CACHE_MISS_STYLE, - } - } - - /// Optional error associated with this result. - pub const fn error(&self) -> Option<&SavedExecutionError> { - match self { - Self::CacheHit { .. } | Self::InProcess => None, - Self::Spawned { outcome, .. } => match outcome { - SpawnOutcome::Success { infra_error, .. } => infra_error.as_ref(), - SpawnOutcome::Failed { .. } => None, - SpawnOutcome::SpawnError(err) => Some(err), - }, - } - } -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Full summary rendering (--verbose and --last-details) -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -/// Render the full detailed execution summary. -/// -/// Used by both `--verbose` (live) and `--last-details` (from file). -#[expect( - clippy::too_many_lines, - reason = "summary formatting is inherently verbose with many write calls" -)] -pub fn format_full_summary(summary: &LastRunSummary) -> Vec { - let mut buf = Vec::new(); - - let stats = SummaryStats::compute(&summary.tasks); - - // Header - let _ = writeln!(buf); - let _ = writeln!( - buf, - "{}", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".style(Style::new().bright_black()) - ); - let _ = writeln!( - buf, - "{}", - " Vite+ Task Runner • Execution Summary".style(Style::new().bold().bright_white()) - ); - let _ = writeln!( - buf, - "{}", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".style(Style::new().bright_black()) - ); - let _ = writeln!(buf); - - // Statistics line - let cache_disabled_str = if stats.cache_disabled > 0 { - let n = stats.cache_disabled; - Str::from( - vite_str::format!("• {n} cache disabled") - .style(Style::new().bright_black()) - .to_string(), - ) - } else { - Str::default() - }; - - let failed_str = if stats.failed > 0 { - let n = stats.failed; - Str::from(vite_str::format!("• {n} failed").style(Style::new().red()).to_string()) - } else { - Str::default() - }; - - let total = stats.total; - let cache_hits = stats.cache_hits; - let cache_misses = stats.cache_misses; - let _ = write!( - buf, - "{} {} {} {}", - "Statistics:".style(Style::new().bold()), - vite_str::format!(" {total} tasks").style(Style::new().bright_white()), - vite_str::format!("• {cache_hits} cache hits").style(Style::new().green()), - vite_str::format!("• {cache_misses} cache misses").style(CACHE_MISS_STYLE), - ); - if !cache_disabled_str.is_empty() { - let _ = write!(buf, " {cache_disabled_str}"); - } - if !failed_str.is_empty() { - let _ = write!(buf, " {failed_str}"); - } - let _ = writeln!(buf); - - // Performance line - #[expect( - clippy::cast_possible_truncation, - reason = "percentage is always 0..=100, fits in u32" - )] - #[expect(clippy::cast_sign_loss, reason = "percentage is always non-negative")] - #[expect( - clippy::cast_precision_loss, - reason = "acceptable precision loss for display percentage" - )] - let cache_rate = if total > 0 { (cache_hits as f64 / total as f64 * 100.0) as u32 } else { 0 }; - - let _ = write!( - buf, - "{} {} cache hit rate", - "Performance:".style(Style::new().bold()), - format_args!("{cache_rate}%").style(if cache_rate >= 75 { - Style::new().green().bold() - } else if cache_rate >= 50 { - CACHE_MISS_STYLE - } else { - Style::new().red() - }) - ); - - if stats.total_saved > Duration::ZERO { - let formatted_total_saved = format_summary_duration(stats.total_saved); - let _ = write!( - buf, - ", {} saved in total", - formatted_total_saved.style(Style::new().green().bold()) - ); - } - let _ = writeln!(buf); - let _ = writeln!(buf); - - // Task Details - let _ = writeln!(buf, "{}", "Task Details:".style(Style::new().bold())); - let _ = writeln!( - buf, - "{}", - "────────────────────────────────────────────────".style(Style::new().bright_black()) - ); - - for (idx, task) in summary.tasks.iter().enumerate() { - // Task index and name - let _ = write!( - buf, - " {} {}", - vite_str::format!("[{}]", idx + 1).style(Style::new().bright_black()), - task.format_task_display().to_string().style(Style::new().bright_white().bold()) - ); - - // Command with cwd prefix - let _ = write!(buf, ": {}", task.format_command_display().style(COMMAND_STYLE)); - - // Exit icon - if task.result.is_success() { - let _ = write!(buf, " {}", "✓".style(Style::new().green().bold())); - } else if let TaskResult::Spawned { outcome: SpawnOutcome::Failed { exit_code }, .. } = - &task.result - { - let code = exit_code.get(); - let _ = write!( - buf, - " {} {}", - "✗".style(Style::new().red().bold()), - vite_str::format!("(exit code: {code})").style(Style::new().red()) - ); - } - let _ = writeln!(buf); - - // Cache status detail - let cache_detail = task.result.format_cache_detail(); - let _ = writeln!(buf, " {}", cache_detail.style(task.result.cache_detail_style())); - - // Error message if present - if let Some(err) = task.result.error() { - let msg = err.display_message(); - let _ = writeln!( - buf, - " {} {}", - "✗ Error:".style(Style::new().red().bold()), - msg.style(Style::new().red()) - ); - } - - // Separator between tasks (except last) - if idx < summary.tasks.len() - 1 { - let _ = writeln!( - buf, - " {}", - "·······················································" - .style(Style::new().bright_black()) - ); - } - } - - let _ = writeln!( - buf, - "{}", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".style(Style::new().bright_black()) - ); - - buf -} - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Compact summary rendering (default mode) -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -/// Render a compact summary (one-liner or empty). -/// -/// Rules: -/// - Single task + not cache hit → empty (no summary at all) -/// - Single task + cache hit → thin line + "vp run: cache hit, {duration} saved." -/// - Multi-task → thin line + "vp run: {hits}/{total} cache hit ({rate}%), {duration} saved." -/// with optional failure count and `--verbose` hint. -pub fn format_compact_summary(summary: &LastRunSummary, program_name: &str) -> Vec { - let stats = SummaryStats::compute(&summary.tasks); - - let is_single_task = summary.tasks.len() == 1; - - // Single task + not cache hit + no input modification → no summary - if is_single_task && stats.cache_hits == 0 && stats.input_modified_task_names.is_empty() { - return Vec::new(); - } - - let mut buf = Vec::new(); - - // Thin line separator - let _ = writeln!(buf, "{}", "---".style(Style::new().bright_black())); - - let run_label = vite_str::format!("{program_name} run:"); - let mut show_last_details_hint = true; - if is_single_task && stats.cache_hits > 0 { - // Single task cache hit — no need for --last-details hint - let formatted_total_saved = format_summary_duration(stats.total_saved); - let _ = write!( - buf, - "{} cache hit, {} saved.", - run_label.as_str().style(Style::new().blue().bold()), - formatted_total_saved.style(Style::new().green().bold()), - ); - show_last_details_hint = false; - } else if !is_single_task { - // Multi-task - let total = stats.total; - let hits = stats.cache_hits; - - #[expect( - clippy::cast_possible_truncation, - reason = "percentage is always 0..=100, fits in u32" - )] - #[expect(clippy::cast_sign_loss, reason = "percentage is always non-negative")] - #[expect( - clippy::cast_precision_loss, - reason = "acceptable precision loss for display percentage" - )] - let rate = if total > 0 { (hits as f64 / total as f64 * 100.0) as u32 } else { 0 }; - - let _ = write!( - buf, - "{} {hits}/{total} cache hit ({rate}%)", - run_label.as_str().style(Style::new().blue().bold()), - ); - - if stats.total_saved > Duration::ZERO { - let formatted_total_saved = format_summary_duration(stats.total_saved); - let _ = write!( - buf, - ", {} saved", - formatted_total_saved.style(Style::new().green().bold()), - ); - } - - if stats.failed > 0 { - let n = stats.failed; - let _ = write!(buf, ", {} failed", n.style(Style::new().red())); - } - - let _ = write!(buf, "."); - } else { - // Single task, no cache hit — only shown when input_modified is non-empty - let _ = write!(buf, "{}", run_label.as_str().style(Style::new().blue().bold())); - } - - // Inline input-modified notice before the --last-details hint - if !stats.input_modified_task_names.is_empty() { - format_input_modified_notice(&mut buf, &stats.input_modified_task_names); - } - - if show_last_details_hint { - let last_details_cmd = vite_str::format!("`{program_name} run --last-details`"); - let _ = write!(buf, " {}", "(Run ".style(Style::new().bright_black())); - let _ = write!(buf, "{}", last_details_cmd.as_str().style(COMMAND_STYLE)); - let _ = write!(buf, "{}", " for full details)".style(Style::new().bright_black())); - } - let _ = writeln!(buf); - - buf -} - -/// Write the "not cached because it modified its input" notice inline. -fn format_input_modified_notice(buf: &mut Vec, task_names: &[Str]) { - let _ = write!(buf, " "); - - let first = &task_names[0]; - let _ = write!(buf, "{}", first.as_str().style(Style::new().bold())); - let remaining = task_names.len() - 1; - if remaining > 0 { - let _ = write!(buf, " (and {remaining} more)"); - } - - if task_names.len() == 1 { - let _ = write!(buf, " not cached because it modified its input."); - } else { - let _ = write!(buf, " not cached because they modified their inputs."); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn output_forwarding_error_has_distinct_message() { - let error = ExecutionError::ForwardTaskProcessOutput(anyhow::anyhow!( - "Resource temporarily unavailable (os error 11)" - )); - - let saved = SavedExecutionError::from_execution_error(&error); - - assert!(matches!( - &saved, - SavedExecutionError::ForwardTaskProcessOutput { message } - if message.as_str() == "Resource temporarily unavailable (os error 11)" - )); - assert_eq!( - saved.display_message().as_str(), - "Failed to forward task process output: Resource temporarily unavailable (os error 11)" - ); - } -} diff --git a/crates/vite_task/src/session/reporter/summary_reporter.rs b/crates/vite_task/src/session/reporter/summary_reporter.rs deleted file mode 100644 index 789bc29a2..000000000 --- a/crates/vite_task/src/session/reporter/summary_reporter.rs +++ /dev/null @@ -1,209 +0,0 @@ -//! Summary reporter — wraps an inner reporter and adds summary tracking. -//! -//! This is a decorator that intercepts leaf `start()`/`finish()` to track task -//! results, then renders a summary when the graph execution completes. The inner -//! reporter handles all output formatting (interleaved, labeled, grouped). - -use std::{cell::RefCell, io::Write, process::ExitStatus as StdExitStatus, rc::Rc, sync::Arc}; - -use vite_path::AbsolutePath; -use vite_str::Str; -use vite_task_plan::{ExecutionItemDisplay, LeafExecutionKind}; - -use super::{ - ColorSupport, ExitStatus, GraphExecutionReporter, GraphExecutionReporterBuilder, - LeafExecutionReporter, StdioConfig, -}; -use crate::session::{ - event::{CacheStatus, CacheUpdateStatus, ExecutionError}, - reporter::summary::{ - LastRunSummary, SavedExecutionError, SpawnOutcome, TaskResult, TaskSummary, - format_compact_summary, format_full_summary, - }, -}; - -/// Callback type for persisting the summary (e.g., writing `last-summary.json`). -pub type WriteSummaryFn = Box; - -/// Builder that wraps an inner builder and adds summary tracking. -pub struct SummaryReporterBuilder { - inner: Box, - workspace_path: Arc, - writer: Box, - show_details: bool, - write_summary: Option, - program_name: Str, -} - -impl SummaryReporterBuilder { - /// `writer` is the summary output stream. The wrapped inner builder - /// owns per-stream stripping of the child-process pipe writers; the - /// reporter's own summary text picks colour-vs-plain at format time - /// via `ColorizeExt`, so `writer` is stored unwrapped. - pub fn new( - inner: Box, - workspace_path: Arc, - writer: Box, - show_details: bool, - write_summary: Option, - program_name: Str, - _color_support: ColorSupport, - ) -> Self { - Self { inner, workspace_path, writer, show_details, write_summary, program_name } - } -} - -impl GraphExecutionReporterBuilder for SummaryReporterBuilder { - fn build(self: Box) -> Box { - Box::new(SummaryGraphReporter { - inner: self.inner.build(), - tasks: Rc::new(RefCell::new(Vec::new())), - workspace_path: self.workspace_path, - writer: self.writer, - show_details: self.show_details, - write_summary: self.write_summary, - program_name: self.program_name, - }) - } -} - -struct SummaryGraphReporter { - inner: Box, - tasks: Rc>>, - workspace_path: Arc, - writer: Box, - show_details: bool, - write_summary: Option, - program_name: Str, -} - -impl GraphExecutionReporter for SummaryGraphReporter { - fn new_leaf_execution( - &mut self, - display: &ExecutionItemDisplay, - leaf_kind: &LeafExecutionKind, - ) -> Box { - let inner = self.inner.new_leaf_execution(display, leaf_kind); - Box::new(SummaryLeafReporter { - inner, - tasks: Rc::clone(&self.tasks), - display: display.clone(), - workspace_path: Arc::clone(&self.workspace_path), - cache_status: None, - }) - } - - fn finish(self: Box) -> Result<(), ExitStatus> { - // Let inner reporter finish first (flushes any pending output). - let inner_result = self.inner.finish(); - - let tasks = self.tasks.take(); - - let has_infra_errors = tasks.iter().any(|t| t.result.error().is_some()); - - let failed_exit_codes: Vec = tasks - .iter() - .filter_map(|t| match &t.result { - TaskResult::Spawned { outcome: SpawnOutcome::Failed { exit_code }, .. } => { - Some(exit_code.get()) - } - _ => None, - }) - .collect(); - - let result = match (has_infra_errors, failed_exit_codes.as_slice()) { - (false, []) => Ok(()), - (false, [code]) => - { - #[expect( - clippy::cast_sign_loss, - reason = "value is clamped to 1..=255, always positive" - )] - Err(ExitStatus((*code).clamp(1, 255) as u8)) - } - _ => Err(ExitStatus::FAILURE), - }; - - let exit_code = match &result { - Ok(()) => 0u8, - Err(status) => status.0, - }; - - let summary = LastRunSummary { tasks, exit_code }; - - let summary_buf = if self.show_details { - format_full_summary(&summary) - } else { - format_compact_summary(&summary, &self.program_name) - }; - - if let Some(write_summary) = self.write_summary { - write_summary(&summary); - } - - // Always flush — even when summary is empty, a preceding spawned process - // may have written to the same fd via Stdio::inherit(). - { - let mut writer = self.writer; - if !summary_buf.is_empty() { - let _ = writer.write_all(&summary_buf); - } - let _ = writer.flush(); - } - - // Use inner result if it failed, otherwise use our computed result. - inner_result.and(result) - } -} - -/// Leaf reporter wrapper that records task results for the summary. -struct SummaryLeafReporter { - inner: Box, - tasks: Rc>>, - display: ExecutionItemDisplay, - workspace_path: Arc, - cache_status: Option, -} - -impl LeafExecutionReporter for SummaryLeafReporter { - fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { - self.cache_status = Some(cache_status.clone()); - self.inner.start(cache_status) - } - - fn finish( - self: Box, - status: Option, - cache_update_status: CacheUpdateStatus, - error: Option, - ) { - // Record task summary before forwarding to inner. - let saved_error = error.as_ref().map(SavedExecutionError::from_execution_error); - - if let Some(ref cache_status) = self.cache_status { - let cwd_relative = - if let Ok(Some(rel)) = self.display.cwd.strip_prefix(&self.workspace_path) { - Str::from(rel.as_str()) - } else { - Str::default() - }; - - let task_summary = TaskSummary { - package_name: self.display.task_display.package_name.clone(), - task_name: self.display.task_display.task_name.clone(), - command: self.display.command.clone(), - cwd: cwd_relative, - result: TaskResult::from_execution( - cache_status, - status, - saved_error.as_ref(), - &cache_update_status, - ), - }; - - self.tasks.borrow_mut().push(task_summary); - } - - self.inner.finish(status, cache_update_status, error); - } -} diff --git a/crates/vite_task_bin/Cargo.toml b/crates/vite_task_bin/Cargo.toml deleted file mode 100644 index 3d179de0d..000000000 --- a/crates/vite_task_bin/Cargo.toml +++ /dev/null @@ -1,71 +0,0 @@ -[package] -name = "vite_task_bin" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[[bin]] -name = "vt" -path = "src/main.rs" - -[[bin]] -name = "vtt" -path = "src/vtt/main.rs" - -[dependencies] -anyhow = { workspace = true } -ctrlc = { workspace = true } -libc = { workspace = true } -notify = { workspace = true } -pty_terminal_test_client = { workspace = true } -async-trait = { workspace = true } -clap = { workspace = true, features = ["derive"] } -jsonc-parser = { workspace = true } -serde_json = { workspace = true } -tokio = { workspace = true, features = ["full"] } -vite_path = { workspace = true } -vite_str = { workspace = true } -vite_task = { workspace = true } -which = { workspace = true } - -[target.'cfg(target_os = "linux")'.dependencies] -nix = { workspace = true, features = ["mount", "sched", "user"] } - -[dev-dependencies] -cow-utils = { workspace = true } -cp_r = { workspace = true } -libtest-mimic = { workspace = true } -snapshot_test = { workspace = true } -pty_terminal = { workspace = true } -pty_terminal_test = { workspace = true } -pty_terminal_test_client = { workspace = true, features = ["testing"] } -regex = { workspace = true } -serde = { workspace = true, features = ["derive", "rc"] } -shell-escape = { workspace = true } -tempfile = { workspace = true } -toml = { workspace = true } -vec1 = { workspace = true, features = ["serde"] } -vite_path = { workspace = true, features = ["absolute-redaction"] } -vite_workspace = { workspace = true } - -[target.'cfg(target_os = "linux")'.dev-dependencies] -# Artifact dep: the cdylib is built and its path is exposed to the e2e -# test harness via `CARGO_CDYLIB_FILE_PRELOAD_TEST_LIB` at test-runtime. -preload_test_lib = { path = "../preload_test_lib", artifact = "cdylib" } - -[package.metadata.cargo-shear] -ignored = ["preload_test_lib"] - -[lints] -workspace = true - -[[test]] -name = "e2e_snapshots" -harness = false - -[lib] -test = false -doctest = false diff --git a/crates/vite_task_bin/src/lib.rs b/crates/vite_task_bin/src/lib.rs deleted file mode 100644 index 43ccd08d1..000000000 --- a/crates/vite_task_bin/src/lib.rs +++ /dev/null @@ -1,155 +0,0 @@ -use std::{ - env::{self, join_paths}, - ffi::OsStr, - iter, - sync::Arc, -}; - -use clap::Parser; -use vite_path::AbsolutePath; -use vite_str::Str; -use vite_task::{ - Command, EnabledCacheConfig, HandledCommand, ScriptCommand, SessionConfig, UserCacheConfig, - get_path_env, plan_request::SyntheticPlanRequest, -}; - -#[derive(Debug, Default)] -pub struct CommandHandler(()); - -/// Find an executable in `node_modules/.bin` directories up the tree. -/// -/// # Errors -/// -/// Returns an error if the executable cannot be found in any searched path. -pub fn find_executable( - path_env: Option<&Arc>, - cwd: &AbsolutePath, - executable: &str, -) -> anyhow::Result> { - #[expect( - clippy::disallowed_types, - reason = "PathBuf required by env::split_paths and which::which_in APIs" - )] - let mut paths: Vec = - path_env.map_or_else(Vec::new, |path_env| env::split_paths(path_env).collect()); - let mut current_cwd_parent = cwd; - loop { - let node_modules_bin = current_cwd_parent.join("node_modules").join(".bin"); - paths.push(node_modules_bin.as_path().to_path_buf()); - if let Some(parent) = current_cwd_parent.parent() { - current_cwd_parent = parent; - } else { - break; - } - } - let executable_path = which::which_in(executable, Some(join_paths(paths)?), cwd)?; - Ok(executable_path.into_os_string().into()) -} - -/// Internal argument parser for `vt`/`vp` commands that appear inside task scripts. -/// -/// [`CommandHandler`] uses this to parse the command line when it intercepts a `vt` or `vp` -/// invocation during script execution. It extends [`Command`] with a `tool` subcommand that -/// forwards to the `vtt` test-utility binary — a subcommand that only makes sense within -/// script execution and is therefore not exposed on the top-level `vt` CLI entry point. -#[derive(Debug, Parser)] -#[command(name = "vt", version)] -enum Args { - /// Forward arguments to the `vtt` test-utility binary. - /// - /// Resolves `vtt` via `node_modules/.bin` lookup (same as any other script executable), - /// then synthesizes a cached invocation with the given arguments. The `--` separator, - /// if present, is stripped before forwarding. - Tool { - #[clap(trailing_var_arg = true, allow_hyphen_values = true)] - args: Vec, - }, - /// Any other `vt` subcommand, delegated to the standard [`Command`] parser. - #[command(flatten)] - Task(Command), -} - -#[async_trait::async_trait(?Send)] -impl vite_task::CommandHandler for CommandHandler { - async fn handle_command( - &mut self, - command: &mut ScriptCommand, - ) -> anyhow::Result { - match command.program.as_str() { - "vt" | "vp" => {} - // `vpr ` is shorthand for `vt run ` - "vpr" => { - command.program = Str::from("vt"); - command.args = - iter::once(Str::from("run")).chain(command.args.iter().cloned()).collect(); - } - _ => return Ok(HandledCommand::Verbatim), - } - let args = Args::try_parse_from( - std::iter::once(command.program.as_str()).chain(command.args.iter().map(Str::as_str)), - )?; - match args { - Args::Tool { args } => { - let program = find_executable(get_path_env(&command.envs), &command.cwd, "vtt")?; - Ok(HandledCommand::Synthesized(SyntheticPlanRequest { - program, - args: args.into_iter().filter(|a| a.as_str() != "--").collect(), - cache_config: UserCacheConfig::with_config(EnabledCacheConfig { - env: None, - untracked_env: None, - input: None, - output: None, - }), - envs: Arc::clone(&command.envs), - })) - } - Args::Task(parsed) => Ok(HandledCommand::ViteTaskCommand(parsed)), - } - } -} - -/// A `UserConfigLoader` implementation that only loads `vite-task.json`. -/// -/// This is mainly for examples and testing as it does not require Node.js environment. -#[derive(Default, Debug)] -pub struct JsonUserConfigLoader(()); - -#[async_trait::async_trait(?Send)] -impl vite_task::loader::UserConfigLoader for JsonUserConfigLoader { - async fn load_user_config_file( - &self, - package_path: &AbsolutePath, - ) -> anyhow::Result> { - let config_path = package_path.join("vite-task.json"); - let config_content = match tokio::fs::read_to_string(&config_path).await { - Ok(content) => content, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - return Ok(None); - } - Err(err) => return Err(err.into()), - }; - let json_value: Option = jsonc_parser::parse_to_serde_value( - &config_content, - &jsonc_parser::ParseOptions::default(), - )?; - let user_config: vite_task::config::UserRunConfig = - serde_json::from_value(json_value.unwrap_or_default())?; - Ok(Some(user_config)) - } -} - -#[derive(Default)] -pub struct OwnedSessionConfig { - command_handler: CommandHandler, - user_config_loader: JsonUserConfigLoader, -} - -impl OwnedSessionConfig { - pub fn as_config(&mut self) -> SessionConfig<'_> { - SessionConfig { - command_handler: &mut self.command_handler, - user_config_loader: &mut self.user_config_loader, - program_name: Str::from("vt"), - } - } -} diff --git a/crates/vite_task_bin/src/main.rs b/crates/vite_task_bin/src/main.rs deleted file mode 100644 index e1599059d..000000000 --- a/crates/vite_task_bin/src/main.rs +++ /dev/null @@ -1,23 +0,0 @@ -use clap::Parser as _; -use vite_task::{Command, ExitStatus, Session}; -use vite_task_bin::OwnedSessionConfig; - -fn main() -> ! { - let status: ExitStatus = - tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(run()); - - std::process::exit(i32::from(status.0)); -} - -async fn run() -> ExitStatus { - let args = Command::parse(); - let mut owned_config = OwnedSessionConfig::default(); - let session = match Session::init(owned_config.as_config()) { - Ok(session) => session, - Err(err) => { - vite_task::print_error(&err); - return ExitStatus::FAILURE; - } - }; - session.main(args).await -} diff --git a/crates/vite_task_bin/src/vtt/barrier.rs b/crates/vite_task_bin/src/vtt/barrier.rs deleted file mode 100644 index c79db0279..000000000 --- a/crates/vite_task_bin/src/vtt/barrier.rs +++ /dev/null @@ -1,89 +0,0 @@ -/// barrier `` `` `` \[--exit=``\] \[--hang\] \[--daemonize\] -/// -/// Cross-platform concurrency barrier for testing. -/// Creates `/_`, then polls until `` files matching -/// `_*` exist in ``. -/// -/// Options: -/// - `--exit=`: Exit with the given code after the barrier is met. -/// - `--hang`: Keep process alive after the barrier (for kill tests). -/// - `--daemonize`: Close stdout/stderr but keep process alive (for daemon kill tests). -pub fn run(args: &[String]) -> Result<(), Box> { - use notify::Watcher as _; - let mut positional: Vec<&str> = Vec::new(); - let mut exit_code: i32 = 0; - let mut hang = false; - let mut daemonize = false; - - for arg in args { - if let Some(code) = arg.strip_prefix("--exit=") { - exit_code = code.parse()?; - } else if arg == "--hang" { - hang = true; - } else if arg == "--daemonize" { - daemonize = true; - } else { - positional.push(arg.as_str()); - } - } - - if positional.len() < 3 { - return Err( - "Usage: vtt barrier [--exit=] [--hang] [--daemonize]" - .into(), - ); - } - - let dir = std::path::Path::new(positional[0]); - let prefix = positional[1]; - let count: usize = positional[2].parse()?; - - std::fs::create_dir_all(dir)?; - - // Create this participant's marker file. - let pid = std::process::id(); - let marker = dir.join(std::format!("{prefix}_{pid}")); - std::fs::write(&marker, "")?; - - // Wait until matching files exist using filesystem notifications. - let prefix_match = std::format!("{prefix}_"); - let count_matches = |d: &std::path::Path| -> Result> { - Ok(std::fs::read_dir(d)? - .filter_map(Result::ok) - .filter(|e| e.file_name().to_string_lossy().starts_with(prefix_match.as_str())) - .count() - >= count) - }; - let (tx, rx) = std::sync::mpsc::channel(); - let mut watcher = notify::recommended_watcher(tx)?; - watcher.watch(dir, notify::RecursiveMode::NonRecursive)?; - if !count_matches(dir)? { - for _ in rx { - if count_matches(dir)? { - break; - } - } - } - - if daemonize { - // Close stdout/stderr but keep the process alive. Simulates a daemon that - // detaches from stdio — tests that the runner can still kill such processes. - // Closing the fds gives the parent's pipe an EOF. - // SAFETY: fds 1 and 2 are always valid (stdout/stderr). - unsafe { - libc::close(1); - libc::close(2); - } - loop { - std::thread::park(); - } - } - - if hang { - loop { - std::thread::park(); - } - } - - std::process::exit(exit_code); -} diff --git a/crates/vite_task_bin/src/vtt/check_tty.rs b/crates/vite_task_bin/src/vtt/check_tty.rs deleted file mode 100644 index 0c3f0a731..000000000 --- a/crates/vite_task_bin/src/vtt/check_tty.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub fn run() { - use std::io::IsTerminal as _; - let stdin_tty = if std::io::stdin().is_terminal() { "tty" } else { "not-tty" }; - let stdout_tty = if std::io::stdout().is_terminal() { "tty" } else { "not-tty" }; - let stderr_tty = if std::io::stderr().is_terminal() { "tty" } else { "not-tty" }; - println!("stdin:{stdin_tty}"); - println!("stdout:{stdout_tty}"); - println!("stderr:{stderr_tty}"); -} diff --git a/crates/vite_task_bin/src/vtt/cp.rs b/crates/vite_task_bin/src/vtt/cp.rs deleted file mode 100644 index 36ed0a934..000000000 --- a/crates/vite_task_bin/src/vtt/cp.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub fn run(args: &[String]) -> Result<(), Box> { - let (recursive, paths) = match args { - [flag, src, dst] if flag == "-r" => (true, [src.as_str(), dst.as_str()]), - [src, dst] => (false, [src.as_str(), dst.as_str()]), - _ => return Err("Usage: vtt cp [-r] ".into()), - }; - - let src = std::path::Path::new(paths[0]); - let dst = std::path::Path::new(paths[1]); - if src.is_dir() { - if !recursive { - return Err("copying a directory requires -r".into()); - } - copy_dir_recursive(src, dst)?; - } else { - std::fs::copy(src, dst)?; - } - Ok(()) -} - -fn copy_dir_recursive( - src: &std::path::Path, - dst: &std::path::Path, -) -> Result<(), Box> { - std::fs::create_dir_all(dst)?; - for entry in std::fs::read_dir(src)? { - let entry = entry?; - let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); - let file_type = entry.file_type()?; - if file_type.is_dir() { - copy_dir_recursive(&src_path, &dst_path)?; - } else if file_type.is_file() { - std::fs::copy(&src_path, &dst_path)?; - } - } - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/exit.rs b/crates/vite_task_bin/src/vtt/exit.rs deleted file mode 100644 index eb33608ac..000000000 --- a/crates/vite_task_bin/src/vtt/exit.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub fn run(args: &[String]) -> Result<(), Box> { - let code: i32 = args.first().map(|s| s.parse()).transpose()?.unwrap_or(0); - std::process::exit(code); -} diff --git a/crates/vite_task_bin/src/vtt/exit_on_ctrlc.rs b/crates/vite_task_bin/src/vtt/exit_on_ctrlc.rs deleted file mode 100644 index 3e13f8f65..000000000 --- a/crates/vite_task_bin/src/vtt/exit_on_ctrlc.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::sync::Arc; - -/// exit-on-ctrlc -/// -/// Sets up a Ctrl+C handler, emits a "ready" milestone, then waits. -/// When Ctrl+C is received, prints "ctrl-c received" and exits. -pub fn run() -> Result<(), Box> { - // On Windows, an ancestor process (e.g. cargo, the test harness) may have - // been created with CREATE_NEW_PROCESS_GROUP, which implicitly calls - // SetConsoleCtrlHandler(NULL, TRUE) and sets CONSOLE_IGNORE_CTRL_C in the - // PEB's ConsoleFlags. This flag is inherited by all descendants and takes - // precedence over registered handlers — CTRL_C_EVENT is silently dropped. - // Clear it so our handler can fire. - // Ref: https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags - #[cfg(windows)] - { - // SAFETY: Passing (None, FALSE) clears the per-process CTRL_C ignore flag. - unsafe extern "system" { - fn SetConsoleCtrlHandler( - handler: Option i32>, - add: i32, - ) -> i32; - } - // SAFETY: Clearing the inherited ignore flag. - unsafe { - SetConsoleCtrlHandler(None, 0); - } - } - - let ctrlc_once_lock = Arc::new(std::sync::OnceLock::<()>::new()); - - ctrlc::set_handler({ - let ctrlc_once_lock = Arc::clone(&ctrlc_once_lock); - move || { - let _ = ctrlc_once_lock.set(()); - } - })?; - - pty_terminal_test_client::mark_milestone("ready"); - - ctrlc_once_lock.wait(); - println!("ctrl-c received"); - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/grep_file.rs b/crates/vite_task_bin/src/vtt/grep_file.rs deleted file mode 100644 index 50b3281b3..000000000 --- a/crates/vite_task_bin/src/vtt/grep_file.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub fn run(args: &[String]) { - let [path, pattern] = args else { - eprintln!("Usage: vtt grep-file "); - std::process::exit(2); - }; - match std::fs::read_to_string(path) { - Ok(content) => { - if content.contains(pattern.as_str()) { - println!("{path}: found {pattern:?}"); - } else { - println!("{path}: missing {pattern:?}"); - } - } - Err(_) => println!("{path}: not found"), - } -} diff --git a/crates/vite_task_bin/src/vtt/list_dir.rs b/crates/vite_task_bin/src/vtt/list_dir.rs deleted file mode 100644 index 0005c21f0..000000000 --- a/crates/vite_task_bin/src/vtt/list_dir.rs +++ /dev/null @@ -1,69 +0,0 @@ -/// List entries in a directory, sorted by name, one per line. -/// -/// Usage: `vtt list-dir [--ext ] [--recursive]` -/// -/// With `--ext`, only entries whose filename ends with the given suffix are -/// printed (the leading `.` is part of the suffix you pass, e.g. `.tar.zst`). -/// -/// With `--recursive`, subdirectories are walked and only their leaf files are -/// printed (by basename, not path). This lets tests assert on cache contents -/// without hardcoding the per-schema-version subdirectory (e.g. `v13`) that the -/// cache database and archives live under, which changes whenever the cache -/// schema version is bumped. -/// -/// Used by e2e tests to assert on cache directory contents (e.g. exactly one -/// `.tar.zst` archive after a re-run that should have cleaned up the prior -/// archive). -pub fn run(args: &[String]) -> Result<(), Box> { - let mut dir: Option<&str> = None; - let mut ext: Option<&str> = None; - let mut recursive = false; - let mut i = 0; - while i < args.len() { - match args[i].as_str() { - "--ext" => { - i += 1; - ext = Some(args.get(i).ok_or("--ext requires a value")?.as_str()); - } - "--recursive" => recursive = true, - other if dir.is_none() => dir = Some(other), - other => return Err(format!("unexpected argument: {other}").into()), - } - i += 1; - } - let dir = dir.ok_or("Usage: vtt list-dir [--ext ] [--recursive]")?; - - let mut names: Vec = Vec::new(); - collect(std::path::Path::new(dir), ext, recursive, &mut names)?; - names.sort(); - for name in names { - println!("{name}"); - } - Ok(()) -} - -/// Collect entry filenames under `dir`. In recursive mode, descend into -/// subdirectories and collect only leaf files (the directory names themselves -/// are not emitted). -fn collect( - dir: &std::path::Path, - ext: Option<&str>, - recursive: bool, - names: &mut Vec, -) -> Result<(), Box> { - for entry in std::fs::read_dir(dir)? { - let entry = entry?; - if recursive && entry.file_type()?.is_dir() { - collect(&entry.path(), ext, recursive, names)?; - continue; - } - let name = entry.file_name().to_string_lossy().into_owned(); - if let Some(suffix) = ext - && !name.ends_with(suffix) - { - continue; - } - names.push(name); - } - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/main.rs b/crates/vite_task_bin/src/vtt/main.rs deleted file mode 100644 index 9e3c7f6fb..000000000 --- a/crates/vite_task_bin/src/vtt/main.rs +++ /dev/null @@ -1,91 +0,0 @@ -// This is a standalone test utility binary that deliberately uses std types -// rather than the project's custom types (vite_str, vite_path, etc.). -#![expect(clippy::disallowed_types, reason = "standalone test utility uses std types")] -#![expect(clippy::disallowed_macros, reason = "standalone test utility uses std macros")] -#![expect(clippy::disallowed_methods, reason = "standalone test utility uses std methods")] -#![expect(clippy::print_stderr, reason = "CLI tool error output")] -#![expect(clippy::print_stdout, reason = "CLI tool output")] - -mod barrier; -mod check_tty; -mod cp; -mod exit; -mod exit_on_ctrlc; -mod grep_file; -mod list_dir; -mod mkdir; -mod pipe_stdin; -mod print; -mod print_color; -mod print_cwd; -mod print_env; -mod print_file; -mod read_stdin; -mod replace_file_content; -mod rm; -#[cfg(target_os = "linux")] -mod small_dev_shm; -mod stat_file; -mod stat_long_filename; -mod touch_file; -mod write_file; - -fn main() { - let args: Vec = std::env::args().collect(); - if args.len() < 2 { - eprintln!("Usage: vtt [args...]"); - eprintln!( - "Subcommands: barrier, check-tty, cp, exit, exit-on-ctrlc, grep-file, list-dir, mkdir, pipe-stdin, print, print-color, print-cwd, print-env, print-file, read-stdin, replace-file-content, rm, small_dev_shm, stat-file, stat_long_filename, touch-file, write-file" - ); - std::process::exit(1); - } - - let result: Result<(), Box> = match args[1].as_str() { - "barrier" => barrier::run(&args[2..]), - "check-tty" => { - check_tty::run(); - Ok(()) - } - "cp" => cp::run(&args[2..]), - "exit" => exit::run(&args[2..]), - "exit-on-ctrlc" => exit_on_ctrlc::run(), - "grep-file" => { - grep_file::run(&args[2..]); - Ok(()) - } - "list-dir" => list_dir::run(&args[2..]), - "mkdir" => mkdir::run(&args[2..]), - "pipe-stdin" => pipe_stdin::run(&args[2..]), - "print" => { - print::run(&args[2..]); - Ok(()) - } - "print-color" => print_color::run(&args[2..]), - "print-cwd" => print_cwd::run(), - "print-env" => print_env::run(&args[2..]), - "print-file" => print_file::run(&args[2..]), - "read-stdin" => read_stdin::run(), - "replace-file-content" => replace_file_content::run(&args[2..]), - "rm" => rm::run(&args[2..]), - #[cfg(target_os = "linux")] - "small_dev_shm" => small_dev_shm::run(&args[2..]).map_err(Into::into), - #[cfg(not(target_os = "linux"))] - "small_dev_shm" => Err("vtt small_dev_shm is only supported on Linux".into()), - "stat-file" => { - stat_file::run(&args[2..]); - Ok(()) - } - "stat_long_filename" => stat_long_filename::run(&args[2..]), - "touch-file" => touch_file::run(&args[2..]), - "write-file" => write_file::run(&args[2..]), - other => { - eprintln!("Unknown subcommand: {other}"); - std::process::exit(1); - } - }; - - if let Err(err) = result { - eprintln!("{err}"); - std::process::exit(1); - } -} diff --git a/crates/vite_task_bin/src/vtt/mkdir.rs b/crates/vite_task_bin/src/vtt/mkdir.rs deleted file mode 100644 index efbafc453..000000000 --- a/crates/vite_task_bin/src/vtt/mkdir.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub fn run(args: &[String]) -> Result<(), Box> { - let mut parents = false; - let mut paths = Vec::new(); - for arg in args { - match arg.as_str() { - "-p" => parents = true, - _ => paths.push(arg.as_str()), - } - } - if paths.is_empty() { - return Err("Usage: vtt mkdir [-p] ...".into()); - } - for path in paths { - if parents { - std::fs::create_dir_all(path)?; - } else { - std::fs::create_dir(path)?; - } - } - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/pipe_stdin.rs b/crates/vite_task_bin/src/vtt/pipe_stdin.rs deleted file mode 100644 index 92f6e6f64..000000000 --- a/crates/vite_task_bin/src/vtt/pipe_stdin.rs +++ /dev/null @@ -1,33 +0,0 @@ -/// pipe-stdin `` -- `` \[``...\] -/// -/// Spawns `` with `` piped to its stdin, then exits with -/// the child's exit code. If `` is empty, an empty stdin is provided. -pub fn run(args: &[String]) -> Result<(), Box> { - let sep = args - .iter() - .position(|a| a == "--") - .ok_or("Usage: vtt pipe-stdin -- [args...]")?; - let data = &args[..sep].join(" "); - let cmd_args = &args[sep + 1..]; - if cmd_args.is_empty() { - return Err("Usage: vtt pipe-stdin -- [args...]".into()); - } - - let mut child = std::process::Command::new(&cmd_args[0]) - .args(&cmd_args[1..]) - .stdin(std::process::Stdio::piped()) - .spawn()?; - - { - use std::io::Write; - let mut stdin = child.stdin.take().unwrap(); - if !data.is_empty() { - stdin.write_all(data.as_bytes())?; - } - stdin.write_all(b"\n")?; - // stdin is closed when dropped, signaling EOF - } - - let status = child.wait()?; - std::process::exit(status.code().unwrap_or(1)); -} diff --git a/crates/vite_task_bin/src/vtt/print.rs b/crates/vite_task_bin/src/vtt/print.rs deleted file mode 100644 index e434e6cf8..000000000 --- a/crates/vite_task_bin/src/vtt/print.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub fn run(args: &[String]) { - println!("{}", args.join(" ")); -} diff --git a/crates/vite_task_bin/src/vtt/print_color.rs b/crates/vite_task_bin/src/vtt/print_color.rs deleted file mode 100644 index 47641247f..000000000 --- a/crates/vite_task_bin/src/vtt/print_color.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! `vtt print-color ...` — prints `text` wrapped in an ANSI SGR -//! escape sequence when `FORCE_COLOR` is set to a non-zero value, otherwise -//! prints plain text. Used by e2e tests to verify color-env handling. - -pub fn run(args: &[String]) -> Result<(), Box> { - if args.len() < 2 { - return Err("Usage: vtt print-color ...".into()); - } - let color = args[0].as_str(); - let text = args[1..].join(" "); - - let force_color = std::env::var("FORCE_COLOR").ok(); - let want_color = match force_color.as_deref() { - Some("" | "0") | None => false, - Some(_) => true, - }; - - let code: u8 = match color { - "red" => 31, - "green" => 32, - "yellow" => 33, - "blue" => 34, - "magenta" => 35, - "cyan" => 36, - other => return Err(format!("Unknown color: {other}").into()), - }; - - if want_color { - println!("\x1b[{code}m{text}\x1b[0m"); - } else { - println!("{text}"); - } - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/print_cwd.rs b/crates/vite_task_bin/src/vtt/print_cwd.rs deleted file mode 100644 index 55db04cb8..000000000 --- a/crates/vite_task_bin/src/vtt/print_cwd.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub fn run() -> Result<(), Box> { - let cwd = std::env::current_dir()?; - println!("{}", cwd.display()); - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/print_env.rs b/crates/vite_task_bin/src/vtt/print_env.rs deleted file mode 100644 index 00387b464..000000000 --- a/crates/vite_task_bin/src/vtt/print_env.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub fn run(args: &[String]) -> Result<(), Box> { - if args.is_empty() { - return Err("Usage: vtt print-env ".into()); - } - let value = std::env::var(&args[0]).unwrap_or_else(|_| "(undefined)".to_string()); - println!("{value}"); - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/print_file.rs b/crates/vite_task_bin/src/vtt/print_file.rs deleted file mode 100644 index 8234dd3c2..000000000 --- a/crates/vite_task_bin/src/vtt/print_file.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub fn run(args: &[String]) -> Result<(), Box> { - use std::io::Write as _; - let stdout = std::io::stdout(); - let mut out = stdout.lock(); - for file in args { - match std::fs::read(file) { - Ok(content) => out.write_all(&content)?, - Err(_) => eprintln!("{file}: not found"), - } - } - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/read_stdin.rs b/crates/vite_task_bin/src/vtt/read_stdin.rs deleted file mode 100644 index 1072df52c..000000000 --- a/crates/vite_task_bin/src/vtt/read_stdin.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub fn run() -> Result<(), Box> { - use std::io::{Read as _, Write as _}; - let mut stdin = std::io::stdin().lock(); - let mut stdout = std::io::stdout().lock(); - let mut buf = [0u8; 8192]; - loop { - match stdin.read(&mut buf) { - Ok(0) | Err(_) => break, - Ok(n) => stdout.write_all(&buf[..n])?, - } - } - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/replace_file_content.rs b/crates/vite_task_bin/src/vtt/replace_file_content.rs deleted file mode 100644 index df868e95c..000000000 --- a/crates/vite_task_bin/src/vtt/replace_file_content.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub fn run(args: &[String]) -> Result<(), Box> { - if args.len() < 3 { - return Err("Usage: vtt replace-file-content ".into()); - } - let filename = &args[0]; - let search_value = &args[1]; - let new_value = &args[2]; - - let filepath = std::path::Path::new(filename).canonicalize()?; - let content = std::fs::read_to_string(&filepath)?; - if !content.contains(search_value) { - return Err(std::format!("searchValue not found in {filename}: {search_value:?}").into()); - } - let new_content = content.replacen(search_value, new_value, 1); - std::fs::write(&filepath, new_content)?; - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/rm.rs b/crates/vite_task_bin/src/vtt/rm.rs deleted file mode 100644 index a8f98d6c0..000000000 --- a/crates/vite_task_bin/src/vtt/rm.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub fn run(args: &[String]) -> Result<(), Box> { - let mut recursive = false; - let mut paths = Vec::new(); - for arg in args { - match arg.as_str() { - "-r" | "-rf" | "-f" => recursive = true, - _ => paths.push(arg.as_str()), - } - } - if paths.is_empty() { - return Err("Usage: vtt rm [-rf] ...".into()); - } - for path in paths { - let p = std::path::Path::new(path); - if p.is_dir() && recursive { - std::fs::remove_dir_all(p)?; - } else { - std::fs::remove_file(p)?; - } - } - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/small_dev_shm.rs b/crates/vite_task_bin/src/vtt/small_dev_shm.rs deleted file mode 100644 index d859b692a..000000000 --- a/crates/vite_task_bin/src/vtt/small_dev_shm.rs +++ /dev/null @@ -1,58 +0,0 @@ -#![cfg(target_os = "linux")] - -use std::{os::unix::process::ExitStatusExt as _, process::Command}; - -use anyhow::{Context as _, Result}; -use nix::{ - mount::{MsFlags, mount}, - sched::{CloneFlags, unshare}, - unistd::{Gid, Uid}, -}; - -const USAGE: &str = "Usage: vtt small_dev_shm [args...]"; - -pub fn run(args: &[String]) -> Result<()> { - let (program, command_args) = parse_command(args)?; - run_platform(program, command_args) -} - -fn parse_command(args: &[String]) -> Result<(&str, &[String])> { - args.split_first().map(|(program, args)| (program.as_str(), args)).context(USAGE) -} - -fn run_platform(program: &str, command_args: &[String]) -> Result<()> { - let uid = Uid::current().as_raw(); - let gid = Gid::current().as_raw(); - - unshare(CloneFlags::CLONE_NEWUSER | CloneFlags::CLONE_NEWNS) - .context("unshare user and mount namespaces")?; - - std::fs::write("/proc/self/uid_map", format!("0 {uid} 1\n")) - .context("write /proc/self/uid_map")?; - match std::fs::write("/proc/self/setgroups", "deny") { - Ok(()) => {} - Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} - Err(error) => return Err(error).context("write /proc/self/setgroups"), - } - std::fs::write("/proc/self/gid_map", format!("0 {gid} 1\n")) - .context("write /proc/self/gid_map")?; - - mount(None::<&str>, "/", None::<&str>, MsFlags::MS_REC | MsFlags::MS_PRIVATE, None::<&str>) - .context("make / recursively private")?; - - mount( - Some("tmpfs"), - "/dev/shm", - Some("tmpfs"), - MsFlags::empty(), - Some("nr_blocks=1,huge=never"), - ) - .context("mount one-page tmpfs at /dev/shm")?; - - let status = Command::new(program) - .args(command_args) - .status() - .context("run command with constrained /dev/shm")?; - let code = status.code().unwrap_or_else(|| status.signal().map_or(1, |signal| 128 + signal)); - std::process::exit(code); -} diff --git a/crates/vite_task_bin/src/vtt/stat_file.rs b/crates/vite_task_bin/src/vtt/stat_file.rs deleted file mode 100644 index 75fbebf4c..000000000 --- a/crates/vite_task_bin/src/vtt/stat_file.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub fn run(args: &[String]) { - for file in args { - if std::fs::metadata(file).is_ok() { - println!("{file}: exists"); - } else { - println!("{file}: missing"); - } - } -} diff --git a/crates/vite_task_bin/src/vtt/stat_long_filename.rs b/crates/vite_task_bin/src/vtt/stat_long_filename.rs deleted file mode 100644 index 1b44b1453..000000000 --- a/crates/vite_task_bin/src/vtt/stat_long_filename.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::{error::Error, io}; - -const USAGE: &str = "Usage: vtt stat_long_filename "; - -pub fn run(args: &[String]) -> Result<(), Box> { - let count = parse_count(args)?; - access_generated_path(count, metadata)?; - Ok(()) -} - -fn parse_count(args: &[String]) -> Result { - let [count] = args else { return Err(USAGE.to_owned()) }; - count.parse().map_err(|_| USAGE.to_owned()) -} - -fn generated_path(count: usize) -> String { - "x".repeat(count) -} - -fn access_generated_path( - count: usize, - mut metadata: impl FnMut(&str) -> io::Result<()>, -) -> io::Result<()> { - let path = generated_path(count); - match metadata(&path) { - Ok(()) => Ok(()), - Err(error) - if error.kind() == io::ErrorKind::NotFound - || error.raw_os_error() == Some(libc::ENAMETOOLONG) => - { - Ok(()) - } - Err(error) => Err(error), - } -} - -fn metadata(path: &str) -> io::Result<()> { - std::fs::metadata(path).map(|_| ()) -} diff --git a/crates/vite_task_bin/src/vtt/touch_file.rs b/crates/vite_task_bin/src/vtt/touch_file.rs deleted file mode 100644 index 96d2393bd..000000000 --- a/crates/vite_task_bin/src/vtt/touch_file.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub fn run(args: &[String]) -> Result<(), Box> { - if args.is_empty() { - return Err("Usage: vtt touch-file ".into()); - } - let _file = std::fs::OpenOptions::new().read(true).write(true).open(&args[0])?; - Ok(()) -} diff --git a/crates/vite_task_bin/src/vtt/write_file.rs b/crates/vite_task_bin/src/vtt/write_file.rs deleted file mode 100644 index b1562bd79..000000000 --- a/crates/vite_task_bin/src/vtt/write_file.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub fn run(args: &[String]) -> Result<(), Box> { - if args.len() < 2 { - return Err("Usage: vtt write-file ".into()); - } - let path = std::path::Path::new(&args[0]); - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent)?; - } - std::fs::write(path, &args[1])?; - Ok(()) -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/README.md b/crates/vite_task_bin/tests/e2e_snapshots/README.md deleted file mode 100644 index 27f592b5a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# E2E Snapshot Tests - -End-to-end tests that execute the `vp` binary and verify its output. - -## When to add tests here - -- Testing CLI behavior and output formatting -- Testing cache hit/miss behavior across multiple command invocations -- Testing error messages shown to users -- Testing integration between multiple commands in sequence - -## How it works - -Each fixture in `fixtures/` is a self-contained workspace. Tests are defined in `snapshots.toml`: - -```toml -[[e2e]] -name = "descriptive test name" -steps = [ - "vp run build", - "vp run build", # second run to test caching -] -``` - -Steps also support an object form with interactions: - -```toml -[[e2e]] -name = "interactive step" -steps = [ - { command = "vp interact", interactions = [{ expect-milestone = "ready" }, { write = "hello" }, { write-line = "world" }, { write-key = "up" }, { write-key = "down" }, { write-key = "enter" }] }, - "echo -n | node check-stdin.js", -] -``` - -Notes: - -- String steps are shorthand for `{ command = "..." }`. -- `write-key` accepts `up`, `down`, and `enter`. -- Snapshots include every interaction line, and each `expect-milestone` records the screen at that point. -- For stdin pipe scenarios, write the step command with shell piping, for example: `echo -n | command`. - -The test runner: - -1. Copies the fixture to a temp directory -2. Executes each step using `/bin/sh` (Unix) or `bash` (Windows) -3. Runs each step in PTY mode (`TestTerminal`) -4. Applies configured interactions in order for PTY steps -5. Captures output and exit codes -6. Compares against snapshot in `fixtures//snapshots/` - -## Adding a new test - -1. Create a new fixture directory under `fixtures/` -2. Add `package.json` (and `pnpm-workspace.yaml` for monorepos) -3. Add `snapshots.toml` with test cases -4. Run `cargo test -p vite_task_bin --test e2e_snapshots` -5. Review and accept new snapshots diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/package.json deleted file mode 100644 index 601a7860b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "scripts": { - "script1": "vtt print hello", - "script2": "vtt print hello" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/snapshots.toml deleted file mode 100644 index a61a2b393..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/snapshots.toml +++ /dev/null @@ -1,29 +0,0 @@ -[[e2e]] -name = "associate_existing_cache" -comment = """ -Tests that tasks with identical commands share cache -""" -steps = [ - { argv = [ - "vt", - "run", - "script1", - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "script2", - ], comment = "cache hit, same command as script1" }, - { argv = [ - "vtt", - "replace-file-content", - "package.json", - "\"script2\": \"vtt print hello\"", - "\"script2\": \"vtt print world\"", - ], comment = "change script2" }, - { argv = [ - "vt", - "run", - "script2", - ], comment = "cache miss" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/snapshots/associate_existing_cache.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/snapshots/associate_existing_cache.md deleted file mode 100644 index 4f7e88a3f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/snapshots/associate_existing_cache.md +++ /dev/null @@ -1,40 +0,0 @@ -# associate_existing_cache - -Tests that tasks with identical commands share cache - -## `vt run script1` - -cache miss - -``` -$ vtt print hello -hello -``` - -## `vt run script2` - -cache hit, same command as script1 - -``` -$ vtt print hello ◉ cache hit, replaying -hello - ---- -vt run: cache hit. -``` - -## `vtt replace-file-content package.json '"script2": "vtt print hello"' '"script2": "vtt print world"'` - -change script2 - -``` -``` - -## `vt run script2` - -cache miss - -``` -$ vtt print world ○ cache miss: args changed, executing -world -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/associate_existing_cache/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/folder1/.gitkeep b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/folder1/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/folder2/.gitkeep b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/folder2/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/package.json deleted file mode 100644 index 0bb387b0c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "scripts": { - "cwd1": "cd folder1 && vt tool print-cwd", - "cwd2": "cd folder2 && vt tool print-cwd" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/snapshots.toml deleted file mode 100644 index be4491c97..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/snapshots.toml +++ /dev/null @@ -1,27 +0,0 @@ -[[e2e]] -name = "builtin_different_cwd" -comment = """ -Tests that synthetic tasks have separate cache per cwd -""" -steps = [ - { argv = [ - "vt", - "run", - "cwd1", - ], comment = "cache miss in folder1" }, - { argv = [ - "vt", - "run", - "cwd2", - ], comment = "cache miss in folder2 (different cwd)" }, - { argv = [ - "vt", - "run", - "cwd1", - ], comment = "cache hit in folder1" }, - { argv = [ - "vt", - "run", - "cwd2", - ], comment = "cache hit in folder2" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/snapshots/builtin_different_cwd.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/snapshots/builtin_different_cwd.md deleted file mode 100644 index 65f3414d9..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/snapshots/builtin_different_cwd.md +++ /dev/null @@ -1,45 +0,0 @@ -# builtin_different_cwd - -Tests that synthetic tasks have separate cache per cwd - -## `vt run cwd1` - -cache miss in folder1 - -``` -~/folder1$ vt tool print-cwd -/folder1 -``` - -## `vt run cwd2` - -cache miss in folder2 (different cwd) - -``` -~/folder2$ vt tool print-cwd -/folder2 -``` - -## `vt run cwd1` - -cache hit in folder1 - -``` -~/folder1$ vt tool print-cwd ◉ cache hit, replaying -/folder1 - ---- -vt run: cache hit. -``` - -## `vt run cwd2` - -cache hit in folder2 - -``` -~/folder2$ vt tool print-cwd ◉ cache hit, replaying -/folder2 - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/builtin_different_cwd/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/package.json deleted file mode 100644 index 1005cf301..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "cache-disabled-test" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/snapshots.toml deleted file mode 100644 index 8649e9927..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/snapshots.toml +++ /dev/null @@ -1,35 +0,0 @@ -[[e2e]] -name = "task_with_cache_disabled" -comment = """ -A task configured with `cache: false` should re-run on every invocation instead of being cached. -""" -steps = [ - { argv = [ - "vt", - "run", - "no-cache-task", - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "no-cache-task", - ], comment = "cache disabled, runs again" }, -] - -[[e2e]] -name = "task_with_cache_enabled" -comment = """ -A task with caching enabled should produce a cache hit on the second run. -""" -steps = [ - { argv = [ - "vt", - "run", - "cached-task", - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "cached-task", - ], comment = "cache hit" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/snapshots/task_with_cache_disabled.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/snapshots/task_with_cache_disabled.md deleted file mode 100644 index 6384ed825..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/snapshots/task_with_cache_disabled.md +++ /dev/null @@ -1,21 +0,0 @@ -# task_with_cache_disabled - -A task configured with `cache: false` should re-run on every invocation instead of being cached. - -## `vt run no-cache-task` - -cache miss - -``` -$ vtt print-file test.txt ⊘ cache disabled -test content -``` - -## `vt run no-cache-task` - -cache disabled, runs again - -``` -$ vtt print-file test.txt ⊘ cache disabled -test content -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/snapshots/task_with_cache_enabled.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/snapshots/task_with_cache_enabled.md deleted file mode 100644 index 065a2a6a8..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/snapshots/task_with_cache_enabled.md +++ /dev/null @@ -1,24 +0,0 @@ -# task_with_cache_enabled - -A task with caching enabled should produce a cache hit on the second run. - -## `vt run cached-task` - -cache miss - -``` -$ vtt print-file test.txt -test content -``` - -## `vt run cached-task` - -cache hit - -``` -$ vtt print-file test.txt ◉ cache hit, replaying -test content - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/test.txt b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/test.txt deleted file mode 100644 index d670460b4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/test.txt +++ /dev/null @@ -1 +0,0 @@ -test content diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/vite-task.json deleted file mode 100644 index 38fffec43..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_disabled/vite-task.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "cache": true, - "tasks": { - "no-cache-task": { - "command": "vtt print-file test.txt", - "cache": false - }, - "cached-task": { - "command": "vtt print-file test.txt", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/package.json deleted file mode 100644 index 0967ef424..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/package.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/snapshots.toml deleted file mode 100644 index 70a515154..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/snapshots.toml +++ /dev/null @@ -1,36 +0,0 @@ -[[e2e]] -name = "cache_miss_command_change" -comment = """ -Tests cache behavior when command changes partially -""" -steps = [ - { argv = [ - "vt", - "run", - "task", - ], comment = "cache miss" }, - { argv = [ - "vtt", - "replace-file-content", - "vite-task.json", - "vtt print foo && vtt print bar", - "vtt print baz && vtt print bar", - ], comment = "change first subtask" }, - { argv = [ - "vt", - "run", - "task", - ], comment = "first: cache miss, second: cache hit" }, - { argv = [ - "vtt", - "replace-file-content", - "vite-task.json", - "vtt print baz && vtt print bar", - "vtt print bar", - ], comment = "remove first subtask" }, - { argv = [ - "vt", - "run", - "task", - ], comment = "cache hit" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/snapshots/cache_miss_command_change.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/snapshots/cache_miss_command_change.md deleted file mode 100644 index 03b0c92aa..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/snapshots/cache_miss_command_change.md +++ /dev/null @@ -1,59 +0,0 @@ -# cache_miss_command_change - -Tests cache behavior when command changes partially - -## `vt run task` - -cache miss - -``` -$ vtt print foo -foo - -$ vtt print bar -bar - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` - -## `vtt replace-file-content vite-task.json 'vtt print foo && vtt print bar' 'vtt print baz && vtt print bar'` - -change first subtask - -``` -``` - -## `vt run task` - -first: cache miss, second: cache hit - -``` -$ vtt print baz ○ cache miss: args changed, executing -baz - -$ vtt print bar ◉ cache hit, replaying -bar - ---- -vt run: 1/2 cache hit (50%). (Run `vt run --last-details` for full details) -``` - -## `vtt replace-file-content vite-task.json 'vtt print baz && vtt print bar' 'vtt print bar'` - -remove first subtask - -``` -``` - -## `vt run task` - -cache hit - -``` -$ vtt print bar ◉ cache hit, replaying -bar - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/vite-task.json deleted file mode 100644 index ffb999e77..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_command_change/vite-task.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tasks": { - "task": { - "command": "vtt print foo && vtt print bar" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/extra.txt b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/extra.txt deleted file mode 100644 index c6ae2b1bb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/extra.txt +++ /dev/null @@ -1 +0,0 @@ -extra content diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/package.json deleted file mode 100644 index 58ac7e939..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "cache-miss-reasons", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots.toml deleted file mode 100644 index c9f2bddee..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots.toml +++ /dev/null @@ -1,284 +0,0 @@ -[[e2e]] -name = "env_value_changed" -comment = """ -Changing the value of a tracked env var between runs should invalidate the cache. -""" -steps = [ - { argv = [ - "vt", - "run", - "test", - ], envs = [ - [ - "MY_ENV", - "1", - ], - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "test", - ], envs = [ - [ - "MY_ENV", - "2", - ], - ], comment = "cache miss: env value changed" }, -] - -[[e2e]] -name = "env_added" -comment = """ -Setting a tracked env var that was previously unset should invalidate the cache. -""" -steps = [ - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "test", - ], envs = [ - [ - "MY_ENV", - "1", - ], - ], comment = "cache miss: env added" }, -] - -[[e2e]] -name = "env_removed" -comment = """ -Unsetting a tracked env var that was previously set should invalidate the cache. -""" -steps = [ - { argv = [ - "vt", - "run", - "test", - ], envs = [ - [ - "MY_ENV", - "1", - ], - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss: env removed" }, -] - -[[e2e]] -name = "untracked_env_added" -comment = """ -Adding an `untrackedEnv` entry to the task config should invalidate the cache. -""" -steps = [ - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss" }, - { argv = [ - "vtt", - "replace-file-content", - "vite-task.json", - "\"cache\": true", - "\"cache\": true, \"untrackedEnv\": [\"MY_UNTRACKED\"]", - ], comment = "add untracked env" }, - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss: untracked env added" }, -] - -[[e2e]] -name = "untracked_env_removed" -comment = """ -Removing an existing `untrackedEnv` entry from the task config should invalidate the cache. -""" -steps = [ - { argv = [ - "vtt", - "replace-file-content", - "vite-task.json", - "\"cache\": true", - "\"cache\": true, \"untrackedEnv\": [\"MY_UNTRACKED\"]", - ], comment = "setup" }, - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss" }, - { argv = [ - "vtt", - "replace-file-content", - "vite-task.json", - "\"cache\": true, \"untrackedEnv\": [\"MY_UNTRACKED\"]", - "\"cache\": true", - ], comment = "remove untracked env" }, - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss: untracked env removed" }, -] - -[[e2e]] -name = "cwd_changed" -comment = """ -Changing the task's `cwd` should invalidate the cache, even if the input file exists at the new location. -""" -steps = [ - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss" }, - [ - "vtt", - "mkdir", - "-p", - "subfolder", - ], - [ - "vtt", - "cp", - "test.txt", - "subfolder/test.txt", - ], - { argv = [ - "vtt", - "replace-file-content", - "vite-task.json", - "\"cache\": true", - "\"cache\": true, \"cwd\": \"subfolder\"", - ], comment = "change cwd" }, - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss: cwd changed" }, -] - -[[e2e]] -name = "inferred_input_changes" -comment = """ -Modifying, removing, and re-adding an input file (tracked via fspy inference) should each invalidate the cache. -""" -steps = [ - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss" }, - { argv = [ - "vtt", - "replace-file-content", - "test.txt", - "initial", - "modified", - ], comment = "modify input" }, - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss: input modified" }, - { argv = [ - "vtt", - "rm", - "test.txt", - ], comment = "remove tracked input" }, - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss: input removed" }, - { argv = [ - "vtt", - "write-file", - "test.txt", - "new content", - ], comment = "recreate previously removed file" }, - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss: input added" }, -] - -[[e2e]] -name = "input_config_changed" -comment = """ -Changing the task's `input` configuration should invalidate the cache even when the underlying files are unchanged. -""" -steps = [ - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss" }, - { argv = [ - "vtt", - "replace-file-content", - "vite-task.json", - "\"cache\": true", - "\"cache\": true, \"input\": [\"test.txt\"]", - ], comment = "change input config" }, - { argv = [ - "vt", - "run", - "test", - ], comment = "cache miss: configuration changed" }, -] - -[[e2e]] -name = "glob_input_changes" -comment = """ -Modifying, adding, or removing files matched by a glob `input` pattern should each invalidate the cache. -""" -steps = [ - { argv = [ - "vt", - "run", - "glob-test", - ], comment = "cache miss" }, - { argv = [ - "vtt", - "replace-file-content", - "test.txt", - "initial", - "modified", - ], comment = "modify glob input" }, - { argv = [ - "vt", - "run", - "glob-test", - ], comment = "cache miss: input modified" }, - { argv = [ - "vtt", - "write-file", - "new.txt", - "new content", - ], comment = "add a new .txt file" }, - { argv = [ - "vt", - "run", - "glob-test", - ], comment = "cache miss: input added" }, - { argv = [ - "vtt", - "rm", - "extra.txt", - ], comment = "remove a .txt file" }, - { argv = [ - "vt", - "run", - "glob-test", - ], comment = "cache miss: input removed" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/cwd_changed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/cwd_changed.md deleted file mode 100644 index 92e4abca7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/cwd_changed.md +++ /dev/null @@ -1,38 +0,0 @@ -# cwd_changed - -Changing the task's `cwd` should invalidate the cache, even if the input file exists at the new location. - -## `vt run test` - -cache miss - -``` -$ vtt print-file test.txt -initial content -``` - -## `vtt mkdir -p subfolder` - -``` -``` - -## `vtt cp test.txt subfolder/test.txt` - -``` -``` - -## `vtt replace-file-content vite-task.json '"cache": true' '"cache": true, "cwd": "subfolder"'` - -change cwd - -``` -``` - -## `vt run test` - -cache miss: cwd changed - -``` -~/subfolder$ vtt print-file test.txt ○ cache miss: working directory changed, executing -initial content -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/env_added.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/env_added.md deleted file mode 100644 index c2ca1acfb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/env_added.md +++ /dev/null @@ -1,21 +0,0 @@ -# env_added - -Setting a tracked env var that was previously unset should invalidate the cache. - -## `vt run test` - -cache miss - -``` -$ vtt print-file test.txt -initial content -``` - -## `MY_ENV=1 vt run test` - -cache miss: env added - -``` -$ vtt print-file test.txt ○ cache miss: env 'MY_ENV' changed, executing -initial content -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/env_removed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/env_removed.md deleted file mode 100644 index 30a062fa9..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/env_removed.md +++ /dev/null @@ -1,21 +0,0 @@ -# env_removed - -Unsetting a tracked env var that was previously set should invalidate the cache. - -## `MY_ENV=1 vt run test` - -cache miss - -``` -$ vtt print-file test.txt -initial content -``` - -## `vt run test` - -cache miss: env removed - -``` -$ vtt print-file test.txt ○ cache miss: env 'MY_ENV' changed, executing -initial content -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/env_value_changed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/env_value_changed.md deleted file mode 100644 index dea12d5b0..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/env_value_changed.md +++ /dev/null @@ -1,21 +0,0 @@ -# env_value_changed - -Changing the value of a tracked env var between runs should invalidate the cache. - -## `MY_ENV=1 vt run test` - -cache miss - -``` -$ vtt print-file test.txt -initial content -``` - -## `MY_ENV=2 vt run test` - -cache miss: env value changed - -``` -$ vtt print-file test.txt ○ cache miss: env 'MY_ENV' changed, executing -initial content -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/glob_input_changes.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/glob_input_changes.md deleted file mode 100644 index e436dd9c3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/glob_input_changes.md +++ /dev/null @@ -1,60 +0,0 @@ -# glob_input_changes - -Modifying, adding, or removing files matched by a glob `input` pattern should each invalidate the cache. - -## `vt run glob-test` - -cache miss - -``` -$ vtt print glob-test -glob-test -``` - -## `vtt replace-file-content test.txt initial modified` - -modify glob input - -``` -``` - -## `vt run glob-test` - -cache miss: input modified - -``` -$ vtt print glob-test ○ cache miss: 'test.txt' modified, executing -glob-test -``` - -## `vtt write-file new.txt 'new content'` - -add a new .txt file - -``` -``` - -## `vt run glob-test` - -cache miss: input added - -``` -$ vtt print glob-test ○ cache miss: 'new.txt' added in workspace root, executing -glob-test -``` - -## `vtt rm extra.txt` - -remove a .txt file - -``` -``` - -## `vt run glob-test` - -cache miss: input removed - -``` -$ vtt print glob-test ○ cache miss: 'extra.txt' removed from workspace root, executing -glob-test -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/inferred_input_changes.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/inferred_input_changes.md deleted file mode 100644 index f82af18a7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/inferred_input_changes.md +++ /dev/null @@ -1,60 +0,0 @@ -# inferred_input_changes - -Modifying, removing, and re-adding an input file (tracked via fspy inference) should each invalidate the cache. - -## `vt run test` - -cache miss - -``` -$ vtt print-file test.txt -initial content -``` - -## `vtt replace-file-content test.txt initial modified` - -modify input - -``` -``` - -## `vt run test` - -cache miss: input modified - -``` -$ vtt print-file test.txt ○ cache miss: 'test.txt' modified, executing -modified content -``` - -## `vtt rm test.txt` - -remove tracked input - -``` -``` - -## `vt run test` - -cache miss: input removed - -``` -$ vtt print-file test.txt ○ cache miss: 'test.txt' removed from workspace root, executing -test.txt: not found -``` - -## `vtt write-file test.txt 'new content'` - -recreate previously removed file - -``` -``` - -## `vt run test` - -cache miss: input added - -``` -$ vtt print-file test.txt ○ cache miss: 'test.txt' added in workspace root, executing -new content -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/input_config_changed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/input_config_changed.md deleted file mode 100644 index e43a4a0e1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/input_config_changed.md +++ /dev/null @@ -1,28 +0,0 @@ -# input_config_changed - -Changing the task's `input` configuration should invalidate the cache even when the underlying files are unchanged. - -## `vt run test` - -cache miss - -``` -$ vtt print-file test.txt -initial content -``` - -## `vtt replace-file-content vite-task.json '"cache": true' '"cache": true, "input": ["test.txt"]'` - -change input config - -``` -``` - -## `vt run test` - -cache miss: configuration changed - -``` -$ vtt print-file test.txt ○ cache miss: input configuration changed, executing -initial content -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/untracked_env_added.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/untracked_env_added.md deleted file mode 100644 index 49b67e401..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/untracked_env_added.md +++ /dev/null @@ -1,28 +0,0 @@ -# untracked_env_added - -Adding an `untrackedEnv` entry to the task config should invalidate the cache. - -## `vt run test` - -cache miss - -``` -$ vtt print-file test.txt -initial content -``` - -## `vtt replace-file-content vite-task.json '"cache": true' '"cache": true, "untrackedEnv": ["MY_UNTRACKED"]'` - -add untracked env - -``` -``` - -## `vt run test` - -cache miss: untracked env added - -``` -$ vtt print-file test.txt ○ cache miss: untracked env config changed, executing -initial content -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/untracked_env_removed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/untracked_env_removed.md deleted file mode 100644 index d44b582f4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/snapshots/untracked_env_removed.md +++ /dev/null @@ -1,35 +0,0 @@ -# untracked_env_removed - -Removing an existing `untrackedEnv` entry from the task config should invalidate the cache. - -## `vtt replace-file-content vite-task.json '"cache": true' '"cache": true, "untrackedEnv": ["MY_UNTRACKED"]'` - -setup - -``` -``` - -## `vt run test` - -cache miss - -``` -$ vtt print-file test.txt -initial content -``` - -## `vtt replace-file-content vite-task.json '"cache": true, "untrackedEnv": ["MY_UNTRACKED"]' '"cache": true'` - -remove untracked env - -``` -``` - -## `vt run test` - -cache miss: untracked env removed - -``` -$ vtt print-file test.txt ○ cache miss: untracked env config changed, executing -initial content -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/test.txt b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/test.txt deleted file mode 100644 index f2376e2ba..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/test.txt +++ /dev/null @@ -1 +0,0 @@ -initial content diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/vite-task.json deleted file mode 100644 index f3f877a9f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_miss_reasons/vite-task.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tasks": { - "test": { - "command": "vtt print-file test.txt", - "env": ["MY_ENV"], - "cache": true - }, - "glob-test": { - "command": "vtt print glob-test", - "input": ["*.txt"], - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/package.json deleted file mode 100644 index 538e7f4dd..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "@test/cache-subcommand" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/snapshots.toml deleted file mode 100644 index 73d1f3995..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/snapshots.toml +++ /dev/null @@ -1,24 +0,0 @@ -[[e2e]] -name = "cache_clean" -steps = [ - { argv = [ - "vt", - "run", - "cached-task", - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "cached-task", - ], comment = "cache hit" }, - [ - "vt", - "cache", - "clean", - ], - { argv = [ - "vt", - "run", - "cached-task", - ], comment = "cache miss after clean" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/snapshots/cache_clean.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/snapshots/cache_clean.md deleted file mode 100644 index f20f5a386..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/snapshots/cache_clean.md +++ /dev/null @@ -1,36 +0,0 @@ -# cache_clean - -## `vt run cached-task` - -cache miss - -``` -$ vtt print-file test.txt -test content -``` - -## `vt run cached-task` - -cache hit - -``` -$ vtt print-file test.txt ◉ cache hit, replaying -test content - ---- -vt run: cache hit. -``` - -## `vt cache clean` - -``` -``` - -## `vt run cached-task` - -cache miss after clean - -``` -$ vtt print-file test.txt -test content -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/test.txt b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/test.txt deleted file mode 100644 index d670460b4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/test.txt +++ /dev/null @@ -1 +0,0 @@ -test content diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/vite-task.json deleted file mode 100644 index 741157963..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_subcommand/vite-task.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "cache": true, - "tasks": { - "cached-task": { - "command": "vtt print-file test.txt", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/package.json deleted file mode 100644 index 49b99b3bf..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "cache-task-select-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/snapshots.toml deleted file mode 100644 index 2de3b3099..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/snapshots.toml +++ /dev/null @@ -1,20 +0,0 @@ -[[e2e]] -name = "cache_hit_with_interactive_selector_and___cache" -steps = [ - { argv = [ - "vt", - "run", - "--cache", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write-key" = "enter" }, - ] }, - { argv = [ - "vt", - "run", - "--cache", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write-key" = "enter" }, - ] }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/snapshots/cache_hit_with_interactive_selector_and___cache.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/snapshots/cache_hit_with_interactive_selector_and___cache.md deleted file mode 100644 index 59b5ce17e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/snapshots/cache_hit_with_interactive_selector_and___cache.md +++ /dev/null @@ -1,42 +0,0 @@ -# cache_hit_with_interactive_selector_and___cache - -## `vt run --cache` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build vtt print-file src/main.ts - lint echo lint app -``` - -**← write-key:** `enter` - -``` -Selected task: build -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vt run --cache` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build vtt print-file src/main.ts - lint echo lint app -``` - -**← write-key:** `enter` - -``` -Selected task: build -$ vtt print-file src/main.ts ◉ cache hit, replaying -export const main = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/src/main.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/src/main.ts deleted file mode 100644 index 38e000a1c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -export const main = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/vite-task.json deleted file mode 100644 index 322e22bca..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/cache_task_select/vite-task.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tasks": { - "build": { - "command": "vtt print-file src/main.ts", - "cache": true - }, - "lint": { - "command": "echo lint app" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/package.json deleted file mode 100644 index a17363799..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "read_colon_in_name": "vtt print-file node:fs" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/snapshots.toml deleted file mode 100644 index 8c5991bcc..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/snapshots.toml +++ /dev/null @@ -1,17 +0,0 @@ -[[e2e]] -name = "read_file_with_colon_in_name" -comment = """ -Tests that a task name with colon works correctly with caching -""" -steps = [ - { argv = [ - "vt", - "run", - "read_colon_in_name", - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "read_colon_in_name", - ], comment = "cache hit" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/snapshots/read_file_with_colon_in_name.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/snapshots/read_file_with_colon_in_name.md deleted file mode 100644 index 430df318a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/snapshots/read_file_with_colon_in_name.md +++ /dev/null @@ -1,24 +0,0 @@ -# read_file_with_colon_in_name - -Tests that a task name with colon works correctly with caching - -## `vt run read_colon_in_name` - -cache miss - -``` -$ vtt print-file node:fs -node:fs: not found -``` - -## `vt run read_colon_in_name` - -cache hit - -``` -$ vtt print-file node:fs ◉ cache hit, replaying -node:fs: not found - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/colon_in_name/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json deleted file mode 100644 index cf301ba7f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "color-env-handling", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml deleted file mode 100644 index 85c1436a7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml +++ /dev/null @@ -1,47 +0,0 @@ -[[e2e]] -name = "force_color_env_is_always_one_in_cached_child" -comment = """ -The parent shell sets `FORCE_COLOR=0`, but a cached task's child always sees `FORCE_COLOR=1`. Color-related env vars are not passed through by default for cached tasks — the runner injects `FORCE_COLOR=1` so cached output is always colored, and the reporter strips colors at display time when the terminal does not support them. (For uncached tasks the parent's environment flows through untouched.) -""" -steps = [ - { argv = [ - "vt", - "run", - "print-force-color", - ], envs = [ - [ - "FORCE_COLOR", - "0", - ], - ], comment = "parent FORCE_COLOR=0, cached child should still see FORCE_COLOR=1" }, -] - -[[e2e]] -name = "cached_output_keeps_colors_across_force_color_values" -comment = """ -A task that emits ANSI escape sequences is run twice. Each cache entry carries the raw coloured bytes — the runner spawns cached tasks with `FORCE_COLOR=1` regardless of the parent's value — and the reporter strips colours at the writer level only when the user's terminal cannot render them. - -Plain-text snapshot steps use the default `vt100::Screen::contents()` renderer, which flattens any rendered ANSI styling into plain characters (so colors don't pollute snapshots). The two `formatted-snapshot = true` steps switch to `vt100::Screen::rows_formatted` (joined with newlines), which preserves SGR escapes without emitting cursor positioning or other rendering state — the latter varies across platforms and would make the snapshot flaky. Bytes are then routed through `std::ascii::escape_default` so escapes appear as `\\xNN`. Those two steps prove that the cached bytes contained colour all along — even when the corresponding initial run had displayed them in plain text. -""" - -[[e2e.steps]] -argv = ["vt", "run", "print-colored-a"] -envs = [["FORCE_COLOR", "1"]] -comment = "task A — cache miss; parent FORCE_COLOR=1; formatted snapshot proves the child emitted ANSI codes" -formatted-snapshot = true - -[[e2e.steps]] -argv = ["vt", "run", "print-colored-a"] -envs = [["FORCE_COLOR", "0"]] -comment = "task A — cache hit replayed; plain snapshot collapses any colour styling to text" - -[[e2e.steps]] -argv = ["vt", "run", "print-colored-b"] -envs = [["FORCE_COLOR", "0"]] -comment = "task B — cache miss; parent FORCE_COLOR=0, plain snapshot collapses styling. The cache still records the coloured bytes." - -[[e2e.steps]] -argv = ["vt", "run", "print-colored-b"] -envs = [["FORCE_COLOR", "1"]] -comment = "task B — cache hit replayed; formatted snapshot proves the cached bytes were coloured, even though the cache-miss run (above) showed them as plain text" -formatted-snapshot = true diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/cached_output_keeps_colors_across_force_color_values.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/cached_output_keeps_colors_across_force_color_values.md deleted file mode 100644 index 4b61de0ed..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/cached_output_keeps_colors_across_force_color_values.md +++ /dev/null @@ -1,47 +0,0 @@ -# cached_output_keeps_colors_across_force_color_values - -A task that emits ANSI escape sequences is run twice. Each cache entry carries the raw coloured bytes — the runner spawns cached tasks with `FORCE_COLOR=1` regardless of the parent's value — and the reporter strips colours at the writer level only when the user's terminal cannot render them. - -Plain-text snapshot steps use the default `vt100::Screen::contents()` renderer, which flattens any rendered ANSI styling into plain characters (so colors don't pollute snapshots). The two `formatted-snapshot = true` steps switch to `vt100::Screen::rows_formatted` (joined with newlines), which preserves SGR escapes without emitting cursor positioning or other rendering state — the latter varies across platforms and would make the snapshot flaky. Bytes are then routed through `std::ascii::escape_default` so escapes appear as `\xNN`. Those two steps prove that the cached bytes contained colour all along — even when the corresponding initial run had displayed them in plain text. - -## `FORCE_COLOR=1 vt run print-colored-a` - -task A — cache miss; parent FORCE_COLOR=1; formatted snapshot proves the child emitted ANSI codes - -``` -\x1b[34m$ vtt print-color red hello-world -\x1b[31mhello-world -``` - -## `FORCE_COLOR=0 vt run print-colored-a` - -task A — cache hit replayed; plain snapshot collapses any colour styling to text - -``` -$ vtt print-color red hello-world ◉ cache hit, replaying -hello-world - ---- -vt run: cache hit. -``` - -## `FORCE_COLOR=0 vt run print-colored-b` - -task B — cache miss; parent FORCE_COLOR=0, plain snapshot collapses styling. The cache still records the coloured bytes. - -``` -$ vtt print-color blue hello-again -hello-again -``` - -## `FORCE_COLOR=1 vt run print-colored-b` - -task B — cache hit replayed; formatted snapshot proves the cached bytes were coloured, even though the cache-miss run (above) showed them as plain text - -``` -\x1b[34m$ vtt print-color blue hello-again \x1b[32m\xe2\x97\x89 \x1b[90mcache hit, replaying -\x1b[34mhello-again - -\x1b[90m--- -\x1b[34;1mvt run: cache hit, \x1b[32;1m saved. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/force_color_env_is_always_one_in_cached_child.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/force_color_env_is_always_one_in_cached_child.md deleted file mode 100644 index 94b2eaa16..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/force_color_env_is_always_one_in_cached_child.md +++ /dev/null @@ -1,12 +0,0 @@ -# force_color_env_is_always_one_in_cached_child - -The parent shell sets `FORCE_COLOR=0`, but a cached task's child always sees `FORCE_COLOR=1`. Color-related env vars are not passed through by default for cached tasks — the runner injects `FORCE_COLOR=1` so cached output is always colored, and the reporter strips colors at display time when the terminal does not support them. (For uncached tasks the parent's environment flows through untouched.) - -## `FORCE_COLOR=0 vt run print-force-color` - -parent FORCE_COLOR=0, cached child should still see FORCE_COLOR=1 - -``` -$ vtt print-env FORCE_COLOR -1 -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/vite-task.json deleted file mode 100644 index 3ba4b0d82..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/color_env_handling/vite-task.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "tasks": { - "print-force-color": { - "command": "vtt print-env FORCE_COLOR", - "cache": true - }, - "print-colored-a": { - "command": "vtt print-color red hello-world", - "cache": true - }, - "print-colored-b": { - "command": "vtt print-color blue hello-again", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/package.json deleted file mode 100644 index e6a3b5369..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "concurrent-execution-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/packages/a/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/packages/a/package.json deleted file mode 100644 index 966765390..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/packages/a/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@concurrent/a", - "scripts": { - "build": "vtt barrier ../../.barrier sync 2", - "test": "vtt barrier ../../.barrier test-sync 2 --exit=1", - "daemon": "vtt barrier ../../.barrier daemon-sync 2 --exit=1" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/packages/b/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/packages/b/package.json deleted file mode 100644 index 26eed0c8c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/packages/b/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@concurrent/b", - "scripts": { - "build": "vtt barrier ../../.barrier sync 2", - "test": "vtt barrier ../../.barrier test-sync 2 --hang", - "daemon": "vtt barrier ../../.barrier daemon-sync 2 --daemonize" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots.toml deleted file mode 100644 index 0e26f2c6c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots.toml +++ /dev/null @@ -1,27 +0,0 @@ -[[e2e]] -name = "independent_tasks_run_concurrently" -comment = """ -Two packages with no dependency relationship should run concurrently. Both tasks participate in a 2-way barrier, so sequential execution would hang forever and time out. -""" -steps = [["vt", "run", "-r", "build"]] - -[[e2e]] -name = "failure_kills_concurrent_tasks" -comment = """ -When one concurrent task fails, the sibling running under inherited stdio must be cancelled. Task a exits 1 after the shared barrier, task b hangs after it — completing without timeout proves cancellation killed b. -""" -steps = [["vt", "run", "-r", "test"]] - -[[e2e]] -name = "failure_kills_concurrent_cached_tasks" -comment = """ -Same failure-cancellation scenario, but with `--cache` so execution goes through the piped stdio / fspy path (spawn_with_tracking) instead of the inherited-stdio path. -""" -steps = [["vt", "run", "-r", "--cache", "test"]] - -[[e2e]] -name = "failure_kills_daemonized_concurrent_tasks" -comment = """ -Cancellation must also kill a sibling that has daemonized — closing stdout/stderr (EOF on the pipe) while the process itself stays alive. The runner must reach the process via the cancellation token + Job Object, not by pipe closure. -""" -steps = [["vt", "run", "-r", "--cache", "daemon"]] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/failure_kills_concurrent_cached_tasks.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/failure_kills_concurrent_cached_tasks.md deleted file mode 100644 index b9244e4ff..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/failure_kills_concurrent_cached_tasks.md +++ /dev/null @@ -1,16 +0,0 @@ -# failure_kills_concurrent_cached_tasks - -Same failure-cancellation scenario, but with `--cache` so execution goes through the piped stdio / fspy path (spawn_with_tracking) instead of the inherited-stdio path. - -## `vt run -r --cache test` - -**Exit code:** 1 - -``` -~/packages/a$ vtt barrier ../../.barrier test-sync 2 --exit=1 -~/packages/b$ vtt barrier ../../.barrier test-sync 2 --hang - - ---- -vt run: 0/2 cache hit (0%), 2 failed. (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/failure_kills_concurrent_tasks.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/failure_kills_concurrent_tasks.md deleted file mode 100644 index 511a2b804..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/failure_kills_concurrent_tasks.md +++ /dev/null @@ -1,16 +0,0 @@ -# failure_kills_concurrent_tasks - -When one concurrent task fails, the sibling running under inherited stdio must be cancelled. Task a exits 1 after the shared barrier, task b hangs after it — completing without timeout proves cancellation killed b. - -## `vt run -r test` - -**Exit code:** 1 - -``` -~/packages/a$ vtt barrier ../../.barrier test-sync 2 --exit=1 ⊘ cache disabled -~/packages/b$ vtt barrier ../../.barrier test-sync 2 --hang ⊘ cache disabled - - ---- -vt run: 0/2 cache hit (0%), 2 failed. (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/failure_kills_daemonized_concurrent_tasks.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/failure_kills_daemonized_concurrent_tasks.md deleted file mode 100644 index 4dd46643e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/failure_kills_daemonized_concurrent_tasks.md +++ /dev/null @@ -1,16 +0,0 @@ -# failure_kills_daemonized_concurrent_tasks - -Cancellation must also kill a sibling that has daemonized — closing stdout/stderr (EOF on the pipe) while the process itself stays alive. The runner must reach the process via the cancellation token + Job Object, not by pipe closure. - -## `vt run -r --cache daemon` - -**Exit code:** 1 - -``` -~/packages/a$ vtt barrier ../../.barrier daemon-sync 2 --exit=1 -~/packages/b$ vtt barrier ../../.barrier daemon-sync 2 --daemonize - - ---- -vt run: 0/2 cache hit (0%), 2 failed. (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/independent_tasks_run_concurrently.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/independent_tasks_run_concurrently.md deleted file mode 100644 index 925a00820..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/snapshots/independent_tasks_run_concurrently.md +++ /dev/null @@ -1,14 +0,0 @@ -# independent_tasks_run_concurrently - -Two packages with no dependency relationship should run concurrently. Both tasks participate in a 2-way barrier, so sequential execution would hang forever and time out. - -## `vt run -r build` - -``` -~/packages/a$ vtt barrier ../../.barrier sync 2 ⊘ cache disabled -~/packages/b$ vtt barrier ../../.barrier sync 2 ⊘ cache disabled - - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/vite-task.json deleted file mode 100644 index b39113d05..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/concurrent_execution/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": false -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/package.json deleted file mode 100644 index 0967ef424..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/package.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/snapshots.toml deleted file mode 100644 index ca240d76e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/snapshots.toml +++ /dev/null @@ -1,8 +0,0 @@ -[[e2e]] -name = "constrained_dev_shm" -comment = """ -With fspy's shared-memory backing moved to memfd, file-access tracking succeeds without consuming a one-page `/dev/shm` mount. -""" -platform = "linux-gnu" -ignore = true -steps = [["vtt", "small_dev_shm", "vt", "run", "stress"]] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/snapshots/constrained_dev_shm.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/snapshots/constrained_dev_shm.md deleted file mode 100644 index 4dd59fa3b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/snapshots/constrained_dev_shm.md +++ /dev/null @@ -1,9 +0,0 @@ -# constrained_dev_shm - -With fspy's shared-memory backing moved to memfd, file-access tracking succeeds without consuming a one-page `/dev/shm` mount. - -## `vtt small_dev_shm vt run stress` - -``` -$ vtt stat_long_filename 1048576 -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/vite-task.json deleted file mode 100644 index 0415ae320..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/constrained_dev_shm/vite-task.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tasks": { - "stress": { - "command": "vtt stat_long_filename 1048576", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/package.json deleted file mode 100644 index a9a08d6fe..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "ctrl-c-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/packages/a/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/packages/a/package.json deleted file mode 100644 index afec67f2f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/packages/a/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@ctrl-c/a", - "scripts": { - "dev": "vtt exit-on-ctrlc" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/packages/b/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/packages/b/package.json deleted file mode 100644 index 3d0a9d823..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/packages/b/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@ctrl-c/b", - "scripts": { - "dev": "vtt print b-ran" - }, - "dependencies": { - "@ctrl-c/a": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots.toml deleted file mode 100644 index a1febd1f6..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots.toml +++ /dev/null @@ -1,74 +0,0 @@ -[[e2e]] -name = "ctrl_c_terminates_running_tasks" -comment = """ -Ctrl+C (SIGINT) on an uncached run should propagate to the running task and terminate it. -""" -steps = [ - { argv = [ - "vt", - "run", - "--no-cache", - "@ctrl-c/a#dev", - ], interactions = [ - { "expect-milestone" = "ready" }, - { "write-key" = "ctrl-c" }, - ] }, -] - -[[e2e]] -name = "ctrl_c_terminates_running_tasks__cached_" -comment = """ -Same as above, but under the cached execution path (piped stdio + fspy tracking). -""" -steps = [ - { argv = [ - "vt", - "run", - "@ctrl-c/a#dev", - ], interactions = [ - { "expect-milestone" = "ready" }, - { "write-key" = "ctrl-c" }, - ] }, -] - -[[e2e]] -name = "ctrl_c_prevents_future_tasks" -comment = """ -Ctrl+C while running sequentially (b depends on a) should terminate a and prevent b from being scheduled. -""" -steps = [ - { argv = [ - "vt", - "run", - "-r", - "--no-cache", - "dev", - ], interactions = [ - { "expect-milestone" = "ready" }, - { "write-key" = "ctrl-c" }, - ] }, -] - -[[e2e]] -name = "ctrl_c_prevents_caching" -comment = """ -A task interrupted by Ctrl+C must not populate the cache, even if it happens to exit 0 — the following run should be a cache miss, not a hit. -""" -steps = [ - { argv = [ - "vt", - "run", - "@ctrl-c/a#dev", - ], interactions = [ - { "expect-milestone" = "ready" }, - { "write-key" = "ctrl-c" }, - ], comment = "exits 0 but should not be cached" }, - { argv = [ - "vt", - "run", - "@ctrl-c/a#dev", - ], interactions = [ - { "expect-milestone" = "ready" }, - { "write-key" = "ctrl-c" }, - ], comment = "should be cache miss, not hit" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_prevents_caching.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_prevents_caching.md deleted file mode 100644 index 2c342a8cc..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_prevents_caching.md +++ /dev/null @@ -1,37 +0,0 @@ -# ctrl_c_prevents_caching - -A task interrupted by Ctrl+C must not populate the cache, even if it happens to exit 0 — the following run should be a cache miss, not a hit. - -## `vt run @ctrl-c/a#dev` - -exits 0 but should not be cached - -**→ expect-milestone:** `ready` - -``` -~/packages/a$ vtt exit-on-ctrlc -``` - -**← write-key:** `ctrl-c` - -``` -~/packages/a$ vtt exit-on-ctrlc -ctrl-c received -``` - -## `vt run @ctrl-c/a#dev` - -should be cache miss, not hit - -**→ expect-milestone:** `ready` - -``` -~/packages/a$ vtt exit-on-ctrlc -``` - -**← write-key:** `ctrl-c` - -``` -~/packages/a$ vtt exit-on-ctrlc -ctrl-c received -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_prevents_future_tasks.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_prevents_future_tasks.md deleted file mode 100644 index 4889964d0..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_prevents_future_tasks.md +++ /dev/null @@ -1,18 +0,0 @@ -# ctrl_c_prevents_future_tasks - -Ctrl+C while running sequentially (b depends on a) should terminate a and prevent b from being scheduled. - -## `vt run -r --no-cache dev` - -**→ expect-milestone:** `ready` - -``` -~/packages/a$ vtt exit-on-ctrlc ⊘ cache disabled -``` - -**← write-key:** `ctrl-c` - -``` -~/packages/a$ vtt exit-on-ctrlc ⊘ cache disabled -ctrl-c received -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_terminates_running_tasks.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_terminates_running_tasks.md deleted file mode 100644 index 344ae7baa..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_terminates_running_tasks.md +++ /dev/null @@ -1,18 +0,0 @@ -# ctrl_c_terminates_running_tasks - -Ctrl+C (SIGINT) on an uncached run should propagate to the running task and terminate it. - -## `vt run --no-cache @ctrl-c/a#dev` - -**→ expect-milestone:** `ready` - -``` -~/packages/a$ vtt exit-on-ctrlc ⊘ cache disabled -``` - -**← write-key:** `ctrl-c` - -``` -~/packages/a$ vtt exit-on-ctrlc ⊘ cache disabled -ctrl-c received -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_terminates_running_tasks__cached_.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_terminates_running_tasks__cached_.md deleted file mode 100644 index 1f751c08b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/snapshots/ctrl_c_terminates_running_tasks__cached_.md +++ /dev/null @@ -1,18 +0,0 @@ -# ctrl_c_terminates_running_tasks__cached_ - -Same as above, but under the cached execution path (piped stdio + fspy tracking). - -## `vt run @ctrl-c/a#dev` - -**→ expect-milestone:** `ready` - -``` -~/packages/a$ vtt exit-on-ctrlc -``` - -**← write-key:** `ctrl-c` - -``` -~/packages/a$ vtt exit-on-ctrlc -ctrl-c received -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ctrl_c/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/package.json deleted file mode 100644 index 0967ef424..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/package.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/snapshots.toml deleted file mode 100644 index a24b8ff31..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/snapshots.toml +++ /dev/null @@ -1,19 +0,0 @@ -[[e2e]] -name = "env_negation_excludes_matching_var" -comment = """A `!`-prefixed env pattern excludes matching variables. With `env: ["PROBE_*", "!PROBE_SECRET"]`, the task receives PROBE_PUBLIC but not PROBE_SECRET — the negation filters it out — so print-env reports it undefined.""" -steps = [ - { argv = [ - "vt", - "run", - "print", - ], envs = [ - [ - "PROBE_PUBLIC", - "public-value", - ], - [ - "PROBE_SECRET", - "secret-value", - ], - ], comment = "PROBE_SECRET is filtered out by !PROBE_SECRET" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/snapshots/env_negation_excludes_matching_var.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/snapshots/env_negation_excludes_matching_var.md deleted file mode 100644 index 739deb99f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/snapshots/env_negation_excludes_matching_var.md +++ /dev/null @@ -1,18 +0,0 @@ -# env_negation_excludes_matching_var - -A `!`-prefixed env pattern excludes matching variables. With `env: ["PROBE_*", "!PROBE_SECRET"]`, the task receives PROBE_PUBLIC but not PROBE_SECRET — the negation filters it out — so print-env reports it undefined. - -## `PROBE_PUBLIC=public-value PROBE_SECRET=secret-value vt run print` - -PROBE_SECRET is filtered out by !PROBE_SECRET - -``` -$ vtt print-env PROBE_PUBLIC -public-value - -$ vtt print-env PROBE_SECRET -(undefined) - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/vite-task.json deleted file mode 100644 index ffb3041ba..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/env_negation/vite-task.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cache": true, - "tasks": { - "print": { - "command": "vtt print-env PROBE_PUBLIC && vtt print-env PROBE_SECRET", - "env": ["PROBE_*", "!PROBE_SECRET"], - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/package.json deleted file mode 100644 index 939ba719a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "error-cycle-dependency-test" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/snapshots.toml deleted file mode 100644 index 1afe07638..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/snapshots.toml +++ /dev/null @@ -1,6 +0,0 @@ -[[e2e]] -name = "cycle_dependency_error" -comment = """ -Tests error message for cyclic task dependencies -""" -steps = [{ argv = ["vt", "run", "task-a"], comment = "task-a -> task-b -> task-a cycle" }] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/snapshots/cycle_dependency_error.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/snapshots/cycle_dependency_error.md deleted file mode 100644 index b2ec4748d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/snapshots/cycle_dependency_error.md +++ /dev/null @@ -1,13 +0,0 @@ -# cycle_dependency_error - -Tests error message for cyclic task dependencies - -## `vt run task-a` - -task-a -> task-b -> task-a cycle - -**Exit code:** 1 - -``` -error: Cycle dependency detected: error-cycle-dependency-test#task-a -> error-cycle-dependency-test#task-b -> error-cycle-dependency-test#task-a -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite-task.json deleted file mode 100644 index 25c64078e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/error_cycle_dependency/vite-task.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "cache": true, - "tasks": { - "task-a": { - "command": "echo a", - "dependsOn": ["task-b"] - }, - "task-b": { - "command": "echo b", - "dependsOn": ["task-a"] - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/package.json deleted file mode 100644 index bff6b705f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "exit-codes-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/packages/pkg-a/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/packages/pkg-a/package.json deleted file mode 100644 index ac5524982..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/packages/pkg-a/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "pkg-a", - "scripts": { - "fail": "vtt exit 42", - "check": "vtt exit 1", - "chained": "vtt exit 3 && echo 'second'" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/packages/pkg-b/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/packages/pkg-b/package.json deleted file mode 100644 index 1dcdb707e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/packages/pkg-b/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "pkg-b", - "scripts": { - "fail": "vtt exit 7", - "check": "echo 'pkg-b check passed'" - }, - "dependencies": { - "pkg-a": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots.toml deleted file mode 100644 index 91bf7f022..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots.toml +++ /dev/null @@ -1,34 +0,0 @@ -[[e2e]] -name = "single_task_failure_returns_task_exit_code" -comment = """ -`vp run` should exit with the underlying task's exit code when a single task fails. -""" -steps = [{ argv = ["vt", "run", "pkg-a#fail"], comment = "exits with code 42" }] - -[[e2e]] -name = "task_failure_fast_fails_remaining_tasks" -comment = """ -A failing task under `-r` should fast-fail the run and skip any remaining packages. -""" -steps = [{ argv = ["vt", "run", "-r", "fail"], comment = "pkg-a fails, pkg-b is skipped" }] - -[[e2e]] -name = "dependency_failure_fast_fails_dependents" -comment = """ -When a dependency fails under `-t`, dependents should be skipped rather than run anyway. -""" -cwd = "packages/pkg-b" -steps = [{ argv = ["vt", "run", "-t", "check"], comment = "pkg-a fails, pkg-b is skipped" }] - -[[e2e]] -name = "chained_command_with____stops_at_first_failure" -comment = """ -In a `&&`-chained command, a non-zero exit from the first sub-command should short-circuit and skip the rest. -""" -steps = [ - { argv = [ - "vt", - "run", - "pkg-a#chained", - ], comment = "first fails with exit code 3, second should not run" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/chained_command_with____stops_at_first_failure.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/chained_command_with____stops_at_first_failure.md deleted file mode 100644 index 3ef7f9138..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/chained_command_with____stops_at_first_failure.md +++ /dev/null @@ -1,13 +0,0 @@ -# chained_command_with____stops_at_first_failure - -In a `&&`-chained command, a non-zero exit from the first sub-command should short-circuit and skip the rest. - -## `vt run pkg-a#chained` - -first fails with exit code 3, second should not run - -**Exit code:** 3 - -``` -~/packages/pkg-a$ vtt exit 3 -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/dependency_failure_fast_fails_dependents.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/dependency_failure_fast_fails_dependents.md deleted file mode 100644 index ec981713d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/dependency_failure_fast_fails_dependents.md +++ /dev/null @@ -1,13 +0,0 @@ -# dependency_failure_fast_fails_dependents - -When a dependency fails under `-t`, dependents should be skipped rather than run anyway. - -## `vt run -t check` - -pkg-a fails, pkg-b is skipped - -**Exit code:** 1 - -``` -~/packages/pkg-a$ vtt exit 1 -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/single_task_failure_returns_task_exit_code.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/single_task_failure_returns_task_exit_code.md deleted file mode 100644 index 3fb8f57b5..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/single_task_failure_returns_task_exit_code.md +++ /dev/null @@ -1,13 +0,0 @@ -# single_task_failure_returns_task_exit_code - -`vp run` should exit with the underlying task's exit code when a single task fails. - -## `vt run pkg-a#fail` - -exits with code 42 - -**Exit code:** 42 - -``` -~/packages/pkg-a$ vtt exit 42 -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/task_failure_fast_fails_remaining_tasks.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/task_failure_fast_fails_remaining_tasks.md deleted file mode 100644 index 32faf8c84..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/snapshots/task_failure_fast_fails_remaining_tasks.md +++ /dev/null @@ -1,13 +0,0 @@ -# task_failure_fast_fails_remaining_tasks - -A failing task under `-r` should fast-fail the run and skip any remaining packages. - -## `vt run -r fail` - -pkg-a fails, pkg-b is skipped - -**Exit code:** 42 - -``` -~/packages/pkg-a$ vtt exit 42 -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/exit_codes/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/package.json deleted file mode 100644 index f90a3ecd4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "filter-unmatched-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/packages/app/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/packages/app/package.json deleted file mode 100644 index e008b9cbb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/packages/app/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@test/app", - "scripts": { - "build": "vtt print built-app" - }, - "dependencies": { - "@test/lib": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/packages/lib/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/packages/lib/package.json deleted file mode 100644 index 2b2d2bc69..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/packages/lib/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/lib", - "scripts": { - "build": "vtt print built-lib" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots.toml deleted file mode 100644 index 5a559345b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots.toml +++ /dev/null @@ -1,102 +0,0 @@ -[[e2e]] -name = "partial_match_warns_for_unmatched_filter" -comment = """ -When some `--filter` flags match and one does not, the run should proceed and warn only for the unmatched filter. -""" -steps = [["vt", "run", "--filter", "@test/app", "--filter", "nonexistent", "build"]] - -[[e2e]] -name = "multiple_unmatched_filters_warn_individually" -comment = """ -Each unmatched `--filter` should produce its own warning — warnings must not be collapsed into a single message. -""" -steps = [["vt", "run", "--filter", "@test/app", "--filter", "nope1", "--filter", "nope2", "build"]] - -[[e2e]] -name = "whitespace_split_filter_warns_for_unmatched_token" -comment = """ -A whitespace-split `--filter` argument should warn about each of its individual unmatched tokens. -""" -steps = [["vt", "run", "--filter", "@test/app nope", "build"]] - -[[e2e]] -name = "unmatched_exclusion_filter_does_not_warn" -comment = """ -An exclusion filter (`!...`) that matches nothing should be silent; only unmatched inclusion filters warn. -""" -steps = [["vt", "run", "--filter", "@test/app", "--filter", "!nonexistent", "build"]] - -[[e2e]] -name = "unmatched_glob_filter_warns" -comment = """ -A glob `--filter` that matches no packages should warn like any other unmatched inclusion filter. -""" -steps = [["vt", "run", "--filter", "@test/app", "--filter", "@nope/*", "build"]] - -[[e2e]] -name = "unmatched_directory_filter_warns" -comment = """ -A directory-style `--filter` (`./packages/...`) that points nowhere should warn just like an unmatched name filter. -""" -steps = [["vt", "run", "--filter", "@test/app", "--filter", "./packages/nope", "build"]] - -[[e2e]] -name = "filter_with_zero_results_exits_zero_by_default" -comment = """ -A `--filter` whose entire result is empty (typo, glob with no match, …) prints the warning and exits 0 — pnpm's default. Previously this errored with `Task "build" not found`. -""" -steps = [["vt", "run", "--filter", "nonexistent", "build"]] - -[[e2e]] -name = "traversal_collapsed_to_empty_exits_zero_by_default" -comment = """ -`{.}^...` selects the dependencies of the current package, excluding itself. On a leaf with no workspace deps the expression collapses to zero matches — a legitimate no-op rather than a typo — so the run warns and exits 0. -""" -cwd = "packages/lib" -steps = [["vt", "run", "--filter", "{.}^...", "build"]] - -[[e2e]] -name = "fail_if_no_match_errors_on_unmatched_filter" -comment = """ -With `--fail-if-no-match`, an unmatched `--filter` aborts the run with a non-zero exit code instead of warning. Mirrors pnpm's `--fail-if-no-match`. -""" -steps = [["vt", "run", "--filter", "nonexistent", "--fail-if-no-match", "build"]] - -[[e2e]] -name = "fail_if_no_match_errors_even_when_other_filters_match" -comment = """ -Strict mode errors on **any** unmatched filter, even when other filters did match packages — this catches typos in CI scripts that combine an exact name with a glob. -""" -steps = [ - [ - "vt", - "run", - "--filter", - "@test/app", - "--filter", - "nonexistent", - "--fail-if-no-match", - "build", - ], -] - -[[e2e]] -name = "fail_if_no_match_succeeds_when_all_filters_match" -comment = """ -With `--fail-if-no-match` and only matching filters, the run proceeds normally — strict mode does not change the success path. -""" -steps = [["vt", "run", "--filter", "@test/app", "--fail-if-no-match", "build"]] - -[[e2e]] -name = "nested_filter_no_match_succeeds_by_default" -comment = """ -The default-success rule must apply to nested `vt run --filter ...` invocations too: a task whose command is `vt run --filter nonexistent build` should exit 0 with the warning, not fail the outer task. Guards the script wrapping that pnpm users typically write. -""" -steps = [["vt", "run", "filter-nonexistent"]] - -[[e2e]] -name = "nested_filter_no_match_with_fail_if_no_match_errors" -comment = """ -Strict mode also propagates through nesting: a task command that adds `--fail-if-no-match` aborts the outer task when the nested filter selects nothing. The error chain identifies both the nested command and the unmatched filter source. -""" -steps = [["vt", "run", "filter-nonexistent-strict"]] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/fail_if_no_match_errors_even_when_other_filters_match.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/fail_if_no_match_errors_even_when_other_filters_match.md deleted file mode 100644 index ca0d61609..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/fail_if_no_match_errors_even_when_other_filters_match.md +++ /dev/null @@ -1,11 +0,0 @@ -# fail_if_no_match_errors_even_when_other_filters_match - -Strict mode errors on **any** unmatched filter, even when other filters did match packages — this catches typos in CI scripts that combine an exact name with a glob. - -## `vt run --filter @test/app --filter nonexistent --fail-if-no-match build` - -**Exit code:** 1 - -``` -error: No packages matched the filter: nonexistent -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/fail_if_no_match_errors_on_unmatched_filter.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/fail_if_no_match_errors_on_unmatched_filter.md deleted file mode 100644 index 16cef521d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/fail_if_no_match_errors_on_unmatched_filter.md +++ /dev/null @@ -1,11 +0,0 @@ -# fail_if_no_match_errors_on_unmatched_filter - -With `--fail-if-no-match`, an unmatched `--filter` aborts the run with a non-zero exit code instead of warning. Mirrors pnpm's `--fail-if-no-match`. - -## `vt run --filter nonexistent --fail-if-no-match build` - -**Exit code:** 1 - -``` -error: No packages matched the filter: nonexistent -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/fail_if_no_match_succeeds_when_all_filters_match.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/fail_if_no_match_succeeds_when_all_filters_match.md deleted file mode 100644 index f900ceff0..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/fail_if_no_match_succeeds_when_all_filters_match.md +++ /dev/null @@ -1,10 +0,0 @@ -# fail_if_no_match_succeeds_when_all_filters_match - -With `--fail-if-no-match` and only matching filters, the run proceeds normally — strict mode does not change the success path. - -## `vt run --filter @test/app --fail-if-no-match build` - -``` -~/packages/app$ vtt print built-app -built-app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/filter_with_zero_results_exits_zero_by_default.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/filter_with_zero_results_exits_zero_by_default.md deleted file mode 100644 index c4eddd951..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/filter_with_zero_results_exits_zero_by_default.md +++ /dev/null @@ -1,9 +0,0 @@ -# filter_with_zero_results_exits_zero_by_default - -A `--filter` whose entire result is empty (typo, glob with no match, …) prints the warning and exits 0 — pnpm's default. Previously this errored with `Task "build" not found`. - -## `vt run --filter nonexistent build` - -``` -No packages matched the filter: nonexistent -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/multiple_unmatched_filters_warn_individually.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/multiple_unmatched_filters_warn_individually.md deleted file mode 100644 index 823e94385..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/multiple_unmatched_filters_warn_individually.md +++ /dev/null @@ -1,12 +0,0 @@ -# multiple_unmatched_filters_warn_individually - -Each unmatched `--filter` should produce its own warning — warnings must not be collapsed into a single message. - -## `vt run --filter @test/app --filter nope1 --filter nope2 build` - -``` -No packages matched the filter: nope1 -No packages matched the filter: nope2 -~/packages/app$ vtt print built-app -built-app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/nested_filter_no_match_succeeds_by_default.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/nested_filter_no_match_succeeds_by_default.md deleted file mode 100644 index 4e1f8d1a9..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/nested_filter_no_match_succeeds_by_default.md +++ /dev/null @@ -1,11 +0,0 @@ -# nested_filter_no_match_succeeds_by_default - -The default-success rule must apply to nested `vt run --filter ...` invocations too: a task whose command is `vt run --filter nonexistent build` should exit 0 with the warning, not fail the outer task. Guards the script wrapping that pnpm users typically write. - -## `vt run filter-nonexistent` - -``` -No packages matched the filter: nonexistent ---- -vt run: 0/0 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/nested_filter_no_match_with_fail_if_no_match_errors.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/nested_filter_no_match_with_fail_if_no_match_errors.md deleted file mode 100644 index 1309372f3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/nested_filter_no_match_with_fail_if_no_match_errors.md +++ /dev/null @@ -1,12 +0,0 @@ -# nested_filter_no_match_with_fail_if_no_match_errors - -Strict mode also propagates through nesting: a task command that adds `--fail-if-no-match` aborts the outer task when the nested filter selects nothing. The error chain identifies both the nested command and the unmatched filter source. - -## `vt run filter-nonexistent-strict` - -**Exit code:** 1 - -``` -error: Failed to plan tasks from `vt run --filter nonexistent --fail-if-no-match build` in task filter-unmatched-test#filter-nonexistent-strict -* No packages matched the filter: nonexistent -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/partial_match_warns_for_unmatched_filter.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/partial_match_warns_for_unmatched_filter.md deleted file mode 100644 index 0a4108b49..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/partial_match_warns_for_unmatched_filter.md +++ /dev/null @@ -1,11 +0,0 @@ -# partial_match_warns_for_unmatched_filter - -When some `--filter` flags match and one does not, the run should proceed and warn only for the unmatched filter. - -## `vt run --filter @test/app --filter nonexistent build` - -``` -No packages matched the filter: nonexistent -~/packages/app$ vtt print built-app -built-app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/traversal_collapsed_to_empty_exits_zero_by_default.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/traversal_collapsed_to_empty_exits_zero_by_default.md deleted file mode 100644 index 536d4e9d1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/traversal_collapsed_to_empty_exits_zero_by_default.md +++ /dev/null @@ -1,9 +0,0 @@ -# traversal_collapsed_to_empty_exits_zero_by_default - -`{.}^...` selects the dependencies of the current package, excluding itself. On a leaf with no workspace deps the expression collapses to zero matches — a legitimate no-op rather than a typo — so the run warns and exits 0. - -## `vt run --filter {.}^... build` - -``` -No packages matched the filter: {.}^... -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/unmatched_directory_filter_warns.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/unmatched_directory_filter_warns.md deleted file mode 100644 index 1fd6c103a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/unmatched_directory_filter_warns.md +++ /dev/null @@ -1,11 +0,0 @@ -# unmatched_directory_filter_warns - -A directory-style `--filter` (`./packages/...`) that points nowhere should warn just like an unmatched name filter. - -## `vt run --filter @test/app --filter ./packages/nope build` - -``` -No packages matched the filter: ./packages/nope -~/packages/app$ vtt print built-app -built-app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/unmatched_exclusion_filter_does_not_warn.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/unmatched_exclusion_filter_does_not_warn.md deleted file mode 100644 index cf69e10ae..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/unmatched_exclusion_filter_does_not_warn.md +++ /dev/null @@ -1,10 +0,0 @@ -# unmatched_exclusion_filter_does_not_warn - -An exclusion filter (`!...`) that matches nothing should be silent; only unmatched inclusion filters warn. - -## `vt run --filter @test/app --filter !nonexistent build` - -``` -~/packages/app$ vtt print built-app -built-app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/unmatched_glob_filter_warns.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/unmatched_glob_filter_warns.md deleted file mode 100644 index 287b46dd3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/unmatched_glob_filter_warns.md +++ /dev/null @@ -1,11 +0,0 @@ -# unmatched_glob_filter_warns - -A glob `--filter` that matches no packages should warn like any other unmatched inclusion filter. - -## `vt run --filter @test/app --filter @nope/* build` - -``` -No packages matched the filter: @nope/* -~/packages/app$ vtt print built-app -built-app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/whitespace_split_filter_warns_for_unmatched_token.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/whitespace_split_filter_warns_for_unmatched_token.md deleted file mode 100644 index b44f34be1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/snapshots/whitespace_split_filter_warns_for_unmatched_token.md +++ /dev/null @@ -1,11 +0,0 @@ -# whitespace_split_filter_warns_for_unmatched_token - -A whitespace-split `--filter` argument should warn about each of its individual unmatched tokens. - -## `vt run --filter '@test/app nope' build` - -``` -No packages matched the filter: nope -~/packages/app$ vtt print built-app -built-app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/vite-task.json deleted file mode 100644 index c895e5e6c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/filter_unmatched/vite-task.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "cache": true, - "tasks": { - "filter-nonexistent": { - "command": "vt run --filter nonexistent build", - "cache": false - }, - "filter-nonexistent-strict": { - "command": "vt run --filter nonexistent --fail-if-no-match build", - "cache": false - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/other/other.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/other/other.ts deleted file mode 100644 index 95b7e3180..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/other/other.ts +++ /dev/null @@ -1 +0,0 @@ -export const rootOther = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/package.json deleted file mode 100644 index 62b9c6f60..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "glob-base-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/other/other.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/other/other.ts deleted file mode 100644 index bba04996f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/other/other.ts +++ /dev/null @@ -1 +0,0 @@ -export const other = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/package.json deleted file mode 100644 index e0caa109d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "sub-pkg", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/src/sub.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/src/sub.ts deleted file mode 100644 index 27a97907d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/src/sub.ts +++ /dev/null @@ -1 +0,0 @@ -export const sub = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/vite-task.json deleted file mode 100644 index a0556a20c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/packages/sub-pkg/vite-task.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "tasks": { - "sub-glob-test": { - "command": "vtt print-file src/sub.ts", - "input": ["src/**/*.ts"], - "cache": true - }, - "sub-glob-with-cwd": { - "command": "vtt print-file sub.ts", - "cwd": "src", - "input": ["src/**/*.ts"], - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots.toml deleted file mode 100644 index 5cc1d0204..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots.toml +++ /dev/null @@ -1,103 +0,0 @@ -[[e2e]] -name = "root_glob___matches_src_files" -comment = """ -A root-package glob should match files under the package's own `src/` — modifying such a file invalidates the cache. -""" -steps = [ - ["vt", "run", "root-glob-test"], - # Modify matched file - ["vtt", "replace-file-content", "src/root.ts", "initial", "modified"], - # Cache miss: file matches glob - ["vt", "run", "root-glob-test"], -] - -[[e2e]] -name = "root_glob___unmatched_directory" -comment = """ -Modifying a file outside the glob pattern (e.g. `other/`) should leave the cache hit intact. -""" -steps = [ - ["vt", "run", "root-glob-test"], - # Modify file outside glob pattern - ["vtt", "replace-file-content", "other/other.ts", "initial", "modified"], - # Cache hit: file doesn't match glob - ["vt", "run", "root-glob-test"], -] - -[[e2e]] -name = "root_glob___subpackage_path_unmatched_by_relative_glob" -comment = """ -A relative glob anchored at the root package should not reach into `packages//src/` — a subpackage file change stays a cache hit. -""" -steps = [ - ["vt", "run", "root-glob-test"], - # Modify file in subpackage - relative glob src/** doesn't reach packages/sub-pkg/src/ - ["vtt", "replace-file-content", "packages/sub-pkg/src/sub.ts", "initial", "modified"], - # Cache hit: path simply doesn't match the relative glob pattern - ["vt", "run", "root-glob-test"], -] - -[[e2e]] -name = "root_glob_with_cwd___glob_relative_to_package_not_cwd" -comment = """ -Even when the task declares a custom `cwd`, its glob stays anchored at the package root — `src/**` still matches `src/root.ts`. -""" -steps = [ - ["vt", "run", "root-glob-with-cwd"], - # Modify file - glob is src/** relative to package root - ["vtt", "replace-file-content", "src/root.ts", "initial", "modified"], - # Cache miss: file matches glob (relative to package, not cwd) - ["vt", "run", "root-glob-with-cwd"], -] - -[[e2e]] -name = "subpackage_glob___matches_own_src_files" -comment = """ -A subpackage's glob should match files under its own `src/` directory. -""" -steps = [ - ["vt", "run", "sub-pkg#sub-glob-test"], - # Modify matched file in subpackage - ["vtt", "replace-file-content", "packages/sub-pkg/src/sub.ts", "initial", "modified"], - # Cache miss: file matches subpackage's glob - ["vt", "run", "sub-pkg#sub-glob-test"], -] - -[[e2e]] -name = "subpackage_glob___unmatched_directory_in_subpackage" -comment = """ -Changes to a sibling directory within the subpackage that the glob does not cover (e.g. `other/`) should leave the cache hit intact. -""" -steps = [ - ["vt", "run", "sub-pkg#sub-glob-test"], - # Modify file outside glob pattern - ["vtt", "replace-file-content", "packages/sub-pkg/other/other.ts", "initial", "modified"], - # Cache hit: file doesn't match glob - ["vt", "run", "sub-pkg#sub-glob-test"], -] - -[[e2e]] -name = "subpackage_glob___root_path_unmatched_by_relative_glob" -comment = """ -A subpackage-anchored relative glob should not reach up into the root package's `src/` — a root-package file change stays a cache hit. -""" -steps = [ - ["vt", "run", "sub-pkg#sub-glob-test"], - # Modify file in root - relative glob src/** is resolved from subpackage dir - ["vtt", "replace-file-content", "src/root.ts", "initial", "modified"], - # Cache hit: path simply doesn't match the relative glob pattern - ["vt", "run", "sub-pkg#sub-glob-test"], -] - -[[e2e]] -name = "subpackage_glob_with_cwd___glob_relative_to_package_not_cwd" -comment = """ -A custom `cwd` on a subpackage task should not shift its glob base — `src/**` still resolves relative to the subpackage directory. -""" -steps = [ - ["vt", "run", "sub-pkg#sub-glob-with-cwd"], - # Modify file - glob is src/** relative to subpackage root - ["vtt", "replace-file-content", "packages/sub-pkg/src/sub.ts", "initial", "modified"], - # Cache miss: file matches glob (relative to subpackage, not cwd) - ["vt", "run", "sub-pkg#sub-glob-with-cwd"], -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob___matches_src_files.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob___matches_src_files.md deleted file mode 100644 index a471c3f69..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob___matches_src_files.md +++ /dev/null @@ -1,22 +0,0 @@ -# root_glob___matches_src_files - -A root-package glob should match files under the package's own `src/` — modifying such a file invalidates the cache. - -## `vt run root-glob-test` - -``` -$ vtt print-file src/root.ts -export const root = 'initial'; -``` - -## `vtt replace-file-content src/root.ts initial modified` - -``` -``` - -## `vt run root-glob-test` - -``` -$ vtt print-file src/root.ts ○ cache miss: 'src/root.ts' modified, executing -export const root = 'modified'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob___subpackage_path_unmatched_by_relative_glob.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob___subpackage_path_unmatched_by_relative_glob.md deleted file mode 100644 index 3fe5fe332..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob___subpackage_path_unmatched_by_relative_glob.md +++ /dev/null @@ -1,25 +0,0 @@ -# root_glob___subpackage_path_unmatched_by_relative_glob - -A relative glob anchored at the root package should not reach into `packages//src/` — a subpackage file change stays a cache hit. - -## `vt run root-glob-test` - -``` -$ vtt print-file src/root.ts -export const root = 'initial'; -``` - -## `vtt replace-file-content packages/sub-pkg/src/sub.ts initial modified` - -``` -``` - -## `vt run root-glob-test` - -``` -$ vtt print-file src/root.ts ◉ cache hit, replaying -export const root = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob___unmatched_directory.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob___unmatched_directory.md deleted file mode 100644 index 55ba42295..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob___unmatched_directory.md +++ /dev/null @@ -1,25 +0,0 @@ -# root_glob___unmatched_directory - -Modifying a file outside the glob pattern (e.g. `other/`) should leave the cache hit intact. - -## `vt run root-glob-test` - -``` -$ vtt print-file src/root.ts -export const root = 'initial'; -``` - -## `vtt replace-file-content other/other.ts initial modified` - -``` -``` - -## `vt run root-glob-test` - -``` -$ vtt print-file src/root.ts ◉ cache hit, replaying -export const root = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob_with_cwd___glob_relative_to_package_not_cwd.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob_with_cwd___glob_relative_to_package_not_cwd.md deleted file mode 100644 index 21e744b1f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/root_glob_with_cwd___glob_relative_to_package_not_cwd.md +++ /dev/null @@ -1,22 +0,0 @@ -# root_glob_with_cwd___glob_relative_to_package_not_cwd - -Even when the task declares a custom `cwd`, its glob stays anchored at the package root — `src/**` still matches `src/root.ts`. - -## `vt run root-glob-with-cwd` - -``` -~/src$ vtt print-file root.ts -export const root = 'initial'; -``` - -## `vtt replace-file-content src/root.ts initial modified` - -``` -``` - -## `vt run root-glob-with-cwd` - -``` -~/src$ vtt print-file root.ts ○ cache miss: 'src/root.ts' modified, executing -export const root = 'modified'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob___matches_own_src_files.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob___matches_own_src_files.md deleted file mode 100644 index 94b87e34f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob___matches_own_src_files.md +++ /dev/null @@ -1,22 +0,0 @@ -# subpackage_glob___matches_own_src_files - -A subpackage's glob should match files under its own `src/` directory. - -## `vt run sub-pkg#sub-glob-test` - -``` -~/packages/sub-pkg$ vtt print-file src/sub.ts -export const sub = 'initial'; -``` - -## `vtt replace-file-content packages/sub-pkg/src/sub.ts initial modified` - -``` -``` - -## `vt run sub-pkg#sub-glob-test` - -``` -~/packages/sub-pkg$ vtt print-file src/sub.ts ○ cache miss: 'packages/sub-pkg/src/sub.ts' modified, executing -export const sub = 'modified'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob___root_path_unmatched_by_relative_glob.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob___root_path_unmatched_by_relative_glob.md deleted file mode 100644 index a77d1c093..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob___root_path_unmatched_by_relative_glob.md +++ /dev/null @@ -1,25 +0,0 @@ -# subpackage_glob___root_path_unmatched_by_relative_glob - -A subpackage-anchored relative glob should not reach up into the root package's `src/` — a root-package file change stays a cache hit. - -## `vt run sub-pkg#sub-glob-test` - -``` -~/packages/sub-pkg$ vtt print-file src/sub.ts -export const sub = 'initial'; -``` - -## `vtt replace-file-content src/root.ts initial modified` - -``` -``` - -## `vt run sub-pkg#sub-glob-test` - -``` -~/packages/sub-pkg$ vtt print-file src/sub.ts ◉ cache hit, replaying -export const sub = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob___unmatched_directory_in_subpackage.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob___unmatched_directory_in_subpackage.md deleted file mode 100644 index 9f96ba6db..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob___unmatched_directory_in_subpackage.md +++ /dev/null @@ -1,25 +0,0 @@ -# subpackage_glob___unmatched_directory_in_subpackage - -Changes to a sibling directory within the subpackage that the glob does not cover (e.g. `other/`) should leave the cache hit intact. - -## `vt run sub-pkg#sub-glob-test` - -``` -~/packages/sub-pkg$ vtt print-file src/sub.ts -export const sub = 'initial'; -``` - -## `vtt replace-file-content packages/sub-pkg/other/other.ts initial modified` - -``` -``` - -## `vt run sub-pkg#sub-glob-test` - -``` -~/packages/sub-pkg$ vtt print-file src/sub.ts ◉ cache hit, replaying -export const sub = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob_with_cwd___glob_relative_to_package_not_cwd.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob_with_cwd___glob_relative_to_package_not_cwd.md deleted file mode 100644 index db5acbabf..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/snapshots/subpackage_glob_with_cwd___glob_relative_to_package_not_cwd.md +++ /dev/null @@ -1,22 +0,0 @@ -# subpackage_glob_with_cwd___glob_relative_to_package_not_cwd - -A custom `cwd` on a subpackage task should not shift its glob base — `src/**` still resolves relative to the subpackage directory. - -## `vt run sub-pkg#sub-glob-with-cwd` - -``` -~/packages/sub-pkg/src$ vtt print-file sub.ts -export const sub = 'initial'; -``` - -## `vtt replace-file-content packages/sub-pkg/src/sub.ts initial modified` - -``` -``` - -## `vt run sub-pkg#sub-glob-with-cwd` - -``` -~/packages/sub-pkg/src$ vtt print-file sub.ts ○ cache miss: 'packages/sub-pkg/src/sub.ts' modified, executing -export const sub = 'modified'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/src/root.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/src/root.ts deleted file mode 100644 index 96424f317..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/src/root.ts +++ /dev/null @@ -1 +0,0 @@ -export const root = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/vite-task.json deleted file mode 100644 index 548da075a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/glob_base_test/vite-task.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "tasks": { - "root-glob-test": { - "command": "vtt print-file src/root.ts", - "input": ["src/**/*.ts"], - "cache": true - }, - "root-glob-with-cwd": { - "command": "vtt print-file root.ts", - "cwd": "src", - "input": ["src/**/*.ts"], - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/package.json deleted file mode 100644 index 21dde7e82..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "grouped-stdio-test", - "private": true, - "dependencies": { - "other": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/packages/other/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/packages/other/package.json deleted file mode 100644 index 6bf03d25d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/packages/other/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "other", - "scripts": { - "check-tty": "vtt check-tty", - "check-tty-cached": "vtt check-tty", - "read-stdin": "vtt read-stdin" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots.toml deleted file mode 100644 index 6b92f7b5a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots.toml +++ /dev/null @@ -1,76 +0,0 @@ -[[e2e]] -name = "single_task__cache_off__grouped_output" -comment = """ -Under `--log=grouped` with caching off, a single task's piped stdout/stderr should be printed as one grouped block; none of the fds should be TTYs. -""" -steps = [["vt", "run", "--log=grouped", "check-tty"]] - -[[e2e]] -name = "multiple_tasks__cache_off__grouped_output" -comment = """ -Under `--log=grouped` with caching off, each task's output should be emitted as its own grouped block (one block per task, never interleaved). -""" -steps = [["vt", "run", "--log=grouped", "-r", "check-tty"]] - -[[e2e]] -name = "single_task__cache_miss__grouped_output" -comment = """ -On a cache miss, grouped mode should still pipe stdio and emit one block for the single task. -""" -steps = [["vt", "run", "--log=grouped", "check-tty-cached"]] - -[[e2e]] -name = "multiple_tasks__cache_miss__grouped_output" -comment = """ -On a cache miss across multiple tasks, grouped mode should emit one block per task with piped, non-TTY stdio. -""" -steps = [["vt", "run", "--log=grouped", "-r", "check-tty-cached"]] - -[[e2e]] -name = "single_task__cache_hit__replayed" -comment = """ -A cache-hit replay of a single task under grouped mode should reproduce the original grouped block from cached output. -""" -steps = [ - [ - "vt", - "run", - "--log=grouped", - "check-tty-cached", - ], - [ - "vt", - "run", - "--log=grouped", - "check-tty-cached", - ], -] - -[[e2e]] -name = "multiple_tasks__cache_hit__replayed" -comment = """ -Cache-hit replays for multiple tasks under grouped mode should each produce their own block, matching the original (non-replayed) output. -""" -steps = [ - [ - "vt", - "run", - "--log=grouped", - "-r", - "check-tty-cached", - ], - [ - "vt", - "run", - "--log=grouped", - "-r", - "check-tty-cached", - ], -] - -[[e2e]] -name = "stdin_is_always_null" -comment = """ -In grouped mode, task stdin is always `/dev/null` regardless of the parent's stdin — piping data in from outside must not reach the task. -""" -steps = [["vtt", "pipe-stdin", "from-stdin", "--", "vt", "run", "--log=grouped", "read-stdin"]] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/multiple_tasks__cache_hit__replayed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/multiple_tasks__cache_hit__replayed.md deleted file mode 100644 index 2ae78751c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/multiple_tasks__cache_hit__replayed.md +++ /dev/null @@ -1,41 +0,0 @@ -# multiple_tasks__cache_hit__replayed - -Cache-hit replays for multiple tasks under grouped mode should each produce their own block, matching the original (non-replayed) output. - -## `vt run --log=grouped -r check-tty-cached` - -``` -[other#check-tty-cached] ~/packages/other$ vtt check-tty -── [other#check-tty-cached] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty - -[grouped-stdio-test#check-tty-cached] $ vtt check-tty -── [grouped-stdio-test#check-tty-cached] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` - -## `vt run --log=grouped -r check-tty-cached` - -``` -[other#check-tty-cached] ~/packages/other$ vtt check-tty ◉ cache hit, replaying -── [other#check-tty-cached] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty - -[grouped-stdio-test#check-tty-cached] $ vtt check-tty ◉ cache hit, replaying -── [grouped-stdio-test#check-tty-cached] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty - ---- -vt run: 2/2 cache hit (100%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/multiple_tasks__cache_miss__grouped_output.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/multiple_tasks__cache_miss__grouped_output.md deleted file mode 100644 index 5f1d5b845..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/multiple_tasks__cache_miss__grouped_output.md +++ /dev/null @@ -1,22 +0,0 @@ -# multiple_tasks__cache_miss__grouped_output - -On a cache miss across multiple tasks, grouped mode should emit one block per task with piped, non-TTY stdio. - -## `vt run --log=grouped -r check-tty-cached` - -``` -[other#check-tty-cached] ~/packages/other$ vtt check-tty -── [other#check-tty-cached] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty - -[grouped-stdio-test#check-tty-cached] $ vtt check-tty -── [grouped-stdio-test#check-tty-cached] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/multiple_tasks__cache_off__grouped_output.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/multiple_tasks__cache_off__grouped_output.md deleted file mode 100644 index 9515a2ba4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/multiple_tasks__cache_off__grouped_output.md +++ /dev/null @@ -1,22 +0,0 @@ -# multiple_tasks__cache_off__grouped_output - -Under `--log=grouped` with caching off, each task's output should be emitted as its own grouped block (one block per task, never interleaved). - -## `vt run --log=grouped -r check-tty` - -``` -[other#check-tty] ~/packages/other$ vtt check-tty -── [other#check-tty] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty - -[grouped-stdio-test#check-tty] $ vtt check-tty ⊘ cache disabled -── [grouped-stdio-test#check-tty] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/single_task__cache_hit__replayed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/single_task__cache_hit__replayed.md deleted file mode 100644 index b36efa32d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/single_task__cache_hit__replayed.md +++ /dev/null @@ -1,26 +0,0 @@ -# single_task__cache_hit__replayed - -A cache-hit replay of a single task under grouped mode should reproduce the original grouped block from cached output. - -## `vt run --log=grouped check-tty-cached` - -``` -[grouped-stdio-test#check-tty-cached] $ vtt check-tty -── [grouped-stdio-test#check-tty-cached] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty -``` - -## `vt run --log=grouped check-tty-cached` - -``` -[grouped-stdio-test#check-tty-cached] $ vtt check-tty ◉ cache hit, replaying -── [grouped-stdio-test#check-tty-cached] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/single_task__cache_miss__grouped_output.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/single_task__cache_miss__grouped_output.md deleted file mode 100644 index 0f8d3de4c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/single_task__cache_miss__grouped_output.md +++ /dev/null @@ -1,13 +0,0 @@ -# single_task__cache_miss__grouped_output - -On a cache miss, grouped mode should still pipe stdio and emit one block for the single task. - -## `vt run --log=grouped check-tty-cached` - -``` -[grouped-stdio-test#check-tty-cached] $ vtt check-tty -── [grouped-stdio-test#check-tty-cached] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/single_task__cache_off__grouped_output.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/single_task__cache_off__grouped_output.md deleted file mode 100644 index b2353a42b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/single_task__cache_off__grouped_output.md +++ /dev/null @@ -1,13 +0,0 @@ -# single_task__cache_off__grouped_output - -Under `--log=grouped` with caching off, a single task's piped stdout/stderr should be printed as one grouped block; none of the fds should be TTYs. - -## `vt run --log=grouped check-tty` - -``` -[grouped-stdio-test#check-tty] $ vtt check-tty ⊘ cache disabled -── [grouped-stdio-test#check-tty] ── -stdin:not-tty -stdout:not-tty -stderr:not-tty -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/stdin_is_always_null.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/stdin_is_always_null.md deleted file mode 100644 index 620f0bfb5..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/snapshots/stdin_is_always_null.md +++ /dev/null @@ -1,9 +0,0 @@ -# stdin_is_always_null - -In grouped mode, task stdin is always `/dev/null` regardless of the parent's stdin — piping data in from outside must not reach the task. - -## `vtt pipe-stdin from-stdin -- vt run --log=grouped read-stdin` - -``` -[grouped-stdio-test#read-stdin] $ vtt read-stdin ⊘ cache disabled -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/vite-task.json deleted file mode 100644 index 9f6a560f4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/grouped_stdio/vite-task.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "cache": true, - "tasks": { - "check-tty": { - "command": "vtt check-tty", - "cache": false - }, - "check-tty-cached": { - "command": "vtt check-tty", - "cache": true - }, - "read-stdin": { - "command": "vtt read-stdin", - "cache": false - }, - "read-stdin-cached": { - "command": "vtt read-stdin", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/package.json deleted file mode 100644 index bd4ec99f7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "say": "vtt print" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/snapshots.toml deleted file mode 100644 index ae50c7acd..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/snapshots.toml +++ /dev/null @@ -1,31 +0,0 @@ -[[e2e]] -name = "individual_cache_for_extra_args" -comment = """ -Tests that different extra args get separate cache entries -""" -steps = [ - { argv = [ - "vt", - "run", - "say", - "a", - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "say", - "b", - ], comment = "cache miss, different args" }, - { argv = [ - "vt", - "run", - "say", - "a", - ], comment = "cache hit" }, - { argv = [ - "vt", - "run", - "say", - "b", - ], comment = "cache hit" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/snapshots/individual_cache_for_extra_args.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/snapshots/individual_cache_for_extra_args.md deleted file mode 100644 index 96f3d2828..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/snapshots/individual_cache_for_extra_args.md +++ /dev/null @@ -1,45 +0,0 @@ -# individual_cache_for_extra_args - -Tests that different extra args get separate cache entries - -## `vt run say a` - -cache miss - -``` -$ vtt print a -a -``` - -## `vt run say b` - -cache miss, different args - -``` -$ vtt print b -b -``` - -## `vt run say a` - -cache hit - -``` -$ vtt print a ◉ cache hit, replaying -a - ---- -vt run: cache hit. -``` - -## `vt run say b` - -cache hit - -``` -$ vtt print b ◉ cache hit, replaying -b - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_adt_args/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/package.json deleted file mode 100644 index 0967ef424..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/package.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/snapshots.toml deleted file mode 100644 index 3dd8f85a8..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/snapshots.toml +++ /dev/null @@ -1,47 +0,0 @@ -[[e2e]] -name = "individual_cache_for_env" -comment = """ -Tests that different env values get separate cache entries -""" -steps = [ - { argv = [ - "vt", - "run", - "hello", - ], envs = [ - [ - "FOO", - "1", - ], - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "hello", - ], envs = [ - [ - "FOO", - "2", - ], - ], comment = "cache miss, different env" }, - { argv = [ - "vt", - "run", - "hello", - ], envs = [ - [ - "FOO", - "1", - ], - ], comment = "cache hit" }, - { argv = [ - "vt", - "run", - "hello", - ], envs = [ - [ - "FOO", - "2", - ], - ], comment = "cache hit" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/snapshots/individual_cache_for_env.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/snapshots/individual_cache_for_env.md deleted file mode 100644 index 2288fdaf1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/snapshots/individual_cache_for_env.md +++ /dev/null @@ -1,45 +0,0 @@ -# individual_cache_for_env - -Tests that different env values get separate cache entries - -## `FOO=1 vt run hello` - -cache miss - -``` -$ vtt print-env FOO -1 -``` - -## `FOO=2 vt run hello` - -cache miss, different env - -``` -$ vtt print-env FOO ○ cache miss: env 'FOO' changed, executing -2 -``` - -## `FOO=1 vt run hello` - -cache hit - -``` -$ vtt print-env FOO ◉ cache hit, replaying -1 - ---- -vt run: cache hit. -``` - -## `FOO=2 vt run hello` - -cache hit - -``` -$ vtt print-env FOO ◉ cache hit, replaying -2 - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/vite-task.json deleted file mode 100644 index b4cb0e1b8..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/individual_cache_for_env/vite-task.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cache": true, - "tasks": { - "hello": { - "command": "vtt print-env FOO", - "env": ["FOO"], - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/dist/output.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/dist/output.js deleted file mode 100644 index d8d5d2108..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/dist/output.js +++ /dev/null @@ -1 +0,0 @@ -// initial output diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/package.json deleted file mode 100644 index 1d369f444..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "inputs-cache-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots.toml deleted file mode 100644 index 54ebae828..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots.toml +++ /dev/null @@ -1,291 +0,0 @@ -[[e2e]] -name = "positive_globs_only___cache_hit_on_second_run" -comment = """ -With only positive globs (`src/**/*.ts`) and no file changes, a second run should be a cache hit. -""" -steps = [ - # First run - cache miss - ["vt", "run", "positive-globs-only"], - # Second run - cache hit - ["vt", "run", "positive-globs-only"], -] - -[[e2e]] -name = "positive_globs_only___miss_on_matched_file_change" -comment = """ -Modifying a file matched by a positive glob should invalidate the cache. -""" -steps = [ - # Initial run - ["vt", "run", "positive-globs-only"], - # Modify a file that matches the glob - ["vtt", "replace-file-content", "src/main.ts", "initial", "modified"], - # Cache miss: matched file changed - ["vt", "run", "positive-globs-only"], -] - -[[e2e]] -name = "positive_globs_only___hit_on_unmatched_file_change" -comment = """ -Modifying a file outside the positive glob (e.g. under `test/`) should not invalidate the cache. -""" -steps = [ - # Initial run - ["vt", "run", "positive-globs-only"], - # Modify a file that does NOT match the glob (test/ is outside src/) - ["vtt", "replace-file-content", "test/main.test.ts", "outside", "modified"], - # Cache hit: file not in input - ["vt", "run", "positive-globs-only"], -] - -[[e2e]] -name = "positive_globs___hit_on_read_but_unmatched_file" -comment = """ -With only positive globs (no `auto`), files read at runtime but not matched by the glob should not be fingerprinted — inference is truly disabled. -""" -steps = [ - # Initial run - reads both src/main.ts and src/utils.ts - ["vt", "run", "positive-globs-reads-unmatched"], - # Modify utils.ts - read by command but NOT in glob - ["vtt", "replace-file-content", "src/utils.ts", "initial", "modified"], - # Cache hit: file was read but not matched by glob, inference is off - ["vt", "run", "positive-globs-reads-unmatched"], -] - -[[e2e]] -name = "positive_negative_globs___miss_on_non_excluded_file" -comment = """ -A file matched by a positive glob and not by any negative glob should still invalidate the cache when modified. -""" -steps = [ - # Initial run - ["vt", "run", "positive-negative-globs"], - # Modify a file that matches positive but NOT negative - ["vtt", "replace-file-content", "src/main.ts", "initial", "modified"], - # Cache miss: file changed - ["vt", "run", "positive-negative-globs"], -] - -[[e2e]] -name = "positive_negative_globs___hit_on_excluded_file" -comment = """ -A file matched by the negative glob should be excluded from fingerprinting even if it also matches a positive glob. -""" -steps = [ - # Initial run - ["vt", "run", "positive-negative-globs"], - # Modify a file that matches the negative glob (excluded) - ["vtt", "replace-file-content", "src/main.test.ts", "main", "modified"], - # Cache hit: file excluded by negative glob - ["vt", "run", "positive-negative-globs"], -] - -[[e2e]] -name = "auto_only___miss_on_inferred_file_change" -comment = """ -With `auto: true`, files read by the command (via fspy) should be fingerprinted and a change to such a file should invalidate the cache. -""" -steps = [ - # Initial run - reads src/main.ts - ["vt", "run", "auto-only"], - # Modify the file that was read - ["vtt", "replace-file-content", "src/main.ts", "initial", "modified"], - # Cache miss: inferred input changed - ["vt", "run", "auto-only"], -] - -[[e2e]] -name = "auto_only___hit_on_non_inferred_file_change" -comment = """ -With `auto: true`, a file never read by the command should not affect the cache, even if it sits next to files that are read. -""" -steps = [ - # Initial run - reads src/main.ts (NOT utils.ts) - ["vt", "run", "auto-only"], - # Modify a file that was NOT read by the command - ["vtt", "replace-file-content", "src/utils.ts", "initial", "modified"], - # Cache hit: file not in inferred input - ["vt", "run", "auto-only"], -] - -[[e2e]] -name = "auto_with_negative___hit_on_excluded_inferred_file" -comment = """ -A negative glob should filter out inferred inputs — a `dist/**`-excluded file that the command happens to read should not invalidate the cache. -""" -steps = [ - # Initial run - reads both src/main.ts and dist/output.js - ["vt", "run", "auto-with-negative"], - # Modify file in excluded directory (dist/) - ["vtt", "replace-file-content", "dist/output.js", "initial", "modified"], - # Cache hit: dist/ is excluded by negative glob - ["vt", "run", "auto-with-negative"], -] - -[[e2e]] -name = "auto_with_negative___miss_on_non_excluded_inferred_file" -comment = """ -With `auto: true` plus a negative glob, changes to inferred inputs outside the excluded range should still invalidate the cache. -""" -steps = [ - # Initial run - ["vt", "run", "auto-with-negative"], - # Modify file NOT in excluded directory - ["vtt", "replace-file-content", "src/main.ts", "initial", "modified"], - # Cache miss: inferred input changed - ["vt", "run", "auto-with-negative"], -] - -[[e2e]] -name = "positive_auto_negative___miss_on_explicit_glob_file" -comment = """ -When positive, `auto`, and negative globs are combined, modifying a file matched by the explicit positive glob should invalidate the cache. -""" -steps = [ - # Initial run - ["vt", "run", "positive-auto-negative"], - # Modify explicit input file - ["vtt", "replace-file-content", "package.json", "inputs-cache-test", "modified-pkg"], - # Cache miss: explicit input changed - ["vt", "run", "positive-auto-negative"], -] - -[[e2e]] -name = "positive_auto_negative___miss_on_inferred_file" -comment = """ -Under combined positive + `auto` + negative config, modifying a file that only the `auto` inference discovered should still invalidate the cache. -""" -steps = [ - # Initial run - ["vt", "run", "positive-auto-negative"], - # Modify inferred input file (read by command) - ["vtt", "replace-file-content", "src/main.ts", "initial", "modified"], - # Cache miss: inferred input changed - ["vt", "run", "positive-auto-negative"], -] - -[[e2e]] -name = "positive_auto_negative___hit_on_excluded_file" -comment = """ -Under combined positive + `auto` + negative config, a negative glob should still filter files contributed by either source. -""" -steps = [ - # Initial run - ["vt", "run", "positive-auto-negative"], - # Modify file excluded by negative glob - ["vtt", "replace-file-content", "src/main.test.ts", "main", "modified"], - # Cache hit: file excluded by negative glob - ["vt", "run", "positive-auto-negative"], -] - -[[e2e]] -name = "empty_input___hit_despite_file_changes" -comment = """ -With `input: []`, no file changes should ever invalidate the cache — only command/env changes can. -""" -steps = [ - # Initial run - ["vt", "run", "empty-inputs"], - # Modify any file - should NOT affect cache - ["vtt", "replace-file-content", "src/main.ts", "initial", "modified"], - # Cache hit: no input configured - ["vt", "run", "empty-inputs"], -] - -[[e2e]] -name = "empty_input___miss_on_command_change" -comment = """ -Even with `input: []`, changing the task's command should still invalidate the cache. -""" -steps = [ - # Initial run - [ - "vt", - "run", - "empty-inputs", - ], - # Change the command - [ - "vtt", - "replace-file-content", - "vite-task.json", - "vtt print-file ./src/main.ts", - "vtt print-file src/utils.ts", - ], - # Cache miss: command changed - [ - "vt", - "run", - "empty-inputs", - ], -] - -[[e2e]] -name = "fspy_env___set_when_auto_inference_enabled" -comment = """ -When auto-inference is enabled (input includes `{auto: true}`), the task process should see `FSPY=1` in its environment. -""" -steps = [ - # Run task with auto inference - should see FSPY=1 - ["vt", "run", "check-fspy-env-with-auto"], -] - -[[e2e]] -name = "fspy_env___not_set_when_auto_inference_disabled" -comment = """ -When both input and output auto-inference are disabled, the task process should not see `FSPY` set. -""" -steps = [ - # Run task without input or output auto inference - should see (undefined) - ["vt", "run", "check-fspy-env-without-auto"], -] - -[[e2e]] -name = "folder_slash_input___miss_on_direct_and_nested_file_changes" -comment = """ -A trailing-slash input (`src/`) should expand to `src/**`, so both direct and nested file changes under it invalidate the cache. -""" -steps = [ - ["vt", "run", "folder-slash-input"], - # Modify a direct file in src/ - ["vtt", "replace-file-content", "src/main.ts", "initial", "modified"], - # Cache miss: direct file changed - ["vt", "run", "folder-slash-input"], - # Reset and run again to re-establish cache - ["vtt", "replace-file-content", "src/main.ts", "modified", "initial"], - ["vt", "run", "folder-slash-input"], - # Modify a nested file in src/sub/ - ["vtt", "replace-file-content", "src/sub/nested.ts", "initial", "modified"], - # Cache miss: nested file changed - ["vt", "run", "folder-slash-input"], -] - -[[e2e]] -name = "folder_slash_input___hit_on_file_outside_directory" -comment = """ -With a trailing-slash input (`src/`), files outside the directory should not be part of the fingerprint. -""" -steps = [ - ["vt", "run", "folder-slash-input"], - # Modify a file outside src/ - ["vtt", "replace-file-content", "test/main.test.ts", "outside", "modified"], - # Cache hit: file not under src/ - ["vt", "run", "folder-slash-input"], -] - -[[e2e]] -name = "folder_input___hit_despite_file_changes_and_folder_deletion" -comment = """ -A bare directory name as input (`src`) matches only the directory entry, not its contents — neither file edits nor folder deletion affect the cache. -""" -steps = [ - ["vt", "run", "folder-input"], - # Modify a file inside the folder - ["vtt", "replace-file-content", "src/main.ts", "initial", "modified"], - # Cache hit: "src" matches the directory itself, not files inside it - ["vt", "run", "folder-input"], - # Delete the entire folder - ["vtt", "rm", "-rf", "src"], - # Cache hit: folder removal doesn't affect fingerprint - ["vt", "run", "folder-input"], -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_only___hit_on_non_inferred_file_change.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_only___hit_on_non_inferred_file_change.md deleted file mode 100644 index 3b8a440cb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_only___hit_on_non_inferred_file_change.md +++ /dev/null @@ -1,25 +0,0 @@ -# auto_only___hit_on_non_inferred_file_change - -With `auto: true`, a file never read by the command should not affect the cache, even if it sits next to files that are read. - -## `vt run auto-only` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content src/utils.ts initial modified` - -``` -``` - -## `vt run auto-only` - -``` -$ vtt print-file src/main.ts ◉ cache hit, replaying -export const main = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_only___miss_on_inferred_file_change.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_only___miss_on_inferred_file_change.md deleted file mode 100644 index 30a2c2aae..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_only___miss_on_inferred_file_change.md +++ /dev/null @@ -1,22 +0,0 @@ -# auto_only___miss_on_inferred_file_change - -With `auto: true`, files read by the command (via fspy) should be fingerprinted and a change to such a file should invalidate the cache. - -## `vt run auto-only` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content src/main.ts initial modified` - -``` -``` - -## `vt run auto-only` - -``` -$ vtt print-file src/main.ts ○ cache miss: 'src/main.ts' modified, executing -export const main = 'modified'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_with_negative___hit_on_excluded_inferred_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_with_negative___hit_on_excluded_inferred_file.md deleted file mode 100644 index 8d2c9ccea..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_with_negative___hit_on_excluded_inferred_file.md +++ /dev/null @@ -1,27 +0,0 @@ -# auto_with_negative___hit_on_excluded_inferred_file - -A negative glob should filter out inferred inputs — a `dist/**`-excluded file that the command happens to read should not invalidate the cache. - -## `vt run auto-with-negative` - -``` -$ vtt print-file src/main.ts dist/output.js -export const main = 'initial'; -// initial output -``` - -## `vtt replace-file-content dist/output.js initial modified` - -``` -``` - -## `vt run auto-with-negative` - -``` -$ vtt print-file src/main.ts dist/output.js ◉ cache hit, replaying -export const main = 'initial'; -// initial output - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_with_negative___miss_on_non_excluded_inferred_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_with_negative___miss_on_non_excluded_inferred_file.md deleted file mode 100644 index e1549d0b6..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/auto_with_negative___miss_on_non_excluded_inferred_file.md +++ /dev/null @@ -1,24 +0,0 @@ -# auto_with_negative___miss_on_non_excluded_inferred_file - -With `auto: true` plus a negative glob, changes to inferred inputs outside the excluded range should still invalidate the cache. - -## `vt run auto-with-negative` - -``` -$ vtt print-file src/main.ts dist/output.js -export const main = 'initial'; -// initial output -``` - -## `vtt replace-file-content src/main.ts initial modified` - -``` -``` - -## `vt run auto-with-negative` - -``` -$ vtt print-file src/main.ts dist/output.js ○ cache miss: 'src/main.ts' modified, executing -export const main = 'modified'; -// initial output -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/empty_input___hit_despite_file_changes.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/empty_input___hit_despite_file_changes.md deleted file mode 100644 index c265d6114..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/empty_input___hit_despite_file_changes.md +++ /dev/null @@ -1,25 +0,0 @@ -# empty_input___hit_despite_file_changes - -With `input: []`, no file changes should ever invalidate the cache — only command/env changes can. - -## `vt run empty-inputs` - -``` -$ vtt print-file ./src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content src/main.ts initial modified` - -``` -``` - -## `vt run empty-inputs` - -``` -$ vtt print-file ./src/main.ts ◉ cache hit, replaying -export const main = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/empty_input___miss_on_command_change.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/empty_input___miss_on_command_change.md deleted file mode 100644 index 143a379aa..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/empty_input___miss_on_command_change.md +++ /dev/null @@ -1,22 +0,0 @@ -# empty_input___miss_on_command_change - -Even with `input: []`, changing the task's command should still invalidate the cache. - -## `vt run empty-inputs` - -``` -$ vtt print-file ./src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content vite-task.json 'vtt print-file ./src/main.ts' 'vtt print-file src/utils.ts'` - -``` -``` - -## `vt run empty-inputs` - -``` -$ vtt print-file src/utils.ts ○ cache miss: args changed, executing -export const utils = 'initial'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/folder_input___hit_despite_file_changes_and_folder_deletion.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/folder_input___hit_despite_file_changes_and_folder_deletion.md deleted file mode 100644 index 5f157228b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/folder_input___hit_despite_file_changes_and_folder_deletion.md +++ /dev/null @@ -1,40 +0,0 @@ -# folder_input___hit_despite_file_changes_and_folder_deletion - -A bare directory name as input (`src`) matches only the directory entry, not its contents — neither file edits nor folder deletion affect the cache. - -## `vt run folder-input` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content src/main.ts initial modified` - -``` -``` - -## `vt run folder-input` - -``` -$ vtt print-file src/main.ts ◉ cache hit, replaying -export const main = 'initial'; - ---- -vt run: cache hit. -``` - -## `vtt rm -rf src` - -``` -``` - -## `vt run folder-input` - -``` -$ vtt print-file src/main.ts ◉ cache hit, replaying -export const main = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/folder_slash_input___hit_on_file_outside_directory.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/folder_slash_input___hit_on_file_outside_directory.md deleted file mode 100644 index 5b05675ad..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/folder_slash_input___hit_on_file_outside_directory.md +++ /dev/null @@ -1,25 +0,0 @@ -# folder_slash_input___hit_on_file_outside_directory - -With a trailing-slash input (`src/`), files outside the directory should not be part of the fingerprint. - -## `vt run folder-slash-input` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content test/main.test.ts outside modified` - -``` -``` - -## `vt run folder-slash-input` - -``` -$ vtt print-file src/main.ts ◉ cache hit, replaying -export const main = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/folder_slash_input___miss_on_direct_and_nested_file_changes.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/folder_slash_input___miss_on_direct_and_nested_file_changes.md deleted file mode 100644 index b9cce9b50..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/folder_slash_input___miss_on_direct_and_nested_file_changes.md +++ /dev/null @@ -1,46 +0,0 @@ -# folder_slash_input___miss_on_direct_and_nested_file_changes - -A trailing-slash input (`src/`) should expand to `src/**`, so both direct and nested file changes under it invalidate the cache. - -## `vt run folder-slash-input` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content src/main.ts initial modified` - -``` -``` - -## `vt run folder-slash-input` - -``` -$ vtt print-file src/main.ts ○ cache miss: 'src/main.ts' modified, executing -export const main = 'modified'; -``` - -## `vtt replace-file-content src/main.ts modified initial` - -``` -``` - -## `vt run folder-slash-input` - -``` -$ vtt print-file src/main.ts ○ cache miss: 'src/main.ts' modified, executing -export const main = 'initial'; -``` - -## `vtt replace-file-content src/sub/nested.ts initial modified` - -``` -``` - -## `vt run folder-slash-input` - -``` -$ vtt print-file src/main.ts ○ cache miss: 'src/sub/nested.ts' modified, executing -export const main = 'initial'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/fspy_env___not_set_when_auto_inference_disabled.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/fspy_env___not_set_when_auto_inference_disabled.md deleted file mode 100644 index c7a67a917..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/fspy_env___not_set_when_auto_inference_disabled.md +++ /dev/null @@ -1,10 +0,0 @@ -# fspy_env___not_set_when_auto_inference_disabled - -When both input and output auto-inference are disabled, the task process should not see `FSPY` set. - -## `vt run check-fspy-env-without-auto` - -``` -$ vtt print-env FSPY -(undefined) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/fspy_env___set_when_auto_inference_enabled.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/fspy_env___set_when_auto_inference_enabled.md deleted file mode 100644 index 83c253cd8..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/fspy_env___set_when_auto_inference_enabled.md +++ /dev/null @@ -1,10 +0,0 @@ -# fspy_env___set_when_auto_inference_enabled - -When auto-inference is enabled (input includes `{auto: true}`), the task process should see `FSPY=1` in its environment. - -## `vt run check-fspy-env-with-auto` - -``` -$ vtt print-env FSPY -1 -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_auto_negative___hit_on_excluded_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_auto_negative___hit_on_excluded_file.md deleted file mode 100644 index 1a9a5339b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_auto_negative___hit_on_excluded_file.md +++ /dev/null @@ -1,25 +0,0 @@ -# positive_auto_negative___hit_on_excluded_file - -Under combined positive + `auto` + negative config, a negative glob should still filter files contributed by either source. - -## `vt run positive-auto-negative` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content src/main.test.ts main modified` - -``` -``` - -## `vt run positive-auto-negative` - -``` -$ vtt print-file src/main.ts ◉ cache hit, replaying -export const main = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_auto_negative___miss_on_explicit_glob_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_auto_negative___miss_on_explicit_glob_file.md deleted file mode 100644 index 44f7676f4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_auto_negative___miss_on_explicit_glob_file.md +++ /dev/null @@ -1,22 +0,0 @@ -# positive_auto_negative___miss_on_explicit_glob_file - -When positive, `auto`, and negative globs are combined, modifying a file matched by the explicit positive glob should invalidate the cache. - -## `vt run positive-auto-negative` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content package.json inputs-cache-test modified-pkg` - -``` -``` - -## `vt run positive-auto-negative` - -``` -$ vtt print-file src/main.ts ○ cache miss: 'package.json' modified, executing -export const main = 'initial'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_auto_negative___miss_on_inferred_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_auto_negative___miss_on_inferred_file.md deleted file mode 100644 index 94ad7da12..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_auto_negative___miss_on_inferred_file.md +++ /dev/null @@ -1,22 +0,0 @@ -# positive_auto_negative___miss_on_inferred_file - -Under combined positive + `auto` + negative config, modifying a file that only the `auto` inference discovered should still invalidate the cache. - -## `vt run positive-auto-negative` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content src/main.ts initial modified` - -``` -``` - -## `vt run positive-auto-negative` - -``` -$ vtt print-file src/main.ts ○ cache miss: 'src/main.ts' modified, executing -export const main = 'modified'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs___hit_on_read_but_unmatched_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs___hit_on_read_but_unmatched_file.md deleted file mode 100644 index 69757277c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs___hit_on_read_but_unmatched_file.md +++ /dev/null @@ -1,27 +0,0 @@ -# positive_globs___hit_on_read_but_unmatched_file - -With only positive globs (no `auto`), files read at runtime but not matched by the glob should not be fingerprinted — inference is truly disabled. - -## `vt run positive-globs-reads-unmatched` - -``` -$ vtt print-file src/main.ts src/utils.ts -export const main = 'initial'; -export const utils = 'initial'; -``` - -## `vtt replace-file-content src/utils.ts initial modified` - -``` -``` - -## `vt run positive-globs-reads-unmatched` - -``` -$ vtt print-file src/main.ts src/utils.ts ◉ cache hit, replaying -export const main = 'initial'; -export const utils = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs_only___cache_hit_on_second_run.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs_only___cache_hit_on_second_run.md deleted file mode 100644 index 3f8713388..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs_only___cache_hit_on_second_run.md +++ /dev/null @@ -1,20 +0,0 @@ -# positive_globs_only___cache_hit_on_second_run - -With only positive globs (`src/**/*.ts`) and no file changes, a second run should be a cache hit. - -## `vt run positive-globs-only` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vt run positive-globs-only` - -``` -$ vtt print-file src/main.ts ◉ cache hit, replaying -export const main = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs_only___hit_on_unmatched_file_change.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs_only___hit_on_unmatched_file_change.md deleted file mode 100644 index 8e509893a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs_only___hit_on_unmatched_file_change.md +++ /dev/null @@ -1,25 +0,0 @@ -# positive_globs_only___hit_on_unmatched_file_change - -Modifying a file outside the positive glob (e.g. under `test/`) should not invalidate the cache. - -## `vt run positive-globs-only` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content test/main.test.ts outside modified` - -``` -``` - -## `vt run positive-globs-only` - -``` -$ vtt print-file src/main.ts ◉ cache hit, replaying -export const main = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs_only___miss_on_matched_file_change.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs_only___miss_on_matched_file_change.md deleted file mode 100644 index d2cf13256..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_globs_only___miss_on_matched_file_change.md +++ /dev/null @@ -1,22 +0,0 @@ -# positive_globs_only___miss_on_matched_file_change - -Modifying a file matched by a positive glob should invalidate the cache. - -## `vt run positive-globs-only` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content src/main.ts initial modified` - -``` -``` - -## `vt run positive-globs-only` - -``` -$ vtt print-file src/main.ts ○ cache miss: 'src/main.ts' modified, executing -export const main = 'modified'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_negative_globs___hit_on_excluded_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_negative_globs___hit_on_excluded_file.md deleted file mode 100644 index 495e93367..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_negative_globs___hit_on_excluded_file.md +++ /dev/null @@ -1,25 +0,0 @@ -# positive_negative_globs___hit_on_excluded_file - -A file matched by the negative glob should be excluded from fingerprinting even if it also matches a positive glob. - -## `vt run positive-negative-globs` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content src/main.test.ts main modified` - -``` -``` - -## `vt run positive-negative-globs` - -``` -$ vtt print-file src/main.ts ◉ cache hit, replaying -export const main = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_negative_globs___miss_on_non_excluded_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_negative_globs___miss_on_non_excluded_file.md deleted file mode 100644 index 170d9385c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/snapshots/positive_negative_globs___miss_on_non_excluded_file.md +++ /dev/null @@ -1,22 +0,0 @@ -# positive_negative_globs___miss_on_non_excluded_file - -A file matched by a positive glob and not by any negative glob should still invalidate the cache when modified. - -## `vt run positive-negative-globs` - -``` -$ vtt print-file src/main.ts -export const main = 'initial'; -``` - -## `vtt replace-file-content src/main.ts initial modified` - -``` -``` - -## `vt run positive-negative-globs` - -``` -$ vtt print-file src/main.ts ○ cache miss: 'src/main.ts' modified, executing -export const main = 'modified'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/main.test.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/main.test.ts deleted file mode 100644 index 3a3ca2964..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/main.test.ts +++ /dev/null @@ -1 +0,0 @@ -test('main', () => {}); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/main.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/main.ts deleted file mode 100644 index 38e000a1c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -export const main = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/sub/nested.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/sub/nested.ts deleted file mode 100644 index 987cac056..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/sub/nested.ts +++ /dev/null @@ -1 +0,0 @@ -export const nested = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/utils.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/utils.ts deleted file mode 100644 index e5b6e206a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/src/utils.ts +++ /dev/null @@ -1 +0,0 @@ -export const utils = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/test/main.test.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/test/main.test.ts deleted file mode 100644 index 12d25c9c3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/test/main.test.ts +++ /dev/null @@ -1 +0,0 @@ -// test file outside src/ diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/vite-task.json deleted file mode 100644 index 212478cee..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_cache_test/vite-task.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "tasks": { - "positive-globs-only": { - "command": "vtt print-file src/main.ts", - "input": ["src/**/*.ts"], - "cache": true - }, - "positive-globs-reads-unmatched": { - "command": "vtt print-file src/main.ts src/utils.ts", - "input": ["src/main.ts"], - "cache": true - }, - "positive-negative-globs": { - "command": "vtt print-file src/main.ts", - "input": ["src/**", "!src/**/*.test.ts"], - "cache": true - }, - "auto-only": { - "command": "vtt print-file src/main.ts", - "input": [ - { - "auto": true - } - ], - "cache": true - }, - "auto-with-negative": { - "command": "vtt print-file src/main.ts dist/output.js", - "input": [ - { - "auto": true - }, - "!dist/**" - ], - "cache": true - }, - "positive-auto-negative": { - "command": "vtt print-file src/main.ts", - "input": [ - "package.json", - { - "auto": true - }, - "!src/**/*.test.ts" - ], - "cache": true - }, - "empty-inputs": { - "command": "vtt print-file ./src/main.ts", - "input": [], - "cache": true - }, - "check-fspy-env-with-auto": { - "command": "vtt print-env FSPY", - "input": [ - { - "auto": true - } - ], - "cache": true - }, - "check-fspy-env-without-auto": { - "command": "vtt print-env FSPY", - "input": ["src/**/*.ts"], - "output": [], - "cache": true - }, - "folder-input": { - "command": "vtt print-file src/main.ts", - "input": ["src"], - "cache": true - }, - "folder-slash-input": { - "command": "vtt print-file src/main.ts", - "input": ["src/"], - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/package.json deleted file mode 100644 index 9e8f0d501..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "inputs-glob-meta-in-path", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/packages/[lib]/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/packages/[lib]/package.json deleted file mode 100644 index 9bd69c4c0..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/packages/[lib]/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "[lib]", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/packages/[lib]/src/main.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/packages/[lib]/src/main.ts deleted file mode 100644 index 834b5a5e9..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/packages/[lib]/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -export const lib = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/packages/[lib]/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/packages/[lib]/vite-task.json deleted file mode 100644 index 788aec26d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/packages/[lib]/vite-task.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "tasks": { - "build": { - "command": "vtt print-file src/main.ts", - "input": ["src/**/*.ts"], - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/snapshots.toml deleted file mode 100644 index 08826a8dd..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/snapshots.toml +++ /dev/null @@ -1,29 +0,0 @@ -[[e2e]] -name = "cache_hit_then_miss_on_file_change" -comment = """ -Test that glob meta characters in package paths are correctly escaped by wax::escape. Without escaping, "packages/[lib]/src/**/*.ts" would interpret [lib] as a character class matching 'l', 'i', or 'b' instead of the literal directory name. -""" -steps = [ - [ - "vt", - "run", - "[lib]#build", - ], - [ - "vt", - "run", - "[lib]#build", - ], - [ - "vtt", - "replace-file-content", - "packages/[lib]/src/main.ts", - "initial", - "modified", - ], - [ - "vt", - "run", - "[lib]#build", - ], -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/snapshots/cache_hit_then_miss_on_file_change.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/snapshots/cache_hit_then_miss_on_file_change.md deleted file mode 100644 index 403fdec0c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_glob_meta_in_path/snapshots/cache_hit_then_miss_on_file_change.md +++ /dev/null @@ -1,32 +0,0 @@ -# cache_hit_then_miss_on_file_change - -Test that glob meta characters in package paths are correctly escaped by wax::escape. Without escaping, "packages/[lib]/src/**/*.ts" would interpret [lib] as a character class matching 'l', 'i', or 'b' instead of the literal directory name. - -## `vt run [lib]#build` - -``` -~/packages/[lib]$ vtt print-file src/main.ts -export const lib = 'initial'; -``` - -## `vt run [lib]#build` - -``` -~/packages/[lib]$ vtt print-file src/main.ts ◉ cache hit, replaying -export const lib = 'initial'; - ---- -vt run: cache hit. -``` - -## `vtt replace-file-content packages/[lib]/src/main.ts initial modified` - -``` -``` - -## `vt run [lib]#build` - -``` -~/packages/[lib]$ vtt print-file src/main.ts ○ cache miss: 'packages/[lib]/src/main.ts' modified, executing -export const lib = 'modified'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/package.json deleted file mode 100644 index 84faec77f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "inputs-negative-glob-subpackage", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/shared/dist/output.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/shared/dist/output.js deleted file mode 100644 index d8d5d2108..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/shared/dist/output.js +++ /dev/null @@ -1 +0,0 @@ -// initial output diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/shared/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/shared/package.json deleted file mode 100644 index 590047bc9..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/shared/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "shared", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/shared/src/utils.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/shared/src/utils.ts deleted file mode 100644 index 34f12be37..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/shared/src/utils.ts +++ /dev/null @@ -1 +0,0 @@ -export const shared = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/dist/output.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/dist/output.js deleted file mode 100644 index d8d5d2108..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/dist/output.js +++ /dev/null @@ -1 +0,0 @@ -// initial output diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/package.json deleted file mode 100644 index e0caa109d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "sub-pkg", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/src/main.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/src/main.ts deleted file mode 100644 index 38e000a1c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -export const main = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/vite-task.json deleted file mode 100644 index 7236a7719..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/packages/sub-pkg/vite-task.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "tasks": { - "auto-with-negative": { - "command": "vtt print-file src/main.ts dist/output.js", - "input": [ - { - "auto": true - }, - "!dist/**" - ], - "cache": true - }, - "dotdot-positive": { - "command": "vtt print-file ../shared/src/utils.ts", - "input": ["../shared/src/**"], - "cache": true - }, - "dotdot-positive-negative": { - "command": "vtt print-file ../shared/src/utils.ts ../shared/dist/output.js", - "input": ["../shared/**", "!../shared/dist/**"], - "cache": true - }, - "dotdot-auto-negative": { - "command": "vtt print-file ../shared/src/utils.ts ../shared/dist/output.js", - "input": [ - { - "auto": true - }, - "!../shared/dist/**" - ], - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots.toml deleted file mode 100644 index 938f308ae..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots.toml +++ /dev/null @@ -1,105 +0,0 @@ -[[e2e]] -name = "subpackage_auto_with_negative___hit_on_excluded_inferred_file" -comment = """ -A subpackage's `!dist/**` exclusion should apply package-relative, not workspace-relative — modifying the subpackage's own `dist/` file stays a cache hit. (Regression: the exclusion used to resolve against the workspace root.) -""" -steps = [ - # First run - reads both src/main.ts and dist/output.js - ["vt", "run", "sub-pkg#auto-with-negative"], - # Modify file in excluded directory (dist/) - ["vtt", "replace-file-content", "packages/sub-pkg/dist/output.js", "initial", "modified"], - # Cache hit: dist/ is excluded by negative glob - ["vt", "run", "sub-pkg#auto-with-negative"], -] - -[[e2e]] -name = "subpackage_auto_with_negative___miss_on_non_excluded_inferred_file" -comment = """ -In a subpackage with `auto: true` plus `!dist/**`, changes to inferred inputs outside `dist/` should still invalidate the cache. -""" -steps = [ - # First run - ["vt", "run", "sub-pkg#auto-with-negative"], - # Modify file NOT in excluded directory - ["vtt", "replace-file-content", "packages/sub-pkg/src/main.ts", "initial", "modified"], - # Cache miss: inferred input changed - ["vt", "run", "sub-pkg#auto-with-negative"], -] - -[[e2e]] -name = "dotdot_positive_glob___miss_on_sibling_file_change" -comment = """ -A `../` positive glob should reach into sibling packages; modifying a matched sibling file should invalidate the cache. -""" -steps = [ - ["vt", "run", "sub-pkg#dotdot-positive"], - # Modify a file that matches ../shared/src/** - ["vtt", "replace-file-content", "packages/shared/src/utils.ts", "initial", "modified"], - # Cache miss: matched file changed - ["vt", "run", "sub-pkg#dotdot-positive"], -] - -[[e2e]] -name = "dotdot_positive_glob___hit_on_unmatched_file_change" -comment = """ -A `../` positive glob should only match the paths it names — sibling files outside the glob (e.g. sibling `dist/`) should not affect the cache. -""" -steps = [ - ["vt", "run", "sub-pkg#dotdot-positive"], - # Modify a file NOT matched by ../shared/src/** - ["vtt", "replace-file-content", "packages/shared/dist/output.js", "initial", "modified"], - # Cache hit: file not in input - ["vt", "run", "sub-pkg#dotdot-positive"], -] - -[[e2e]] -name = "dotdot_positive_negative___miss_on_non_excluded_sibling_file" -comment = """ -With `../shared/**` plus `!../shared/dist/**`, modifying a sibling file outside the exclusion should invalidate the cache. -""" -steps = [ - ["vt", "run", "sub-pkg#dotdot-positive-negative"], - # Modify file matching positive but NOT negative - ["vtt", "replace-file-content", "packages/shared/src/utils.ts", "initial", "modified"], - # Cache miss: file changed - ["vt", "run", "sub-pkg#dotdot-positive-negative"], -] - -[[e2e]] -name = "dotdot_positive_negative___hit_on_excluded_sibling_file" -comment = """ -A `../`-prefixed negative glob should correctly exclude files in the sibling package's `dist/` directory. -""" -steps = [ - ["vt", "run", "sub-pkg#dotdot-positive-negative"], - # Modify file in excluded sibling dist/ - ["vtt", "replace-file-content", "packages/shared/dist/output.js", "initial", "modified"], - # Cache hit: excluded by !../shared/dist/** - ["vt", "run", "sub-pkg#dotdot-positive-negative"], -] - -[[e2e]] -name = "dotdot_auto_negative___hit_on_excluded_sibling_inferred_file" -comment = """ -A `!../shared/dist/**` negative glob should filter inferred reads that land in the sibling package's `dist/` directory. -""" -steps = [ - ["vt", "run", "sub-pkg#dotdot-auto-negative"], - # Modify file in excluded sibling dist/ - ["vtt", "replace-file-content", "packages/shared/dist/output.js", "initial", "modified"], - # Cache hit: excluded by !../shared/dist/** - ["vt", "run", "sub-pkg#dotdot-auto-negative"], -] - -[[e2e]] -name = "dotdot_auto_negative___miss_on_non_excluded_sibling_inferred_file" -comment = """ -Under `auto: true` plus a `../` negative glob, inferred reads from sibling files outside the exclusion should still invalidate the cache. -""" -steps = [ - ["vt", "run", "sub-pkg#dotdot-auto-negative"], - # Modify non-excluded sibling file - ["vtt", "replace-file-content", "packages/shared/src/utils.ts", "initial", "modified"], - # Cache miss: inferred input changed - ["vt", "run", "sub-pkg#dotdot-auto-negative"], -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_auto_negative___hit_on_excluded_sibling_inferred_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_auto_negative___hit_on_excluded_sibling_inferred_file.md deleted file mode 100644 index 00a4a4ae4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_auto_negative___hit_on_excluded_sibling_inferred_file.md +++ /dev/null @@ -1,27 +0,0 @@ -# dotdot_auto_negative___hit_on_excluded_sibling_inferred_file - -A `!../shared/dist/**` negative glob should filter inferred reads that land in the sibling package's `dist/` directory. - -## `vt run sub-pkg#dotdot-auto-negative` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts ../shared/dist/output.js -export const shared = 'initial'; -// initial output -``` - -## `vtt replace-file-content packages/shared/dist/output.js initial modified` - -``` -``` - -## `vt run sub-pkg#dotdot-auto-negative` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts ../shared/dist/output.js ◉ cache hit, replaying -export const shared = 'initial'; -// initial output - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_auto_negative___miss_on_non_excluded_sibling_inferred_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_auto_negative___miss_on_non_excluded_sibling_inferred_file.md deleted file mode 100644 index 9a0536d49..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_auto_negative___miss_on_non_excluded_sibling_inferred_file.md +++ /dev/null @@ -1,24 +0,0 @@ -# dotdot_auto_negative___miss_on_non_excluded_sibling_inferred_file - -Under `auto: true` plus a `../` negative glob, inferred reads from sibling files outside the exclusion should still invalidate the cache. - -## `vt run sub-pkg#dotdot-auto-negative` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts ../shared/dist/output.js -export const shared = 'initial'; -// initial output -``` - -## `vtt replace-file-content packages/shared/src/utils.ts initial modified` - -``` -``` - -## `vt run sub-pkg#dotdot-auto-negative` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts ../shared/dist/output.js ○ cache miss: 'packages/shared/src/utils.ts' modified, executing -export const shared = 'modified'; -// initial output -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_glob___hit_on_unmatched_file_change.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_glob___hit_on_unmatched_file_change.md deleted file mode 100644 index cc4676151..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_glob___hit_on_unmatched_file_change.md +++ /dev/null @@ -1,25 +0,0 @@ -# dotdot_positive_glob___hit_on_unmatched_file_change - -A `../` positive glob should only match the paths it names — sibling files outside the glob (e.g. sibling `dist/`) should not affect the cache. - -## `vt run sub-pkg#dotdot-positive` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts -export const shared = 'initial'; -``` - -## `vtt replace-file-content packages/shared/dist/output.js initial modified` - -``` -``` - -## `vt run sub-pkg#dotdot-positive` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts ◉ cache hit, replaying -export const shared = 'initial'; - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_glob___miss_on_sibling_file_change.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_glob___miss_on_sibling_file_change.md deleted file mode 100644 index 6fef01c24..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_glob___miss_on_sibling_file_change.md +++ /dev/null @@ -1,22 +0,0 @@ -# dotdot_positive_glob___miss_on_sibling_file_change - -A `../` positive glob should reach into sibling packages; modifying a matched sibling file should invalidate the cache. - -## `vt run sub-pkg#dotdot-positive` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts -export const shared = 'initial'; -``` - -## `vtt replace-file-content packages/shared/src/utils.ts initial modified` - -``` -``` - -## `vt run sub-pkg#dotdot-positive` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts ○ cache miss: 'packages/shared/src/utils.ts' modified, executing -export const shared = 'modified'; -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_negative___hit_on_excluded_sibling_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_negative___hit_on_excluded_sibling_file.md deleted file mode 100644 index eeddef80a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_negative___hit_on_excluded_sibling_file.md +++ /dev/null @@ -1,27 +0,0 @@ -# dotdot_positive_negative___hit_on_excluded_sibling_file - -A `../`-prefixed negative glob should correctly exclude files in the sibling package's `dist/` directory. - -## `vt run sub-pkg#dotdot-positive-negative` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts ../shared/dist/output.js -export const shared = 'initial'; -// initial output -``` - -## `vtt replace-file-content packages/shared/dist/output.js initial modified` - -``` -``` - -## `vt run sub-pkg#dotdot-positive-negative` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts ../shared/dist/output.js ◉ cache hit, replaying -export const shared = 'initial'; -// initial output - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_negative___miss_on_non_excluded_sibling_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_negative___miss_on_non_excluded_sibling_file.md deleted file mode 100644 index 5f6f2c72f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/dotdot_positive_negative___miss_on_non_excluded_sibling_file.md +++ /dev/null @@ -1,24 +0,0 @@ -# dotdot_positive_negative___miss_on_non_excluded_sibling_file - -With `../shared/**` plus `!../shared/dist/**`, modifying a sibling file outside the exclusion should invalidate the cache. - -## `vt run sub-pkg#dotdot-positive-negative` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts ../shared/dist/output.js -export const shared = 'initial'; -// initial output -``` - -## `vtt replace-file-content packages/shared/src/utils.ts initial modified` - -``` -``` - -## `vt run sub-pkg#dotdot-positive-negative` - -``` -~/packages/sub-pkg$ vtt print-file ../shared/src/utils.ts ../shared/dist/output.js ○ cache miss: 'packages/shared/src/utils.ts' modified, executing -export const shared = 'modified'; -// initial output -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/subpackage_auto_with_negative___hit_on_excluded_inferred_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/subpackage_auto_with_negative___hit_on_excluded_inferred_file.md deleted file mode 100644 index 88ed14f01..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/subpackage_auto_with_negative___hit_on_excluded_inferred_file.md +++ /dev/null @@ -1,27 +0,0 @@ -# subpackage_auto_with_negative___hit_on_excluded_inferred_file - -A subpackage's `!dist/**` exclusion should apply package-relative, not workspace-relative — modifying the subpackage's own `dist/` file stays a cache hit. (Regression: the exclusion used to resolve against the workspace root.) - -## `vt run sub-pkg#auto-with-negative` - -``` -~/packages/sub-pkg$ vtt print-file src/main.ts dist/output.js -export const main = 'initial'; -// initial output -``` - -## `vtt replace-file-content packages/sub-pkg/dist/output.js initial modified` - -``` -``` - -## `vt run sub-pkg#auto-with-negative` - -``` -~/packages/sub-pkg$ vtt print-file src/main.ts dist/output.js ◉ cache hit, replaying -export const main = 'initial'; -// initial output - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/subpackage_auto_with_negative___miss_on_non_excluded_inferred_file.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/subpackage_auto_with_negative___miss_on_non_excluded_inferred_file.md deleted file mode 100644 index 0d7157e0d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_negative_glob_subpackage/snapshots/subpackage_auto_with_negative___miss_on_non_excluded_inferred_file.md +++ /dev/null @@ -1,24 +0,0 @@ -# subpackage_auto_with_negative___miss_on_non_excluded_inferred_file - -In a subpackage with `auto: true` plus `!dist/**`, changes to inferred inputs outside `dist/` should still invalidate the cache. - -## `vt run sub-pkg#auto-with-negative` - -``` -~/packages/sub-pkg$ vtt print-file src/main.ts dist/output.js -export const main = 'initial'; -// initial output -``` - -## `vtt replace-file-content packages/sub-pkg/src/main.ts initial modified` - -``` -``` - -## `vt run sub-pkg#auto-with-negative` - -``` -~/packages/sub-pkg$ vtt print-file src/main.ts dist/output.js ○ cache miss: 'packages/sub-pkg/src/main.ts' modified, executing -export const main = 'modified'; -// initial output -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/package.json deleted file mode 100644 index 4600be52b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "input-read-write-not-cached", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/normal-pkg/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/normal-pkg/package.json deleted file mode 100644 index 265ad0712..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/normal-pkg/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "@test/normal-pkg" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/normal-pkg/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/normal-pkg/vite-task.json deleted file mode 100644 index 218191192..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/normal-pkg/vite-task.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tasks": { - "task": { - "command": "vtt print hello" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/rw-pkg/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/rw-pkg/package.json deleted file mode 100644 index 3661f543d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/rw-pkg/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/rw-pkg", - "dependencies": { - "@test/touch-pkg": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/rw-pkg/src/data.txt b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/rw-pkg/src/data.txt deleted file mode 100644 index 94f3610c0..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/rw-pkg/src/data.txt +++ /dev/null @@ -1 +0,0 @@ -original \ No newline at end of file diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/rw-pkg/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/rw-pkg/vite-task.json deleted file mode 100644 index 3f7ab78ff..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/rw-pkg/vite-task.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tasks": { - "task": { - "command": "vtt replace-file-content src/data.txt i !" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/touch-pkg/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/touch-pkg/package.json deleted file mode 100644 index 09eef8e15..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/touch-pkg/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/touch-pkg", - "dependencies": { - "@test/normal-pkg": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/touch-pkg/src/data.txt b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/touch-pkg/src/data.txt deleted file mode 100644 index 94f3610c0..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/touch-pkg/src/data.txt +++ /dev/null @@ -1 +0,0 @@ -original \ No newline at end of file diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/touch-pkg/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/touch-pkg/vite-task.json deleted file mode 100644 index 092af5bef..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/packages/touch-pkg/vite-task.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tasks": { - "task": { - "command": "vtt touch-file src/data.txt" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots.toml deleted file mode 100644 index e3c51ace1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots.toml +++ /dev/null @@ -1,30 +0,0 @@ -[[e2e]] -name = "single_read_write_task_shows_not_cached_message" -comment = """ -A single task that reads and writes the same file (fspy sees both ops) should be flagged as "not cached because it modified its input" in the compact summary. -""" -cwd = "packages/rw-pkg" -steps = [["vt", "run", "task"], ["vt", "run", "task"]] - -[[e2e]] -name = "multi_task_with_read_write_shows_not_cached_in_summary" -comment = """ -In a multi-task (`-r`) run, tasks with a read-write overlap should appear in the compact summary stats alongside an `InputModified` notice. -""" -steps = [["vt", "run", "-r", "task"], ["vt", "run", "-r", "task"]] - -[[e2e]] -name = "verbose_read_write_task_shows_path_in_full_summary" -comment = """ -Under `-v`, the full summary should list the exact overlapping path that caused the task to skip caching. -""" -cwd = "packages/rw-pkg" -steps = [["vt", "run", "-v", "task"]] - -[[e2e]] -name = "single_O_RDWR_open_is_not_cached" -comment = """ -Opening a single file with `O_RDWR` (e.g. `touch` keeping the file) should count as a read-write overlap and prevent caching, just like separate read+write syscalls. -""" -cwd = "packages/touch-pkg" -steps = [["vt", "run", "task"], ["vt", "run", "task"]] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/multi_task_with_read_write_shows_not_cached_in_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/multi_task_with_read_write_shows_not_cached_in_summary.md deleted file mode 100644 index 74e809467..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/multi_task_with_read_write_shows_not_cached_in_summary.md +++ /dev/null @@ -1,31 +0,0 @@ -# multi_task_with_read_write_shows_not_cached_in_summary - -In a multi-task (`-r`) run, tasks with a read-write overlap should appear in the compact summary stats alongside an `InputModified` notice. - -## `vt run -r task` - -``` -~/packages/normal-pkg$ vtt print hello -hello - -~/packages/touch-pkg$ vtt touch-file src/data.txt - -~/packages/rw-pkg$ vtt replace-file-content src/data.txt i ! - ---- -vt run: 0/3 cache hit (0%). @test/touch-pkg#task (and 1 more) not cached because they modified their inputs. (Run `vt run --last-details` for full details) -``` - -## `vt run -r task` - -``` -~/packages/normal-pkg$ vtt print hello ◉ cache hit, replaying -hello - -~/packages/touch-pkg$ vtt touch-file src/data.txt - -~/packages/rw-pkg$ vtt replace-file-content src/data.txt i ! - ---- -vt run: 1/3 cache hit (33%). @test/touch-pkg#task (and 1 more) not cached because they modified their inputs. (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/single_O_RDWR_open_is_not_cached.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/single_O_RDWR_open_is_not_cached.md deleted file mode 100644 index c940d6b28..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/single_O_RDWR_open_is_not_cached.md +++ /dev/null @@ -1,21 +0,0 @@ -# single_O_RDWR_open_is_not_cached - -Opening a single file with `O_RDWR` (e.g. `touch` keeping the file) should count as a read-write overlap and prevent caching, just like separate read+write syscalls. - -## `vt run task` - -``` -~/packages/touch-pkg$ vtt touch-file src/data.txt - ---- -vt run: @test/touch-pkg#task not cached because it modified its input. (Run `vt run --last-details` for full details) -``` - -## `vt run task` - -``` -~/packages/touch-pkg$ vtt touch-file src/data.txt - ---- -vt run: @test/touch-pkg#task not cached because it modified its input. (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/single_read_write_task_shows_not_cached_message.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/single_read_write_task_shows_not_cached_message.md deleted file mode 100644 index 97428a0dd..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/single_read_write_task_shows_not_cached_message.md +++ /dev/null @@ -1,21 +0,0 @@ -# single_read_write_task_shows_not_cached_message - -A single task that reads and writes the same file (fspy sees both ops) should be flagged as "not cached because it modified its input" in the compact summary. - -## `vt run task` - -``` -~/packages/rw-pkg$ vtt replace-file-content src/data.txt i ! - ---- -vt run: @test/rw-pkg#task not cached because it modified its input. (Run `vt run --last-details` for full details) -``` - -## `vt run task` - -``` -~/packages/rw-pkg$ vtt replace-file-content src/data.txt i ! - ---- -vt run: @test/rw-pkg#task not cached because it modified its input. (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/verbose_read_write_task_shows_path_in_full_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/verbose_read_write_task_shows_path_in_full_summary.md deleted file mode 100644 index bc1bab7ec..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/snapshots/verbose_read_write_task_shows_path_in_full_summary.md +++ /dev/null @@ -1,23 +0,0 @@ -# verbose_read_write_task_shows_path_in_full_summary - -Under `-v`, the full summary should list the exact overlapping path that caused the task to skip caching. - -## `vt run -v task` - -``` -~/packages/rw-pkg$ vtt replace-file-content src/data.txt i ! - - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Vite+ Task Runner • Execution Summary -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Statistics: 1 tasks • 0 cache hits • 1 cache misses -Performance: 0% cache hit rate - -Task Details: -──────────────────────────────────────────────── - [1] @test/rw-pkg#task: ~/packages/rw-pkg$ vtt replace-file-content src/data.txt i ! ✓ - → Not cached: read and wrote 'packages/rw-pkg/src/data.txt' -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/input_read_write_not_cached/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/package.json deleted file mode 100644 index 853624825..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "interleaved-stdio-test", - "private": true, - "dependencies": { - "other": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/packages/other/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/packages/other/package.json deleted file mode 100644 index 6bf03d25d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/packages/other/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "other", - "scripts": { - "check-tty": "vtt check-tty", - "check-tty-cached": "vtt check-tty", - "read-stdin": "vtt read-stdin" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots.toml deleted file mode 100644 index 4160d0f7a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots.toml +++ /dev/null @@ -1,55 +0,0 @@ -[[e2e]] -name = "single_task__cache_off__inherits_stdio" -comment = """ -In interleaved mode with caching off, a single task should inherit stdio from the parent — all fds should be TTYs. -""" -steps = [["vt", "run", "check-tty"]] - -[[e2e]] -name = "multiple_tasks__cache_off__inherit_stdio" -comment = """ -In interleaved mode with caching off, multiple tasks under `-r` should each inherit stdio (TTY-preserving behavior applies per task regardless of count). -""" -steps = [["vt", "run", "-r", "check-tty"]] - -[[e2e]] -name = "single_task__cache_miss__piped_stdio" -comment = """ -On a cache miss in interleaved mode, stdio must be piped (not a TTY) so that output can be captured for future replay. -""" -steps = [["vt", "run", "check-tty-cached"]] - -[[e2e]] -name = "multiple_tasks__cache_miss__piped_stdio" -comment = """ -Across multiple tasks on a cache miss, each one should still see piped (non-TTY) stdio for output capture. -""" -steps = [["vt", "run", "-r", "check-tty-cached"]] - -[[e2e]] -name = "single_task__cache_hit__replayed" -comment = """ -A cache-hit second run should replay the previously captured output verbatim instead of re-executing the task. -""" -steps = [["vt", "run", "check-tty-cached"], ["vt", "run", "check-tty-cached"]] - -[[e2e]] -name = "multiple_tasks__cache_hit__replayed" -comment = """ -Across multiple tasks, the second run should be all cache hits with each task's captured output replayed. -""" -steps = [["vt", "run", "-r", "check-tty-cached"], ["vt", "run", "-r", "check-tty-cached"]] - -[[e2e]] -name = "cache_off_inherits_stdin" -comment = """ -With caching off, stdin piped into `vp run` should flow through to the task process. -""" -steps = [["vtt", "pipe-stdin", "from-stdin", "--", "vt", "run", "read-stdin"]] - -[[e2e]] -name = "cache_on_gets_null_stdin" -comment = """ -With caching on, task stdin should be replaced with `/dev/null` — piping data in from outside must not reach the task. -""" -steps = [["vtt", "pipe-stdin", "from-stdin", "--", "vt", "run", "read-stdin-cached"]] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/cache_off_inherits_stdin.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/cache_off_inherits_stdin.md deleted file mode 100644 index 97039147c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/cache_off_inherits_stdin.md +++ /dev/null @@ -1,10 +0,0 @@ -# cache_off_inherits_stdin - -With caching off, stdin piped into `vp run` should flow through to the task process. - -## `vtt pipe-stdin from-stdin -- vt run read-stdin` - -``` -$ vtt read-stdin ⊘ cache disabled -from-stdin -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/cache_on_gets_null_stdin.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/cache_on_gets_null_stdin.md deleted file mode 100644 index e09b7129f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/cache_on_gets_null_stdin.md +++ /dev/null @@ -1,9 +0,0 @@ -# cache_on_gets_null_stdin - -With caching on, task stdin should be replaced with `/dev/null` — piping data in from outside must not reach the task. - -## `vtt pipe-stdin from-stdin -- vt run read-stdin-cached` - -``` -$ vtt read-stdin -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/multiple_tasks__cache_hit__replayed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/multiple_tasks__cache_hit__replayed.md deleted file mode 100644 index 3612bbd5c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/multiple_tasks__cache_hit__replayed.md +++ /dev/null @@ -1,37 +0,0 @@ -# multiple_tasks__cache_hit__replayed - -Across multiple tasks, the second run should be all cache hits with each task's captured output replayed. - -## `vt run -r check-tty-cached` - -``` -~/packages/other$ vtt check-tty -stdin:not-tty -stdout:not-tty -stderr:not-tty - -$ vtt check-tty -stdin:not-tty -stdout:not-tty -stderr:not-tty - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` - -## `vt run -r check-tty-cached` - -``` -~/packages/other$ vtt check-tty ◉ cache hit, replaying -stdin:not-tty -stdout:not-tty -stderr:not-tty - -$ vtt check-tty ◉ cache hit, replaying -stdin:not-tty -stdout:not-tty -stderr:not-tty - ---- -vt run: 2/2 cache hit (100%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/multiple_tasks__cache_miss__piped_stdio.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/multiple_tasks__cache_miss__piped_stdio.md deleted file mode 100644 index 53627f89b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/multiple_tasks__cache_miss__piped_stdio.md +++ /dev/null @@ -1,20 +0,0 @@ -# multiple_tasks__cache_miss__piped_stdio - -Across multiple tasks on a cache miss, each one should still see piped (non-TTY) stdio for output capture. - -## `vt run -r check-tty-cached` - -``` -~/packages/other$ vtt check-tty -stdin:not-tty -stdout:not-tty -stderr:not-tty - -$ vtt check-tty -stdin:not-tty -stdout:not-tty -stderr:not-tty - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/multiple_tasks__cache_off__inherit_stdio.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/multiple_tasks__cache_off__inherit_stdio.md deleted file mode 100644 index e1f483e7c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/multiple_tasks__cache_off__inherit_stdio.md +++ /dev/null @@ -1,20 +0,0 @@ -# multiple_tasks__cache_off__inherit_stdio - -In interleaved mode with caching off, multiple tasks under `-r` should each inherit stdio (TTY-preserving behavior applies per task regardless of count). - -## `vt run -r check-tty` - -``` -~/packages/other$ vtt check-tty -stdin:not-tty -stdout:not-tty -stderr:not-tty - -$ vtt check-tty ⊘ cache disabled -stdin:tty -stdout:tty -stderr:tty - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/single_task__cache_hit__replayed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/single_task__cache_hit__replayed.md deleted file mode 100644 index 4b1aa46d7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/single_task__cache_hit__replayed.md +++ /dev/null @@ -1,24 +0,0 @@ -# single_task__cache_hit__replayed - -A cache-hit second run should replay the previously captured output verbatim instead of re-executing the task. - -## `vt run check-tty-cached` - -``` -$ vtt check-tty -stdin:not-tty -stdout:not-tty -stderr:not-tty -``` - -## `vt run check-tty-cached` - -``` -$ vtt check-tty ◉ cache hit, replaying -stdin:not-tty -stdout:not-tty -stderr:not-tty - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/single_task__cache_miss__piped_stdio.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/single_task__cache_miss__piped_stdio.md deleted file mode 100644 index dba390a5b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/single_task__cache_miss__piped_stdio.md +++ /dev/null @@ -1,12 +0,0 @@ -# single_task__cache_miss__piped_stdio - -On a cache miss in interleaved mode, stdio must be piped (not a TTY) so that output can be captured for future replay. - -## `vt run check-tty-cached` - -``` -$ vtt check-tty -stdin:not-tty -stdout:not-tty -stderr:not-tty -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/single_task__cache_off__inherits_stdio.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/single_task__cache_off__inherits_stdio.md deleted file mode 100644 index 399838f0a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/snapshots/single_task__cache_off__inherits_stdio.md +++ /dev/null @@ -1,12 +0,0 @@ -# single_task__cache_off__inherits_stdio - -In interleaved mode with caching off, a single task should inherit stdio from the parent — all fds should be TTYs. - -## `vt run check-tty` - -``` -$ vtt check-tty ⊘ cache disabled -stdin:tty -stdout:tty -stderr:tty -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/vite-task.json deleted file mode 100644 index 9f6a560f4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/interleaved_stdio/vite-task.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "cache": true, - "tasks": { - "check-tty": { - "command": "vtt check-tty", - "cache": false - }, - "check-tty-cached": { - "command": "vtt check-tty", - "cache": true - }, - "read-stdin": { - "command": "vtt read-stdin", - "cache": false - }, - "read-stdin-cached": { - "command": "vtt read-stdin", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/package.json deleted file mode 100644 index be1e0be88..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "ipc-client-test", - "private": true, - "type": "module" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/assert_undefined_env.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/assert_undefined_env.mjs deleted file mode 100644 index c1549ae8f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/assert_undefined_env.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { getEnv } from '@voidzero-dev/vite-task-client'; - -const name = '__VITE_TASK_CLIENT_MISSING_ENV__'; -const value = getEnv(name); - -if (value !== undefined) { - throw new Error(`expected ${name} to be undefined, got ${value}`); -} - -console.log('missing undefined'); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/auto_output_negative.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/auto_output_negative.mjs deleted file mode 100644 index c630f5cbe..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/auto_output_negative.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import { mkdirSync, writeFileSync } from 'node:fs'; - -mkdirSync('dist', { recursive: true }); -writeFileSync('dist/keep.txt', 'keep\n'); -writeFileSync('dist/skip.txt', 'skip\n'); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/auto_output_negative_overlap.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/auto_output_negative_overlap.mjs deleted file mode 100644 index 6233d842d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/auto_output_negative_overlap.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; - -mkdirSync('scratch', { recursive: true }); -mkdirSync('dist', { recursive: true }); - -writeFileSync('scratch/overlap.txt', 'before\n'); -readFileSync('scratch/overlap.txt', 'utf8'); -writeFileSync('scratch/overlap.txt', 'after\n'); -writeFileSync('dist/negative-overlap.txt', 'keep\n'); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/disable_cache.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/disable_cache.mjs deleted file mode 100644 index f868cef50..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/disable_cache.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import { disableCache } from '@voidzero-dev/vite-task-client'; -import { writeFileSync, mkdirSync } from 'node:fs'; - -// Produce an output, then ask the runner not to cache this execution — the -// next `vt run` should re-execute the task. -mkdirSync('dist', { recursive: true }); -writeFileSync('dist/out.txt', 'ok\n'); -disableCache(); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/fetch_env.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/fetch_env.mjs deleted file mode 100644 index 4dd4c88dd..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/fetch_env.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import { getEnv } from '@voidzero-dev/vite-task-client'; - -const args = process.argv.slice(2); -const tracked = args[0] === '--untracked' ? false : true; -const names = tracked ? args : args.slice(1); - -if (names.length === 0) { - throw new Error('usage: fetch_env.mjs [--untracked] [...]'); -} - -const served = names.map((name) => `${name}=${getEnv(name, { tracked }) ?? '(unset)'}`); -const own = names.map((name) => `${name}=${process.env[name] ?? '(unset)'}`); - -console.log(`served ${served.join(' ')}`); -console.log(`process.env ${own.join(' ')}`); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/fetch_envs.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/fetch_envs.mjs deleted file mode 100644 index 66342b899..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/fetch_envs.mjs +++ /dev/null @@ -1,12 +0,0 @@ -import { getEnvs } from '@voidzero-dev/vite-task-client'; - -const args = process.argv.slice(2); -const tracked = !args.includes('--untracked'); -const prefixIndex = args.indexOf('--prefix'); -const query = prefixIndex === -1 ? 'PROBE_*' : { prefix: args[prefixIndex + 1] ?? 'PROBE_' }; -const matches = getEnvs(query, { tracked }); -const sorted = Object.entries(matches).sort(([a], [b]) => a.localeCompare(b)); - -for (const [key, value] of sorted) { - console.log(`${key}=${value}`); -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/ignore_input.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/ignore_input.mjs deleted file mode 100644 index 2f76622d5..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/ignore_input.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import { mkdirSync, readFileSync } from 'node:fs'; -import { ignoreInput } from '@voidzero-dev/vite-task-client'; - -mkdirSync('cache_like', { recursive: true }); - -ignoreInput('cache_like'); - -const value = readFileSync('cache_like/input.txt', 'utf8').trim(); -console.log(value); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/ignore_output.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/ignore_output.mjs deleted file mode 100644 index 622789242..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/scripts/ignore_output.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; -import { ignoreOutput } from '@voidzero-dev/vite-task-client'; - -mkdirSync('sidecar', { recursive: true }); -writeFileSync('sidecar/tmp.txt', 'initial\n'); -readFileSync('sidecar/tmp.txt', 'utf8'); -writeFileSync('sidecar/tmp.txt', 'final\n'); -ignoreOutput('sidecar'); - -mkdirSync('dist', { recursive: true }); -writeFileSync('dist/out.txt', 'ok\n'); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots.toml deleted file mode 100644 index 64f809c2f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots.toml +++ /dev/null @@ -1,672 +0,0 @@ -[[e2e]] -name = "ignore_input_keeps_cache_valid" -comment = """ -Exercises `ignoreInput` through `@voidzero-dev/vite-task-client`. The runner treats `cache_like/` as non-input for auto-inferred reads, but declared inputs under that path stay in the configured cache key. -""" -ignore = true -steps = [ - { argv = [ - "vtt", - "write-file", - "cache_like/input.txt", - "before", - ], comment = "seed the file the task will read and ignore" }, - { argv = [ - "vt", - "run", - "ignore-input", - ], comment = "populate the cache" }, - { argv = [ - "vtt", - "write-file", - "cache_like/input.txt", - "after", - ], comment = "mutate the ignored input — would invalidate if tracked" }, - { argv = [ - "vt", - "run", - "ignore-input", - ], comment = "cache hit: cache_like/ was ignored via ignoreInput" }, - { argv = [ - "vtt", - "write-file", - "cache_like/input.txt", - "manual-before", - ], comment = "seed the declared input for input: [cache_like/input.txt]" }, - { argv = [ - "vt", - "run", - "ignore-input-manual-input", - ], comment = "populate the cache with the manual-only input fingerprint" }, - { argv = [ - "vtt", - "write-file", - "cache_like/input.txt", - "manual-after", - ], comment = "mutate the declared input for input: [cache_like/input.txt]" }, - { argv = [ - "vt", - "run", - "ignore-input-manual-input", - ], comment = "cache miss: manual input config wins over ignoreInput" }, - { argv = [ - "vtt", - "write-file", - "cache_like/input.txt", - "auto-manual-before", - ], comment = "seed the declared input for input: [{ auto: true }, cache_like/input.txt]" }, - { argv = [ - "vt", - "run", - "ignore-input-auto-manual-input", - ], comment = "populate the cache with the auto-plus-manual input fingerprint" }, - { argv = [ - "vtt", - "write-file", - "cache_like/input.txt", - "auto-manual-after", - ], comment = "mutate the declared input for input: [{ auto: true }, cache_like/input.txt]" }, - { argv = [ - "vt", - "run", - "ignore-input-auto-manual-input", - ], comment = "cache miss: explicit input still wins when auto inference is also enabled" }, -] - -[[e2e]] -name = "ignore_output_allows_read_write_overlap" -comment = """ -Exercises `ignoreOutput` with auto output tracking. The task reads and writes `sidecar/tmp.txt`; without the ignore the runner's read-write overlap check would refuse to cache the run. The task also writes `dist/out.txt`, which should be auto-archived and restored on a cache hit. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "ignore-output", - ], comment = "first run populates the cache" }, - { argv = [ - "vtt", - "rm", - "dist/out.txt", - ], comment = "remove the auto output so restoration is observable" }, - { argv = [ - "vt", - "run", - "ignore-output", - ], comment = "cache hit: sidecar/ writes were ignored" }, - { argv = [ - "vtt", - "print-file", - "dist/out.txt", - ], comment = "restored from the auto output archive" }, -] - -[[e2e]] -name = "explicit_auto_output_restores_written_files" -comment = """ -Exercises `output: [{ auto: true }]` with explicit inputs disabled. The runner attaches fspy for output tracking, archives the written file, and restores it on cache hit. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "auto-output-explicit", - ], comment = "first run writes dist/auto.txt and archives fspy-tracked outputs" }, - { argv = [ - "vtt", - "rm", - "dist/auto.txt", - ], comment = "remove the output so restoration is observable" }, - { argv = [ - "vt", - "run", - "auto-output-explicit", - ], comment = "cache hit: fspy-tracked output is restored" }, - { argv = [ - "vtt", - "print-file", - "dist/auto.txt", - ], comment = "restored from the auto output archive" }, -] - -[[e2e]] -name = "explicit_auto_output_respects_negative_globs" -comment = """ -Exercises `output: [{ auto: true }, "!dist/skip.txt"]`. The runner archives auto-tracked writes except those excluded by output negative globs, so cache hits restore `dist/keep.txt` but not `dist/skip.txt`. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "auto-output-negative", - ], comment = "first run writes keep and skip files, but skip is excluded by a negative output glob" }, - { argv = [ - "vtt", - "rm", - "dist/keep.txt", - "dist/skip.txt", - ], comment = "remove both writes so restoration proves the negative glob was honored" }, - { argv = [ - "vt", - "run", - "auto-output-negative", - ], comment = "cache hit: only non-excluded auto output is restored" }, - { argv = [ - "vtt", - "print-file", - "dist/keep.txt", - ], comment = "restored from the auto output archive" }, - { argv = [ - "vtt", - "print-file", - "dist/skip.txt", - ], comment = "not restored because the negative output glob excluded it" }, -] - -[[e2e]] -name = "output_negative_glob_prevents_read_write_overlap" -comment = """ -Exercises `output: [{ auto: true }, "!scratch/**"]` on a task that reads and writes `scratch/overlap.txt`. Because the write is excluded from the auto-output set, it should not count as a read-write overlap, while non-excluded outputs are still archived and restored. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "auto-output-negative-overlap", - ], comment = "first run reads and writes an output-negative path" }, - { argv = [ - "vtt", - "rm", - "dist/negative-overlap.txt", - ], comment = "remove the non-excluded output so restoration is observable" }, - { argv = [ - "vt", - "run", - "auto-output-negative-overlap", - ], comment = "cache hit: the output-negative write did not block caching" }, - { argv = [ - "vtt", - "print-file", - "dist/negative-overlap.txt", - ], comment = "restored from the auto output archive" }, -] - -[[e2e]] -name = "default_auto_output_restores_written_files" -comment = """ -Exercises the default output behavior. When `output` is omitted, the runner automatically tracks writes, archives the generated file, and restores it on cache hit. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "auto-output-default", - ], comment = "first run writes dist/default.txt and archives fspy-tracked outputs" }, - { argv = [ - "vtt", - "rm", - "dist/default.txt", - ], comment = "remove the output so restoration is observable" }, - { argv = [ - "vt", - "run", - "auto-output-default", - ], comment = "cache hit: default auto output is restored" }, - { argv = [ - "vtt", - "print-file", - "dist/default.txt", - ], comment = "restored from the default auto output archive" }, -] - -[[e2e]] -name = "disable_cache_noop_allows_cache_hit" -comment = """ -Exercises the temporary `disableCache` no-op workaround. The tool asks the -runner not to cache this run, but the client ignores that request, so the next -invocation hits the cache. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "disable-cache", - ], comment = "first run — tool calls disableCache, currently ignored by the client" }, - { argv = [ - "vt", - "run", - "disable-cache", - ], comment = "cache hit because disableCache is temporarily a no-op" }, - { argv = [ - "vt", - "run", - "--last-details", - ], comment = "summary reports the replayed cache hit" }, -] - -[[e2e]] -name = "disable_cache_noop_with_explicit_inputs" -comment = """ -Exercises the temporary `disableCache` no-op workaround on a cached task with -explicit inputs. The client ignores the opt-out request, so the second run hits -even when fspy auto-input inference is disabled. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "disable-cache-explicit-input", - ], comment = "first run uses input: [] and calls disableCache, currently ignored by the client" }, - { argv = [ - "vt", - "run", - "disable-cache-explicit-input", - ], comment = "cache hit because disableCache is temporarily a no-op" }, - { argv = [ - "vt", - "run", - "--last-details", - ], comment = "summary reports the replayed cache hit" }, -] - -[[e2e]] -name = "fetch_env_missing_returns_undefined" -comment = """ -Exercises the public `getEnv(name)` contract for an absent env var. The client API must expose `undefined` so callers can distinguish absence using the documented API. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-missing-env", - ], comment = "missing env values are normalized to undefined" }, -] - -[[e2e]] -name = "fetch_env_reads_undeclared_env" -comment = """ -Exercises `getEnv(name)`: the tool asks the runner for an env var that is not declared in the task's `env` list. This verifies runner-served envs resolve from the unfiltered spawn env context while the child process env remains cache-filtered. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-env", - ], envs = [ - [ - "PROBE_ENV", - "served", - ], - ], comment = "runner serves undeclared PROBE_ENV from the unfiltered env context" }, -] - -[[e2e]] -name = "fetch_env_sees_command_prefix_env" -comment = """ -A command-prefixed env (`PREFIXED_ENV=from-command node ...`) is part of the spawn's full env context. `getEnv('PREFIXED_ENV')` and `process.env.PREFIXED_ENV` must both see the command prefix value. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-prefixed-env", - ], comment = "tool asks the runner for an env the command prefix sets" }, -] - -[[e2e]] -name = "fetch_env_sees_intermediate_prefix_envs" -comment = """ -Prefix envs accumulate through nested runs: `outer-prefixed` is `PREFIXED_A=a vt run inner-prefixed`, and `inner-prefixed` is `PREFIXED_B=b node ...`. The inner tool's `getEnv` must see both through the runner's env context, while `process.env` only sees the direct child prefix. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "outer-prefixed", - ], comment = "outer prefix env reaches the inner tool via the runner" }, -] - -[[e2e]] -name = "fetch_env_tracked_invalidates_on_change" -comment = """ -Exercises `getEnv(name, { tracked: true })`. The env value becomes part of the post-run fingerprint: the same value still hits, and a different value misses with the env var named in the miss message. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-env", - ], envs = [ - [ - "PROBE_ENV", - "first", - ], - ], comment = "first run captures PROBE_ENV=first in the post-run fingerprint" }, - { argv = [ - "vt", - "run", - "fetch-env", - ], envs = [ - [ - "PROBE_ENV", - "first", - ], - ], comment = "cache hit: PROBE_ENV unchanged" }, - { argv = [ - "vt", - "run", - "fetch-env", - ], envs = [ - [ - "PROBE_ENV", - "second", - ], - ], comment = "cache miss: tracked PROBE_ENV changed" }, -] - -[[e2e]] -name = "fetch_env_tracks_with_explicit_inputs" -comment = """ -Runner-aware env tracking must not depend on fspy auto-input inference. This task uses `input: []`, but `getEnv(name, { tracked: true })` still records the served value and later env changes miss. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-env-explicit-input", - ], envs = [ - [ - "PROBE_ENV", - "first", - ], - ], comment = "first run captures PROBE_ENV even though input auto-inference is disabled" }, - { argv = [ - "vt", - "run", - "fetch-env-explicit-input", - ], envs = [ - [ - "PROBE_ENV", - "first", - ], - ], comment = "cache hit: explicit inputs are unchanged and PROBE_ENV is unchanged" }, - { argv = [ - "vt", - "run", - "fetch-env-explicit-input", - ], envs = [ - [ - "PROBE_ENV", - "second", - ], - ], comment = "cache miss: tracked env changed despite input auto-inference being disabled" }, -] - -[[e2e]] -name = "fetch_envs_reads_match_set" -comment = """ -Exercises `getEnvs(pattern)`: the tool asks the runner for every env matching `PROBE_*` and prints the served match set. This verifies the bulk env IPC round trip before match sets are added to cache fingerprints. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-envs", - ], envs = [ - [ - "PROBE_A", - "a", - ], - [ - "PROBE_B", - "b", - ], - [ - "UNRELATED", - "noise", - ], - ], comment = "runner serves only envs matching PROBE_*" }, -] - -[[e2e]] -name = "fetch_envs_prefix_reads_match_set" -comment = """ -Exercises `getEnvs({ prefix: "PROBE_" })`: the tool asks the runner for every env whose name starts with `PROBE_` and prints the served match set. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-envs-prefix", - ], envs = [ - [ - "PROBE_A", - "a", - ], - [ - "PROBE_B", - "b", - ], - [ - "PROBEX", - "not-a-prefix-match", - ], - [ - "UNRELATED", - "noise", - ], - ], comment = "runner serves only envs with the literal PROBE_ prefix" }, -] - -[[e2e]] -name = "fetch_envs_prefix_treats_star_literally" -comment = """ -Exercises `getEnvs({ prefix: "PROBE_*" })`: `*` is part of the prefix string, not a glob wildcard. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-envs-star-prefix", - ], envs = [ - [ - "PROBE_*A", - "literal", - ], - [ - "PROBE_XA", - "wildcard-if-glob", - ], - [ - "PROBE_A", - "also-wildcard-if-glob", - ], - ], comment = "runner serves only envs whose name starts with literal PROBE_*" }, -] - -[[e2e]] -name = "fetch_envs_tracks_glob_match_set" -comment = """ -Exercises `getEnvs(pattern, { tracked: true })`. The glob `PROBE_*` and its match-set snapshot enter the post-run fingerprint: later runs miss on changed, added, or removed matching envs, but hit when only non-matching envs differ. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-envs", - ], envs = [ - [ - "PROBE_A", - "a", - ], - [ - "PROBE_B", - "b", - ], - ], comment = "populate: first run captures {PROBE_A, PROBE_B} under the glob" }, - { argv = [ - "vt", - "run", - "fetch-envs", - ], envs = [ - [ - "PROBE_A", - "a", - ], - [ - "PROBE_B", - "b", - ], - ], comment = "unchanged: same match-set -> cache hit" }, - { argv = [ - "vt", - "run", - "fetch-envs", - ], envs = [ - [ - "PROBE_A", - "changed", - ], - [ - "PROBE_B", - "b", - ], - ], comment = "change: PROBE_A value differs -> cache miss" }, - { argv = [ - "vt", - "run", - "fetch-envs", - ], envs = [ - [ - "PROBE_A", - "changed", - ], - [ - "PROBE_B", - "b", - ], - [ - "PROBE_C", - "c", - ], - ], comment = "add: PROBE_C is new under the glob -> cache miss" }, - { argv = [ - "vt", - "run", - "fetch-envs", - ], envs = [ - [ - "PROBE_B", - "b", - ], - [ - "PROBE_C", - "c", - ], - ], comment = "remove: PROBE_A dropped from the match-set -> cache miss" }, - { argv = [ - "vt", - "run", - "fetch-envs", - ], envs = [ - [ - "PROBE_B", - "b", - ], - [ - "PROBE_C", - "c", - ], - [ - "UNRELATED", - "noise", - ], - ], comment = "non-matching noise: UNRELATED does not match PROBE_* -> cache hit" }, -] - -[[e2e]] -name = "fetch_env_untracked_does_not_invalidate" -comment = """ -Exercises `getEnv(name, { tracked: false })`. The runner still serves the env value, but the value does not enter the post-run fingerprint, so changing it later replays the cached output. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-env-untracked", - ], envs = [ - [ - "PROBE_ENV", - "first", - ], - ], comment = "first run serves PROBE_ENV without tracking it" }, - { argv = [ - "vt", - "run", - "fetch-env-untracked", - ], envs = [ - [ - "PROBE_ENV", - "second", - ], - ], comment = "cache hit: PROBE_ENV changed but was requested with tracked: false" }, -] - -[[e2e]] -name = "fetch_envs_untracked_does_not_invalidate" -comment = """ -Exercises `getEnvs(pattern, { tracked: false })`. The runner still serves the matching envs, but the match set does not enter the post-run fingerprint, so changing it later replays the cached output. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "fetch-envs-untracked", - ], envs = [ - [ - "PROBE_A", - "a", - ], - [ - "PROBE_B", - "b", - ], - ], comment = "first run serves the PROBE_* match set without tracking it" }, - { argv = [ - "vt", - "run", - "fetch-envs-untracked", - ], envs = [ - [ - "PROBE_A", - "changed", - ], - [ - "PROBE_B", - "b", - ], - [ - "PROBE_C", - "c", - ], - ], comment = "cache hit: changed match set was requested with tracked: false" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/default_auto_output_restores_written_files.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/default_auto_output_restores_written_files.md deleted file mode 100644 index 5eb48fbb7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/default_auto_output_restores_written_files.md +++ /dev/null @@ -1,37 +0,0 @@ -# default_auto_output_restores_written_files - -Exercises the default output behavior. When `output` is omitted, the runner automatically tracks writes, archives the generated file, and restores it on cache hit. - -## `vt run auto-output-default` - -first run writes dist/default.txt and archives fspy-tracked outputs - -``` -$ vtt write-file dist/default.txt ok -``` - -## `vtt rm dist/default.txt` - -remove the output so restoration is observable - -``` -``` - -## `vt run auto-output-default` - -cache hit: default auto output is restored - -``` -$ vtt write-file dist/default.txt ok ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `vtt print-file dist/default.txt` - -restored from the default auto output archive - -``` -ok -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/disable_cache_noop_allows_cache_hit.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/disable_cache_noop_allows_cache_hit.md deleted file mode 100644 index 971a6ed09..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/disable_cache_noop_allows_cache_hit.md +++ /dev/null @@ -1,44 +0,0 @@ -# disable_cache_noop_allows_cache_hit - -Exercises the temporary `disableCache` no-op workaround. The tool asks the -runner not to cache this run, but the client ignores that request, so the next -invocation hits the cache. - -## `vt run disable-cache` - -first run — tool calls disableCache, currently ignored by the client - -``` -$ node scripts/disable_cache.mjs -``` - -## `vt run disable-cache` - -cache hit because disableCache is temporarily a no-op - -``` -$ node scripts/disable_cache.mjs ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `vt run --last-details` - -summary reports the replayed cache hit - -``` - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Vite+ Task Runner • Execution Summary -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Statistics: 1 tasks • 1 cache hits • 0 cache misses -Performance: 100% cache hit rate - -Task Details: -──────────────────────────────────────────────── - [1] ipc-client-test#disable-cache: $ node scripts/disable_cache.mjs ✓ - → Cache hit - output replayed - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/disable_cache_noop_with_explicit_inputs.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/disable_cache_noop_with_explicit_inputs.md deleted file mode 100644 index 6afee61d5..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/disable_cache_noop_with_explicit_inputs.md +++ /dev/null @@ -1,44 +0,0 @@ -# disable_cache_noop_with_explicit_inputs - -Exercises the temporary `disableCache` no-op workaround on a cached task with -explicit inputs. The client ignores the opt-out request, so the second run hits -even when fspy auto-input inference is disabled. - -## `vt run disable-cache-explicit-input` - -first run uses input: [] and calls disableCache, currently ignored by the client - -``` -$ node scripts/disable_cache.mjs -``` - -## `vt run disable-cache-explicit-input` - -cache hit because disableCache is temporarily a no-op - -``` -$ node scripts/disable_cache.mjs ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `vt run --last-details` - -summary reports the replayed cache hit - -``` - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Vite+ Task Runner • Execution Summary -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Statistics: 1 tasks • 1 cache hits • 0 cache misses -Performance: 100% cache hit rate - -Task Details: -──────────────────────────────────────────────── - [1] ipc-client-test#disable-cache-explicit-input: $ node scripts/disable_cache.mjs ✓ - → Cache hit - output replayed - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/explicit_auto_output_respects_negative_globs.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/explicit_auto_output_respects_negative_globs.md deleted file mode 100644 index b136e6c15..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/explicit_auto_output_respects_negative_globs.md +++ /dev/null @@ -1,45 +0,0 @@ -# explicit_auto_output_respects_negative_globs - -Exercises `output: [{ auto: true }, "!dist/skip.txt"]`. The runner archives auto-tracked writes except those excluded by output negative globs, so cache hits restore `dist/keep.txt` but not `dist/skip.txt`. - -## `vt run auto-output-negative` - -first run writes keep and skip files, but skip is excluded by a negative output glob - -``` -$ node scripts/auto_output_negative.mjs -``` - -## `vtt rm dist/keep.txt dist/skip.txt` - -remove both writes so restoration proves the negative glob was honored - -``` -``` - -## `vt run auto-output-negative` - -cache hit: only non-excluded auto output is restored - -``` -$ node scripts/auto_output_negative.mjs ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `vtt print-file dist/keep.txt` - -restored from the auto output archive - -``` -keep -``` - -## `vtt print-file dist/skip.txt` - -not restored because the negative output glob excluded it - -``` -dist/skip.txt: not found -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/explicit_auto_output_restores_written_files.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/explicit_auto_output_restores_written_files.md deleted file mode 100644 index 2cc35cdcc..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/explicit_auto_output_restores_written_files.md +++ /dev/null @@ -1,37 +0,0 @@ -# explicit_auto_output_restores_written_files - -Exercises `output: [{ auto: true }]` with explicit inputs disabled. The runner attaches fspy for output tracking, archives the written file, and restores it on cache hit. - -## `vt run auto-output-explicit` - -first run writes dist/auto.txt and archives fspy-tracked outputs - -``` -$ vtt write-file dist/auto.txt ok -``` - -## `vtt rm dist/auto.txt` - -remove the output so restoration is observable - -``` -``` - -## `vt run auto-output-explicit` - -cache hit: fspy-tracked output is restored - -``` -$ vtt write-file dist/auto.txt ok ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `vtt print-file dist/auto.txt` - -restored from the auto output archive - -``` -ok -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_missing_returns_undefined.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_missing_returns_undefined.md deleted file mode 100644 index ef6d4c5f5..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_missing_returns_undefined.md +++ /dev/null @@ -1,12 +0,0 @@ -# fetch_env_missing_returns_undefined - -Exercises the public `getEnv(name)` contract for an absent env var. The client API must expose `undefined` so callers can distinguish absence using the documented API. - -## `vt run fetch-missing-env` - -missing env values are normalized to undefined - -``` -$ node scripts/assert_undefined_env.mjs -missing undefined -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_reads_undeclared_env.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_reads_undeclared_env.md deleted file mode 100644 index 87380e4da..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_reads_undeclared_env.md +++ /dev/null @@ -1,13 +0,0 @@ -# fetch_env_reads_undeclared_env - -Exercises `getEnv(name)`: the tool asks the runner for an env var that is not declared in the task's `env` list. This verifies runner-served envs resolve from the unfiltered spawn env context while the child process env remains cache-filtered. - -## `PROBE_ENV=served vt run fetch-env` - -runner serves undeclared PROBE_ENV from the unfiltered env context - -``` -$ node scripts/fetch_env.mjs PROBE_ENV -served PROBE_ENV=served -process.env PROBE_ENV=(unset) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_sees_command_prefix_env.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_sees_command_prefix_env.md deleted file mode 100644 index 25cd63674..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_sees_command_prefix_env.md +++ /dev/null @@ -1,13 +0,0 @@ -# fetch_env_sees_command_prefix_env - -A command-prefixed env (`PREFIXED_ENV=from-command node ...`) is part of the spawn's full env context. `getEnv('PREFIXED_ENV')` and `process.env.PREFIXED_ENV` must both see the command prefix value. - -## `vt run fetch-prefixed-env` - -tool asks the runner for an env the command prefix sets - -``` -$ PREFIXED_ENV=from-command node scripts/fetch_env.mjs PREFIXED_ENV -served PREFIXED_ENV=from-command -process.env PREFIXED_ENV=from-command -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_sees_intermediate_prefix_envs.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_sees_intermediate_prefix_envs.md deleted file mode 100644 index 6a9d2396b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_sees_intermediate_prefix_envs.md +++ /dev/null @@ -1,13 +0,0 @@ -# fetch_env_sees_intermediate_prefix_envs - -Prefix envs accumulate through nested runs: `outer-prefixed` is `PREFIXED_A=a vt run inner-prefixed`, and `inner-prefixed` is `PREFIXED_B=b node ...`. The inner tool's `getEnv` must see both through the runner's env context, while `process.env` only sees the direct child prefix. - -## `vt run outer-prefixed` - -outer prefix env reaches the inner tool via the runner - -``` -$ PREFIXED_B=b node scripts/fetch_env.mjs PREFIXED_A PREFIXED_B -served PREFIXED_A=a PREFIXED_B=b -process.env PREFIXED_A=(unset) PREFIXED_B=b -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_tracked_invalidates_on_change.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_tracked_invalidates_on_change.md deleted file mode 100644 index cea4a5ad3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_tracked_invalidates_on_change.md +++ /dev/null @@ -1,36 +0,0 @@ -# fetch_env_tracked_invalidates_on_change - -Exercises `getEnv(name, { tracked: true })`. The env value becomes part of the post-run fingerprint: the same value still hits, and a different value misses with the env var named in the miss message. - -## `PROBE_ENV=first vt run fetch-env` - -first run captures PROBE_ENV=first in the post-run fingerprint - -``` -$ node scripts/fetch_env.mjs PROBE_ENV -served PROBE_ENV=first -process.env PROBE_ENV=(unset) -``` - -## `PROBE_ENV=first vt run fetch-env` - -cache hit: PROBE_ENV unchanged - -``` -$ node scripts/fetch_env.mjs PROBE_ENV ◉ cache hit, replaying -served PROBE_ENV=first -process.env PROBE_ENV=(unset) - ---- -vt run: cache hit. -``` - -## `PROBE_ENV=second vt run fetch-env` - -cache miss: tracked PROBE_ENV changed - -``` -$ node scripts/fetch_env.mjs PROBE_ENV ○ cache miss: env 'PROBE_ENV' changed, executing -served PROBE_ENV=second -process.env PROBE_ENV=(unset) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_tracks_with_explicit_inputs.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_tracks_with_explicit_inputs.md deleted file mode 100644 index c4a2e2b0a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_tracks_with_explicit_inputs.md +++ /dev/null @@ -1,36 +0,0 @@ -# fetch_env_tracks_with_explicit_inputs - -Runner-aware env tracking must not depend on fspy auto-input inference. This task uses `input: []`, but `getEnv(name, { tracked: true })` still records the served value and later env changes miss. - -## `PROBE_ENV=first vt run fetch-env-explicit-input` - -first run captures PROBE_ENV even though input auto-inference is disabled - -``` -$ node scripts/fetch_env.mjs PROBE_ENV -served PROBE_ENV=first -process.env PROBE_ENV=(unset) -``` - -## `PROBE_ENV=first vt run fetch-env-explicit-input` - -cache hit: explicit inputs are unchanged and PROBE_ENV is unchanged - -``` -$ node scripts/fetch_env.mjs PROBE_ENV ◉ cache hit, replaying -served PROBE_ENV=first -process.env PROBE_ENV=(unset) - ---- -vt run: cache hit. -``` - -## `PROBE_ENV=second vt run fetch-env-explicit-input` - -cache miss: tracked env changed despite input auto-inference being disabled - -``` -$ node scripts/fetch_env.mjs PROBE_ENV ○ cache miss: env 'PROBE_ENV' changed, executing -served PROBE_ENV=second -process.env PROBE_ENV=(unset) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_untracked_does_not_invalidate.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_untracked_does_not_invalidate.md deleted file mode 100644 index faa760031..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_env_untracked_does_not_invalidate.md +++ /dev/null @@ -1,26 +0,0 @@ -# fetch_env_untracked_does_not_invalidate - -Exercises `getEnv(name, { tracked: false })`. The runner still serves the env value, but the value does not enter the post-run fingerprint, so changing it later replays the cached output. - -## `PROBE_ENV=first vt run fetch-env-untracked` - -first run serves PROBE_ENV without tracking it - -``` -$ node scripts/fetch_env.mjs --untracked PROBE_ENV -served PROBE_ENV=first -process.env PROBE_ENV=(unset) -``` - -## `PROBE_ENV=second vt run fetch-env-untracked` - -cache hit: PROBE_ENV changed but was requested with tracked: false - -``` -$ node scripts/fetch_env.mjs --untracked PROBE_ENV ◉ cache hit, replaying -served PROBE_ENV=first -process.env PROBE_ENV=(unset) - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_prefix_reads_match_set.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_prefix_reads_match_set.md deleted file mode 100644 index 833953897..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_prefix_reads_match_set.md +++ /dev/null @@ -1,13 +0,0 @@ -# fetch_envs_prefix_reads_match_set - -Exercises `getEnvs({ prefix: "PROBE_" })`: the tool asks the runner for every env whose name starts with `PROBE_` and prints the served match set. - -## `PROBE_A=a PROBE_B=b PROBEX=not-a-prefix-match UNRELATED=noise vt run fetch-envs-prefix` - -runner serves only envs with the literal PROBE_ prefix - -``` -$ node scripts/fetch_envs.mjs --prefix -PROBE_A=a -PROBE_B=b -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_prefix_treats_star_literally.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_prefix_treats_star_literally.md deleted file mode 100644 index 01f4b2d18..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_prefix_treats_star_literally.md +++ /dev/null @@ -1,12 +0,0 @@ -# fetch_envs_prefix_treats_star_literally - -Exercises `getEnvs({ prefix: "PROBE_*" })`: `*` is part of the prefix string, not a glob wildcard. - -## `PROBE_*A=literal PROBE_XA=wildcard-if-glob PROBE_A=also-wildcard-if-glob vt run fetch-envs-star-prefix` - -runner serves only envs whose name starts with literal PROBE_* - -``` -$ node scripts/fetch_envs.mjs --prefix PROBE_* -PROBE_*A=literal -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_reads_match_set.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_reads_match_set.md deleted file mode 100644 index 265770d31..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_reads_match_set.md +++ /dev/null @@ -1,13 +0,0 @@ -# fetch_envs_reads_match_set - -Exercises `getEnvs(pattern)`: the tool asks the runner for every env matching `PROBE_*` and prints the served match set. This verifies the bulk env IPC round trip before match sets are added to cache fingerprints. - -## `PROBE_A=a PROBE_B=b UNRELATED=noise vt run fetch-envs` - -runner serves only envs matching PROBE_* - -``` -$ node scripts/fetch_envs.mjs -PROBE_A=a -PROBE_B=b -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_tracks_glob_match_set.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_tracks_glob_match_set.md deleted file mode 100644 index ae9d711c8..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_tracks_glob_match_set.md +++ /dev/null @@ -1,70 +0,0 @@ -# fetch_envs_tracks_glob_match_set - -Exercises `getEnvs(pattern, { tracked: true })`. The glob `PROBE_*` and its match-set snapshot enter the post-run fingerprint: later runs miss on changed, added, or removed matching envs, but hit when only non-matching envs differ. - -## `PROBE_A=a PROBE_B=b vt run fetch-envs` - -populate: first run captures {PROBE_A, PROBE_B} under the glob - -``` -$ node scripts/fetch_envs.mjs -PROBE_A=a -PROBE_B=b -``` - -## `PROBE_A=a PROBE_B=b vt run fetch-envs` - -unchanged: same match-set -> cache hit - -``` -$ node scripts/fetch_envs.mjs ◉ cache hit, replaying -PROBE_A=a -PROBE_B=b - ---- -vt run: cache hit. -``` - -## `PROBE_A=changed PROBE_B=b vt run fetch-envs` - -change: PROBE_A value differs -> cache miss - -``` -$ node scripts/fetch_envs.mjs ○ cache miss: env 'PROBE_A' changed, executing -PROBE_A=changed -PROBE_B=b -``` - -## `PROBE_A=changed PROBE_B=b PROBE_C=c vt run fetch-envs` - -add: PROBE_C is new under the glob -> cache miss - -``` -$ node scripts/fetch_envs.mjs ○ cache miss: env 'PROBE_C' added, executing -PROBE_A=changed -PROBE_B=b -PROBE_C=c -``` - -## `PROBE_B=b PROBE_C=c vt run fetch-envs` - -remove: PROBE_A dropped from the match-set -> cache miss - -``` -$ node scripts/fetch_envs.mjs ○ cache miss: env 'PROBE_A' removed, executing -PROBE_B=b -PROBE_C=c -``` - -## `PROBE_B=b PROBE_C=c UNRELATED=noise vt run fetch-envs` - -non-matching noise: UNRELATED does not match PROBE_* -> cache hit - -``` -$ node scripts/fetch_envs.mjs ◉ cache hit, replaying -PROBE_B=b -PROBE_C=c - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_untracked_does_not_invalidate.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_untracked_does_not_invalidate.md deleted file mode 100644 index d66704ae5..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/fetch_envs_untracked_does_not_invalidate.md +++ /dev/null @@ -1,26 +0,0 @@ -# fetch_envs_untracked_does_not_invalidate - -Exercises `getEnvs(pattern, { tracked: false })`. The runner still serves the matching envs, but the match set does not enter the post-run fingerprint, so changing it later replays the cached output. - -## `PROBE_A=a PROBE_B=b vt run fetch-envs-untracked` - -first run serves the PROBE_* match set without tracking it - -``` -$ node scripts/fetch_envs.mjs --untracked -PROBE_A=a -PROBE_B=b -``` - -## `PROBE_A=changed PROBE_B=b PROBE_C=c vt run fetch-envs-untracked` - -cache hit: changed match set was requested with tracked: false - -``` -$ node scripts/fetch_envs.mjs --untracked ◉ cache hit, replaying -PROBE_A=a -PROBE_B=b - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/ignore_input_keeps_cache_valid.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/ignore_input_keeps_cache_valid.md deleted file mode 100644 index 30e1fcc36..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/ignore_input_keeps_cache_valid.md +++ /dev/null @@ -1,102 +0,0 @@ -# ignore_input_keeps_cache_valid - -Exercises `ignoreInput` through `@voidzero-dev/vite-task-client`. The runner treats `cache_like/` as non-input for auto-inferred reads, but declared inputs under that path stay in the configured cache key. - -## `vtt write-file cache_like/input.txt before` - -seed the file the task will read and ignore - -``` -``` - -## `vt run ignore-input` - -populate the cache - -``` -$ node scripts/ignore_input.mjs -before -``` - -## `vtt write-file cache_like/input.txt after` - -mutate the ignored input — would invalidate if tracked - -``` -``` - -## `vt run ignore-input` - -cache hit: cache_like/ was ignored via ignoreInput - -``` -$ node scripts/ignore_input.mjs ◉ cache hit, replaying -before - ---- -vt run: cache hit. -``` - -## `vtt write-file cache_like/input.txt manual-before` - -seed the declared input for input: [cache_like/input.txt] - -``` -``` - -## `vt run ignore-input-manual-input` - -populate the cache with the manual-only input fingerprint - -``` -$ node scripts/ignore_input.mjs -manual-before -``` - -## `vtt write-file cache_like/input.txt manual-after` - -mutate the declared input for input: [cache_like/input.txt] - -``` -``` - -## `vt run ignore-input-manual-input` - -cache miss: manual input config wins over ignoreInput - -``` -$ node scripts/ignore_input.mjs ○ cache miss: 'cache_like/input.txt' modified, executing -manual-after -``` - -## `vtt write-file cache_like/input.txt auto-manual-before` - -seed the declared input for input: [{ auto: true }, cache_like/input.txt] - -``` -``` - -## `vt run ignore-input-auto-manual-input` - -populate the cache with the auto-plus-manual input fingerprint - -``` -$ node scripts/ignore_input.mjs -auto-manual-before -``` - -## `vtt write-file cache_like/input.txt auto-manual-after` - -mutate the declared input for input: [{ auto: true }, cache_like/input.txt] - -``` -``` - -## `vt run ignore-input-auto-manual-input` - -cache miss: explicit input still wins when auto inference is also enabled - -``` -$ node scripts/ignore_input.mjs ○ cache miss: 'cache_like/input.txt' modified, executing -auto-manual-after -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/ignore_output_allows_read_write_overlap.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/ignore_output_allows_read_write_overlap.md deleted file mode 100644 index b1e8b3140..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/ignore_output_allows_read_write_overlap.md +++ /dev/null @@ -1,37 +0,0 @@ -# ignore_output_allows_read_write_overlap - -Exercises `ignoreOutput` with auto output tracking. The task reads and writes `sidecar/tmp.txt`; without the ignore the runner's read-write overlap check would refuse to cache the run. The task also writes `dist/out.txt`, which should be auto-archived and restored on a cache hit. - -## `vt run ignore-output` - -first run populates the cache - -``` -$ node scripts/ignore_output.mjs -``` - -## `vtt rm dist/out.txt` - -remove the auto output so restoration is observable - -``` -``` - -## `vt run ignore-output` - -cache hit: sidecar/ writes were ignored - -``` -$ node scripts/ignore_output.mjs ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `vtt print-file dist/out.txt` - -restored from the auto output archive - -``` -ok -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/output_negative_glob_prevents_read_write_overlap.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/output_negative_glob_prevents_read_write_overlap.md deleted file mode 100644 index 44d9003da..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/snapshots/output_negative_glob_prevents_read_write_overlap.md +++ /dev/null @@ -1,37 +0,0 @@ -# output_negative_glob_prevents_read_write_overlap - -Exercises `output: [{ auto: true }, "!scratch/**"]` on a task that reads and writes `scratch/overlap.txt`. Because the write is excluded from the auto-output set, it should not count as a read-write overlap, while non-excluded outputs are still archived and restored. - -## `vt run auto-output-negative-overlap` - -first run reads and writes an output-negative path - -``` -$ node scripts/auto_output_negative_overlap.mjs -``` - -## `vtt rm dist/negative-overlap.txt` - -remove the non-excluded output so restoration is observable - -``` -``` - -## `vt run auto-output-negative-overlap` - -cache hit: the output-negative write did not block caching - -``` -$ node scripts/auto_output_negative_overlap.mjs ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `vtt print-file dist/negative-overlap.txt` - -restored from the auto output archive - -``` -keep -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/vite-task.json deleted file mode 100644 index 4f00c3ceb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/ipc_client_test/vite-task.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "tasks": { - "ignore-input": { - "command": "node scripts/ignore_input.mjs", - "cache": true - }, - "ignore-input-manual-input": { - "command": "node scripts/ignore_input.mjs", - "input": ["cache_like/input.txt"], - "output": [], - "cache": true - }, - "ignore-input-auto-manual-input": { - "command": "node scripts/ignore_input.mjs", - "input": [{ "auto": true }, "cache_like/input.txt"], - "output": [], - "cache": true - }, - "ignore-output": { - "command": "node scripts/ignore_output.mjs", - "output": [{ "auto": true }], - "cache": true - }, - "auto-output-explicit": { - "command": "vtt write-file dist/auto.txt ok", - "input": [], - "output": [{ "auto": true }], - "cache": true - }, - "auto-output-negative": { - "command": "node scripts/auto_output_negative.mjs", - "input": [], - "output": [{ "auto": true }, "!dist/skip.txt"], - "cache": true - }, - "auto-output-negative-overlap": { - "command": "node scripts/auto_output_negative_overlap.mjs", - "input": [{ "auto": true }], - "output": [{ "auto": true }, "!scratch/**"], - "cache": true - }, - "auto-output-default": { - "command": "vtt write-file dist/default.txt ok", - "input": [], - "cache": true - }, - "disable-cache": { - "command": "node scripts/disable_cache.mjs", - "cache": true - }, - "disable-cache-explicit-input": { - "command": "node scripts/disable_cache.mjs", - "input": [], - "output": [], - "cache": true - }, - "fetch-env": { - "command": "node scripts/fetch_env.mjs PROBE_ENV", - "cache": true - }, - "fetch-missing-env": { - "command": "node scripts/assert_undefined_env.mjs", - "cache": true - }, - "fetch-env-explicit-input": { - "command": "node scripts/fetch_env.mjs PROBE_ENV", - "input": [], - "output": [], - "cache": true - }, - "fetch-envs": { - "command": "node scripts/fetch_envs.mjs", - "cache": true - }, - "fetch-envs-prefix": { - "command": "node scripts/fetch_envs.mjs --prefix", - "cache": true - }, - "fetch-envs-star-prefix": { - "command": "node scripts/fetch_envs.mjs --prefix PROBE_*", - "cache": true - }, - "fetch-env-untracked": { - "command": "node scripts/fetch_env.mjs --untracked PROBE_ENV", - "cache": true - }, - "fetch-envs-untracked": { - "command": "node scripts/fetch_envs.mjs --untracked", - "cache": true - }, - "fetch-prefixed-env": { - "command": "PREFIXED_ENV=from-command node scripts/fetch_env.mjs PREFIXED_ENV", - "cache": true - }, - "outer-prefixed": { - "command": "PREFIXED_A=a vt run inner-prefixed", - "cache": true - }, - "inner-prefixed": { - "command": "PREFIXED_B=b node scripts/fetch_env.mjs PREFIXED_A PREFIXED_B", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/package.json deleted file mode 100644 index 8d8424938..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "labeled-stdio-test", - "private": true, - "dependencies": { - "other": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/packages/other/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/packages/other/package.json deleted file mode 100644 index 6bf03d25d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/packages/other/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "other", - "scripts": { - "check-tty": "vtt check-tty", - "check-tty-cached": "vtt check-tty", - "read-stdin": "vtt read-stdin" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots.toml deleted file mode 100644 index 3346a85ef..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots.toml +++ /dev/null @@ -1,76 +0,0 @@ -[[e2e]] -name = "single_task__cache_off__piped_stdio" -comment = """ -Under `--log=labeled` with caching off, a single task's stdio should be piped through the line-prefixing writer (not a TTY). -""" -steps = [["vt", "run", "--log=labeled", "check-tty"]] - -[[e2e]] -name = "multiple_tasks__cache_off__piped_stdio" -comment = """ -Under `--log=labeled` with caching off, multiple tasks should each get piped stdio and each line should be prefixed with `[pkg#task]`. -""" -steps = [["vt", "run", "--log=labeled", "-r", "check-tty"]] - -[[e2e]] -name = "single_task__cache_miss__piped_stdio" -comment = """ -On a cache miss for a single task, labeled mode should still pipe stdio and prefix output lines. -""" -steps = [["vt", "run", "--log=labeled", "check-tty-cached"]] - -[[e2e]] -name = "multiple_tasks__cache_miss__piped_stdio" -comment = """ -On a cache miss across multiple tasks, each task's piped output should be individually prefixed in labeled mode. -""" -steps = [["vt", "run", "--log=labeled", "-r", "check-tty-cached"]] - -[[e2e]] -name = "single_task__cache_hit__replayed" -comment = """ -A cache-hit replay of a single task under labeled mode should reproduce the labeled output from cached data. -""" -steps = [ - [ - "vt", - "run", - "--log=labeled", - "check-tty-cached", - ], - [ - "vt", - "run", - "--log=labeled", - "check-tty-cached", - ], -] - -[[e2e]] -name = "multiple_tasks__cache_hit__replayed" -comment = """ -A cache-hit replay across multiple tasks under labeled mode should reproduce each task's labeled output. -""" -steps = [ - [ - "vt", - "run", - "--log=labeled", - "-r", - "check-tty-cached", - ], - [ - "vt", - "run", - "--log=labeled", - "-r", - "check-tty-cached", - ], -] - -[[e2e]] -name = "stdin_is_always_null" -comment = """ -In labeled mode, task stdin is always `/dev/null` regardless of the parent's stdin — piping data in from outside must not reach the task. -""" -steps = [["vtt", "pipe-stdin", "from-stdin", "--", "vt", "run", "--log=labeled", "read-stdin"]] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/multiple_tasks__cache_hit__replayed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/multiple_tasks__cache_hit__replayed.md deleted file mode 100644 index 7c44d58b9..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/multiple_tasks__cache_hit__replayed.md +++ /dev/null @@ -1,37 +0,0 @@ -# multiple_tasks__cache_hit__replayed - -A cache-hit replay across multiple tasks under labeled mode should reproduce each task's labeled output. - -## `vt run --log=labeled -r check-tty-cached` - -``` -[other#check-tty-cached] ~/packages/other$ vtt check-tty -[other#check-tty-cached] stdin:not-tty -[other#check-tty-cached] stdout:not-tty -[other#check-tty-cached] stderr:not-tty - -[labeled-stdio-test#check-tty-cached] $ vtt check-tty -[labeled-stdio-test#check-tty-cached] stdin:not-tty -[labeled-stdio-test#check-tty-cached] stdout:not-tty -[labeled-stdio-test#check-tty-cached] stderr:not-tty - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` - -## `vt run --log=labeled -r check-tty-cached` - -``` -[other#check-tty-cached] ~/packages/other$ vtt check-tty ◉ cache hit, replaying -[other#check-tty-cached] stdin:not-tty -[other#check-tty-cached] stdout:not-tty -[other#check-tty-cached] stderr:not-tty - -[labeled-stdio-test#check-tty-cached] $ vtt check-tty ◉ cache hit, replaying -[labeled-stdio-test#check-tty-cached] stdin:not-tty -[labeled-stdio-test#check-tty-cached] stdout:not-tty -[labeled-stdio-test#check-tty-cached] stderr:not-tty - ---- -vt run: 2/2 cache hit (100%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/multiple_tasks__cache_miss__piped_stdio.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/multiple_tasks__cache_miss__piped_stdio.md deleted file mode 100644 index e9d2bf9b4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/multiple_tasks__cache_miss__piped_stdio.md +++ /dev/null @@ -1,20 +0,0 @@ -# multiple_tasks__cache_miss__piped_stdio - -On a cache miss across multiple tasks, each task's piped output should be individually prefixed in labeled mode. - -## `vt run --log=labeled -r check-tty-cached` - -``` -[other#check-tty-cached] ~/packages/other$ vtt check-tty -[other#check-tty-cached] stdin:not-tty -[other#check-tty-cached] stdout:not-tty -[other#check-tty-cached] stderr:not-tty - -[labeled-stdio-test#check-tty-cached] $ vtt check-tty -[labeled-stdio-test#check-tty-cached] stdin:not-tty -[labeled-stdio-test#check-tty-cached] stdout:not-tty -[labeled-stdio-test#check-tty-cached] stderr:not-tty - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/multiple_tasks__cache_off__piped_stdio.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/multiple_tasks__cache_off__piped_stdio.md deleted file mode 100644 index fecd082e8..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/multiple_tasks__cache_off__piped_stdio.md +++ /dev/null @@ -1,20 +0,0 @@ -# multiple_tasks__cache_off__piped_stdio - -Under `--log=labeled` with caching off, multiple tasks should each get piped stdio and each line should be prefixed with `[pkg#task]`. - -## `vt run --log=labeled -r check-tty` - -``` -[other#check-tty] ~/packages/other$ vtt check-tty -[other#check-tty] stdin:not-tty -[other#check-tty] stdout:not-tty -[other#check-tty] stderr:not-tty - -[labeled-stdio-test#check-tty] $ vtt check-tty ⊘ cache disabled -[labeled-stdio-test#check-tty] stdin:not-tty -[labeled-stdio-test#check-tty] stdout:not-tty -[labeled-stdio-test#check-tty] stderr:not-tty - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/single_task__cache_hit__replayed.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/single_task__cache_hit__replayed.md deleted file mode 100644 index 3199e6816..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/single_task__cache_hit__replayed.md +++ /dev/null @@ -1,24 +0,0 @@ -# single_task__cache_hit__replayed - -A cache-hit replay of a single task under labeled mode should reproduce the labeled output from cached data. - -## `vt run --log=labeled check-tty-cached` - -``` -[labeled-stdio-test#check-tty-cached] $ vtt check-tty -[labeled-stdio-test#check-tty-cached] stdin:not-tty -[labeled-stdio-test#check-tty-cached] stdout:not-tty -[labeled-stdio-test#check-tty-cached] stderr:not-tty -``` - -## `vt run --log=labeled check-tty-cached` - -``` -[labeled-stdio-test#check-tty-cached] $ vtt check-tty ◉ cache hit, replaying -[labeled-stdio-test#check-tty-cached] stdin:not-tty -[labeled-stdio-test#check-tty-cached] stdout:not-tty -[labeled-stdio-test#check-tty-cached] stderr:not-tty - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/single_task__cache_miss__piped_stdio.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/single_task__cache_miss__piped_stdio.md deleted file mode 100644 index 0a0bdc07c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/single_task__cache_miss__piped_stdio.md +++ /dev/null @@ -1,12 +0,0 @@ -# single_task__cache_miss__piped_stdio - -On a cache miss for a single task, labeled mode should still pipe stdio and prefix output lines. - -## `vt run --log=labeled check-tty-cached` - -``` -[labeled-stdio-test#check-tty-cached] $ vtt check-tty -[labeled-stdio-test#check-tty-cached] stdin:not-tty -[labeled-stdio-test#check-tty-cached] stdout:not-tty -[labeled-stdio-test#check-tty-cached] stderr:not-tty -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/single_task__cache_off__piped_stdio.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/single_task__cache_off__piped_stdio.md deleted file mode 100644 index 50b558924..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/single_task__cache_off__piped_stdio.md +++ /dev/null @@ -1,12 +0,0 @@ -# single_task__cache_off__piped_stdio - -Under `--log=labeled` with caching off, a single task's stdio should be piped through the line-prefixing writer (not a TTY). - -## `vt run --log=labeled check-tty` - -``` -[labeled-stdio-test#check-tty] $ vtt check-tty ⊘ cache disabled -[labeled-stdio-test#check-tty] stdin:not-tty -[labeled-stdio-test#check-tty] stdout:not-tty -[labeled-stdio-test#check-tty] stderr:not-tty -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/stdin_is_always_null.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/stdin_is_always_null.md deleted file mode 100644 index 097c2406e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/snapshots/stdin_is_always_null.md +++ /dev/null @@ -1,9 +0,0 @@ -# stdin_is_always_null - -In labeled mode, task stdin is always `/dev/null` regardless of the parent's stdin — piping data in from outside must not reach the task. - -## `vtt pipe-stdin from-stdin -- vt run --log=labeled read-stdin` - -``` -[labeled-stdio-test#read-stdin] $ vtt read-stdin ⊘ cache disabled -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/vite-task.json deleted file mode 100644 index 9f6a560f4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/labeled_stdio/vite-task.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "cache": true, - "tasks": { - "check-tty": { - "command": "vtt check-tty", - "cache": false - }, - "check-tty-cached": { - "command": "vtt check-tty", - "cache": true - }, - "read-stdin": { - "command": "vtt read-stdin", - "cache": false - }, - "read-stdin-cached": { - "command": "vtt read-stdin", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/package.json deleted file mode 100644 index aeae8e0c7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "@test/legacy-cache-ignored" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/snapshots.toml deleted file mode 100644 index 5028a3d31..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/snapshots.toml +++ /dev/null @@ -1,23 +0,0 @@ -[[e2e]] -name = "leftover_cache_from_another_version_is_ignored" -comment = """ -A cache left at the old top-level location by a different Vite+ version is ignored: this build reads and writes only its own per-schema-version subdirectory, so the leftover database never aborts the run (the bug behind vite-plus#1785) and caching still works. -""" -steps = [ - { argv = [ - "vtt", - "write-file", - "node_modules/.vite/task-cache/cache.db", - "cache from another version", - ], comment = "simulate a leftover cache database from a different Vite+ version" }, - { argv = [ - "vt", - "run", - "cached-task", - ], comment = "first run is unaffected by the leftover (cache miss)" }, - { argv = [ - "vt", - "run", - "cached-task", - ], comment = "second run hits this build's own per-version cache" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/snapshots/leftover_cache_from_another_version_is_ignored.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/snapshots/leftover_cache_from_another_version_is_ignored.md deleted file mode 100644 index a0af49ce7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/snapshots/leftover_cache_from_another_version_is_ignored.md +++ /dev/null @@ -1,31 +0,0 @@ -# leftover_cache_from_another_version_is_ignored - -A cache left at the old top-level location by a different Vite+ version is ignored: this build reads and writes only its own per-schema-version subdirectory, so the leftover database never aborts the run (the bug behind vite-plus#1785) and caching still works. - -## `vtt write-file node_modules/.vite/task-cache/cache.db 'cache from another version'` - -simulate a leftover cache database from a different Vite+ version - -``` -``` - -## `vt run cached-task` - -first run is unaffected by the leftover (cache miss) - -``` -$ vtt print-file test.txt -test content -``` - -## `vt run cached-task` - -second run hits this build's own per-version cache - -``` -$ vtt print-file test.txt ◉ cache hit, replaying -test content - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/test.txt b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/test.txt deleted file mode 100644 index d670460b4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/test.txt +++ /dev/null @@ -1 +0,0 @@ -test content diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/vite-task.json deleted file mode 100644 index 741157963..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/legacy_cache_ignored/vite-task.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "cache": true, - "tasks": { - "cached-task": { - "command": "vtt print-file test.txt", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/package.json deleted file mode 100644 index 13bc35eb6..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "malformed-fspy-path", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/snapshots.toml deleted file mode 100644 index 28398b3b0..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/snapshots.toml +++ /dev/null @@ -1,6 +0,0 @@ -[[e2e]] -name = "malformed_observed_path_does_not_panic" -comment = """ -Repro for issue 325: a malformed observed path must not panic when fspy input inference normalizes workspace-relative accesses. -""" -steps = [{ argv = ["vt", "run", "read-malformed-path"], envs = [["TEMP", "."], ["TMP", "."]] }] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/snapshots/malformed_observed_path_does_not_panic.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/snapshots/malformed_observed_path_does_not_panic.md deleted file mode 100644 index 5f46fdcaa..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/snapshots/malformed_observed_path_does_not_panic.md +++ /dev/null @@ -1,10 +0,0 @@ -# malformed_observed_path_does_not_panic - -Repro for issue 325: a malformed observed path must not panic when fspy input inference normalizes workspace-relative accesses. - -## `TEMP=. TMP=. vt run read-malformed-path` - -``` -$ vtt print-file foo/C:/bar -foo/C:/bar: not found -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/vite-task.json deleted file mode 100644 index c6790a866..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/malformed_fspy_path/vite-task.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "tasks": { - "read-malformed-path": { - "command": "vtt print-file foo/C:/bar", - "cache": true, - "input": [ - { - "auto": true - } - ] - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/a.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/a.mjs deleted file mode 100644 index 647828c70..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/a.mjs +++ /dev/null @@ -1 +0,0 @@ -export const value = 'a'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/b.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/b.mjs deleted file mode 100644 index cb16d9402..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/b.mjs +++ /dev/null @@ -1 +0,0 @@ -export const value = 'b'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/c.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/c.mjs deleted file mode 100644 index 2a0fd0fb2..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/c.mjs +++ /dev/null @@ -1 +0,0 @@ -export const value = 'c'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/package.json deleted file mode 100644 index bc695cb8e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "node-compile-cache-outside-workspace-fixture", - "private": true, - "type": "module" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/run.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/run.mjs deleted file mode 100644 index a361ddad4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/run.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Tiny Node script that turns on the v22 compile cache and imports a -// few sibling modules so the cache directory actually gets some files -// written to it. On a normally-configured machine the cache lives in -// the OS temp directory (outside the workspace), so the runner doesn't -// see those files when it decides whether the run can be cached. -// -// If the spawned process doesn't have LOCALAPPDATA (or TMP/TEMP/ -// USERPROFILE) set on Windows, Node ends up putting the cache inside -// the workspace, the same files are both written and read in this one -// run, and the runner refuses to cache it. That's the bug this fixture -// catches. -import { enableCompileCache } from 'node:module'; - -enableCompileCache(); - -await import('./a.mjs'); -await import('./b.mjs'); -await import('./c.mjs'); - -console.log('done'); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots.toml deleted file mode 100644 index 06c1507b1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots.toml +++ /dev/null @@ -1,20 +0,0 @@ -[[e2e]] -name = "node_compile_cache_does_not_poison_workspace" -comment = """ -Runs a small Node script that turns on Node's compile cache. The cache should land in the OS temp directory (outside the workspace), so two `vt run --cache build` calls should be a miss then a hit. On Windows, if the spawned task env doesn't have `LOCALAPPDATA`, Node puts the cache inside the workspace instead, the runner sees the same files both written and read, and refuses to cache the run — so the second call becomes another miss with a "not cached because it modified its input" message. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "--cache", - "build", - ], comment = "first run: cache miss" }, - { argv = [ - "vt", - "run", - "--cache", - "build", - ], comment = "second run: cache hit" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots/node_compile_cache_does_not_poison_workspace.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots/node_compile_cache_does_not_poison_workspace.md deleted file mode 100644 index e0f113f81..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/snapshots/node_compile_cache_does_not_poison_workspace.md +++ /dev/null @@ -1,24 +0,0 @@ -# node_compile_cache_does_not_poison_workspace - -Runs a small Node script that turns on Node's compile cache. The cache should land in the OS temp directory (outside the workspace), so two `vt run --cache build` calls should be a miss then a hit. On Windows, if the spawned task env doesn't have `LOCALAPPDATA`, Node puts the cache inside the workspace instead, the runner sees the same files both written and read, and refuses to cache the run — so the second call becomes another miss with a "not cached because it modified its input" message. - -## `vt run --cache build` - -first run: cache miss - -``` -$ node run.mjs -done -``` - -## `vt run --cache build` - -second run: cache hit - -``` -$ node run.mjs ◉ cache hit, replaying -done - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/vite-task.json deleted file mode 100644 index 8c247f01e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/node_compile_cache_outside_workspace/vite-task.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tasks": { - "build": { - "command": "node run.mjs", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/package.json deleted file mode 100644 index 60b87e87f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "output-cache-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml deleted file mode 100644 index c72cc53f3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots.toml +++ /dev/null @@ -1,117 +0,0 @@ -[[e2e]] -name = "output_globs___files_restored_on_cache_hit" -comment = """ -With explicit output globs (`dist/**`), the first run writes a file to `dist/`. After deleting `dist/`, a second run with no input changes is a cache hit and the archived output file is restored. -""" -steps = [ - { argv = [ - "vt", - "run", - "build", - ], comment = "first run — cache miss, writes dist/output.txt" }, - { argv = [ - "vtt", - "print-file", - "dist/output.txt", - ], comment = "file is on disk after the run" }, - { argv = [ - "vtt", - "rm", - "-rf", - "dist", - ], comment = "delete dist/ to prove the restore is real" }, - { argv = [ - "vt", - "run", - "build", - ], comment = "second run — cache hit, restores from archive" }, - { argv = [ - "vtt", - "print-file", - "dist/output.txt", - ], comment = "file restored from archive" }, -] - -[[e2e]] -name = "output_globs___old_archive_removed_on_rewrite" -comment = """ -When a cached task re-runs (cache miss because an input changed), it writes a new archive and the previous archive file is cleaned up. After two cache-missing runs of the same task the cache directory still contains only one `.tar.zst` archive. -""" -steps = [ - { argv = [ - "vt", - "run", - "build", - ], comment = "first run — cache miss, writes archive A" }, - { argv = [ - "vtt", - "list-dir", - "node_modules/.vite/task-cache", - "--ext", - ".tar.zst", - "--recursive", - ], comment = "exactly one archive on disk" }, - { argv = [ - "vtt", - "write-file", - "src/main.ts", - "changed", - ], comment = "modify an input so the next run is a cache miss" }, - { argv = [ - "vt", - "run", - "build", - ], comment = "second run — cache miss, writes archive B and removes A" }, - { argv = [ - "vtt", - "list-dir", - "node_modules/.vite/task-cache", - "--ext", - ".tar.zst", - "--recursive", - ], comment = "still exactly one archive — A was cleaned up" }, -] - -[[e2e]] -name = "output_globs___negative_excludes_files_from_archive" -comment = """ -A file matched by a negative output glob is not archived, so it is not restored on cache hit. -""" -steps = [ - { argv = [ - "vt", - "run", - "build-with-negative", - ], comment = "first run — writes dist/keep.txt and dist/skip.txt" }, - { argv = [ - "vtt", - "print-file", - "dist/keep.txt", - ], comment = "keep.txt was written" }, - { argv = [ - "vtt", - "print-file", - "dist/skip.txt", - ], comment = "skip.txt was written" }, - { argv = [ - "vtt", - "rm", - "-rf", - "dist", - ], comment = "delete dist/ to prove the restore is real" }, - { argv = [ - "vt", - "run", - "build-with-negative", - ], comment = "second run — cache hit, restores from archive" }, - { argv = [ - "vtt", - "print-file", - "dist/keep.txt", - ], comment = "keep.txt restored from archive" }, - { argv = [ - "vtt", - "print-file", - "dist/skip.txt", - ], comment = "skip.txt is NOT restored — it was excluded by the negative glob" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md deleted file mode 100644 index 6af5672be..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___files_restored_on_cache_hit.md +++ /dev/null @@ -1,45 +0,0 @@ -# output_globs___files_restored_on_cache_hit - -With explicit output globs (`dist/**`), the first run writes a file to `dist/`. After deleting `dist/`, a second run with no input changes is a cache hit and the archived output file is restored. - -## `vt run build` - -first run — cache miss, writes dist/output.txt - -``` -$ vtt write-file dist/output.txt built -``` - -## `vtt print-file dist/output.txt` - -file is on disk after the run - -``` -built -``` - -## `vtt rm -rf dist` - -delete dist/ to prove the restore is real - -``` -``` - -## `vt run build` - -second run — cache hit, restores from archive - -``` -$ vtt write-file dist/output.txt built ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `vtt print-file dist/output.txt` - -file restored from archive - -``` -built -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md deleted file mode 100644 index 79aa568eb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___negative_excludes_files_from_archive.md +++ /dev/null @@ -1,68 +0,0 @@ -# output_globs___negative_excludes_files_from_archive - -A file matched by a negative output glob is not archived, so it is not restored on cache hit. - -## `vt run build-with-negative` - -first run — writes dist/keep.txt and dist/skip.txt - -``` -$ vtt write-file dist/keep.txt keep - -$ vtt write-file dist/skip.txt skip - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` - -## `vtt print-file dist/keep.txt` - -keep.txt was written - -``` -keep -``` - -## `vtt print-file dist/skip.txt` - -skip.txt was written - -``` -skip -``` - -## `vtt rm -rf dist` - -delete dist/ to prove the restore is real - -``` -``` - -## `vt run build-with-negative` - -second run — cache hit, restores from archive - -``` -$ vtt write-file dist/keep.txt keep ◉ cache hit, replaying - -$ vtt write-file dist/skip.txt skip ◉ cache hit, replaying - ---- -vt run: 2/2 cache hit (100%). (Run `vt run --last-details` for full details) -``` - -## `vtt print-file dist/keep.txt` - -keep.txt restored from archive - -``` -keep -``` - -## `vtt print-file dist/skip.txt` - -skip.txt is NOT restored — it was excluded by the negative glob - -``` -dist/skip.txt: not found -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md deleted file mode 100644 index 3bafc9d02..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/snapshots/output_globs___old_archive_removed_on_rewrite.md +++ /dev/null @@ -1,42 +0,0 @@ -# output_globs___old_archive_removed_on_rewrite - -When a cached task re-runs (cache miss because an input changed), it writes a new archive and the previous archive file is cleaned up. After two cache-missing runs of the same task the cache directory still contains only one `.tar.zst` archive. - -## `vt run build` - -first run — cache miss, writes archive A - -``` -$ vtt write-file dist/output.txt built -``` - -## `vtt list-dir node_modules/.vite/task-cache --ext .tar.zst --recursive` - -exactly one archive on disk - -``` -.tar.zst -``` - -## `vtt write-file src/main.ts changed` - -modify an input so the next run is a cache miss - -``` -``` - -## `vt run build` - -second run — cache miss, writes archive B and removes A - -``` -$ vtt write-file dist/output.txt built ○ cache miss: 'src/main.ts' modified, executing -``` - -## `vtt list-dir node_modules/.vite/task-cache --ext .tar.zst --recursive` - -still exactly one archive — A was cleaned up - -``` -.tar.zst -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/src/main.ts b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/src/main.ts deleted file mode 100644 index 38e000a1c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -export const main = 'initial'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/vite-task.json deleted file mode 100644 index 6bd35a604..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/output_cache_test/vite-task.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "tasks": { - "build": { - "command": "vtt write-file dist/output.txt built", - "input": ["src/**"], - "output": ["dist/**"], - "cache": true - }, - "build-with-negative": { - "command": "vtt write-file dist/keep.txt keep && vtt write-file dist/skip.txt skip", - "input": ["src/**"], - "output": ["dist/**", "!dist/skip.txt"], - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/package.json deleted file mode 100644 index 9bb9eec66..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "parallel-execution-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/packages/a/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/packages/a/package.json deleted file mode 100644 index 094c896c1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/packages/a/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@parallel/a", - "scripts": { - "build": "vtt barrier ../../.barrier sync 2" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/packages/b/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/packages/b/package.json deleted file mode 100644 index 84cf64291..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/packages/b/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@parallel/b", - "scripts": { - "build": "vtt barrier ../../.barrier sync 2" - }, - "dependencies": { - "@parallel/a": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/snapshots.toml deleted file mode 100644 index 1e62388c5..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/snapshots.toml +++ /dev/null @@ -1,6 +0,0 @@ -[[e2e]] -name = "parallel_flag_runs_dependent_tasks_concurrently" -comment = """ -Package b depends on a, so without --parallel they run sequentially. Both use a barrier requiring 2 participants — if run sequentially the first would wait forever and the test would timeout. --parallel discards dependency edges, allowing both to run at once. -""" -steps = [["vt", "run", "-r", "--parallel", "build"]] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/snapshots/parallel_flag_runs_dependent_tasks_concurrently.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/snapshots/parallel_flag_runs_dependent_tasks_concurrently.md deleted file mode 100644 index 119a64ade..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/snapshots/parallel_flag_runs_dependent_tasks_concurrently.md +++ /dev/null @@ -1,14 +0,0 @@ -# parallel_flag_runs_dependent_tasks_concurrently - -Package b depends on a, so without --parallel they run sequentially. Both use a barrier requiring 2 participants — if run sequentially the first would wait forever and the test would timeout. --parallel discards dependency edges, allowing both to run at once. - -## `vt run -r --parallel build` - -``` -~/packages/a$ vtt barrier ../../.barrier sync 2 ⊘ cache disabled -~/packages/b$ vtt barrier ../../.barrier sync 2 ⊘ cache disabled - - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/vite-task.json deleted file mode 100644 index b39113d05..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/parallel_execution/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": false -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/package.json deleted file mode 100644 index 0e0f53354..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "echo": "echo" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/snapshots.toml deleted file mode 100644 index 21d4f6fc6..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/snapshots.toml +++ /dev/null @@ -1,11 +0,0 @@ -[[e2e]] -name = "pass_args_to_task" -comment = """ -Tests that arguments after task name should be passed to the task https://github.com/voidzero-dev/vite-task/issues/285 -""" -steps = [ - ["vt", "run", "echo", "--help"], # Should just print `help` instead of Vite task's help - ["vt", "run", "echo", "--version"], # Should just print `version` instead of Vite task's version - ["vt", "run", "echo", "-v"], # Should just print `-v` - ["vt", "run", "echo", "-a"], # Should just print `-a` -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/snapshots/pass_args_to_task.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/snapshots/pass_args_to_task.md deleted file mode 100644 index 94e5adbbc..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/snapshots/pass_args_to_task.md +++ /dev/null @@ -1,31 +0,0 @@ -# pass_args_to_task - -Tests that arguments after task name should be passed to the task https://github.com/voidzero-dev/vite-task/issues/285 - -## `vt run echo --help` - -``` -$ echo --help ⊘ cache disabled ---help -``` - -## `vt run echo --version` - -``` -$ echo --version ⊘ cache disabled ---version -``` - -## `vt run echo -v` - -``` -$ echo -v ⊘ cache disabled --v -``` - -## `vt run echo -a` - -``` -$ echo -a ⊘ cache disabled --a -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/vite-task.json deleted file mode 100644 index b39113d05..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/pass_args_to_task/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": false -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/package.json deleted file mode 100644 index e30e61190..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "preexisting-ld-preload", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/preload_test_short_circuit.txt b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/preload_test_short_circuit.txt deleted file mode 100644 index c107b9f52..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/preload_test_short_circuit.txt +++ /dev/null @@ -1 +0,0 @@ -short-circuited content diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/real.txt b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/real.txt deleted file mode 100644 index 10622902e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/real.txt +++ /dev/null @@ -1 +0,0 @@ -real content diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/snapshots.toml deleted file mode 100644 index da1855fe7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/snapshots.toml +++ /dev/null @@ -1,68 +0,0 @@ -[[e2e]] -name = "preexisting_ld_preload" -# Requires fspy's LD_PRELOAD injection path, which is only active on -# glibc-Linux. On musl fspy uses seccomp-unotify instead and strips -# LD_PRELOAD from spawned children, so the fixture's interposer-chain -# assumptions don't hold. -platform = "linux-gnu" -comment = """ -Reproduces #340 and verifies that fspy tolerates a user-supplied `LD_PRELOAD` by appending its shim instead of rejecting the spawn. Appending (not prepending) also preserves symbol-interposition order: the user's preload runs first, so short-circuited calls remain invisible to fspy — what the OS actually executed is what fspy records. - -`preload_test_lib` (built as a cdylib artifact dep) intercepts `open`/`openat` and short-circuits any path containing the marker `preload_test_short_circuit`, returning `ENOENT` without forwarding. Every other path is forwarded via `RTLD_NEXT` so fspy still observes the real syscall. - -The `read` task prints two files. `real.txt` goes through the full interposer chain and is tracked as an input. `preload_test_short_circuit.txt` is short-circuited by the user preload; fspy never sees it and does not track it. Modifying the short-circuited file must therefore be a cache hit; modifying the real file must be a miss. -""" -steps = [ - { argv = [ - "vt", - "run", - "read", - ], envs = [ - [ - "LD_PRELOAD", - "", - ], - ], comment = "cache miss: real.txt tracked; short-circuited file reported not found" }, - { argv = [ - "vt", - "run", - "read", - ], envs = [ - [ - "LD_PRELOAD", - "", - ], - ], comment = "cache hit" }, - { argv = [ - "vtt", - "write-file", - "preload_test_short_circuit.txt", - "modified short-circuited content", - ], comment = "modify the untracked (short-circuited) file" }, - { argv = [ - "vt", - "run", - "read", - ], envs = [ - [ - "LD_PRELOAD", - "", - ], - ], comment = "still cache hit: short-circuited access was never tracked" }, - { argv = [ - "vtt", - "write-file", - "real.txt", - "modified real content", - ], comment = "modify the tracked file" }, - { argv = [ - "vt", - "run", - "read", - ], envs = [ - [ - "LD_PRELOAD", - "", - ], - ], comment = "cache miss: tracked input changed" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/snapshots/preexisting_ld_preload.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/snapshots/preexisting_ld_preload.md deleted file mode 100644 index a09842e71..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/snapshots/preexisting_ld_preload.md +++ /dev/null @@ -1,80 +0,0 @@ -# preexisting_ld_preload - -Reproduces #340 and verifies that fspy tolerates a user-supplied `LD_PRELOAD` by appending its shim instead of rejecting the spawn. Appending (not prepending) also preserves symbol-interposition order: the user's preload runs first, so short-circuited calls remain invisible to fspy — what the OS actually executed is what fspy records. - -`preload_test_lib` (built as a cdylib artifact dep) intercepts `open`/`openat` and short-circuits any path containing the marker `preload_test_short_circuit`, returning `ENOENT` without forwarding. Every other path is forwarded via `RTLD_NEXT` so fspy still observes the real syscall. - -The `read` task prints two files. `real.txt` goes through the full interposer chain and is tracked as an input. `preload_test_short_circuit.txt` is short-circuited by the user preload; fspy never sees it and does not track it. Modifying the short-circuited file must therefore be a cache hit; modifying the real file must be a miss. - -## `LD_PRELOAD= vt run read` - -cache miss: real.txt tracked; short-circuited file reported not found - -``` -$ vtt print-file real.txt -real content - -$ vtt print-file preload_test_short_circuit.txt -preload_test_short_circuit.txt: not found - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` - -## `LD_PRELOAD= vt run read` - -cache hit - -``` -$ vtt print-file real.txt ◉ cache hit, replaying -real content - -$ vtt print-file preload_test_short_circuit.txt ◉ cache hit, replaying -preload_test_short_circuit.txt: not found - ---- -vt run: 2/2 cache hit (100%). (Run `vt run --last-details` for full details) -``` - -## `vtt write-file preload_test_short_circuit.txt 'modified short-circuited content'` - -modify the untracked (short-circuited) file - -``` -``` - -## `LD_PRELOAD= vt run read` - -still cache hit: short-circuited access was never tracked - -``` -$ vtt print-file real.txt ◉ cache hit, replaying -real content - -$ vtt print-file preload_test_short_circuit.txt ◉ cache hit, replaying -preload_test_short_circuit.txt: not found - ---- -vt run: 2/2 cache hit (100%). (Run `vt run --last-details` for full details) -``` - -## `vtt write-file real.txt 'modified real content'` - -modify the tracked file - -``` -``` - -## `LD_PRELOAD= vt run read` - -cache miss: tracked input changed - -``` -$ vtt print-file real.txt ○ cache miss: 'real.txt' modified, executing -modified real content -$ vtt print-file preload_test_short_circuit.txt ◉ cache hit, replaying -preload_test_short_circuit.txt: not found - ---- -vt run: 1/2 cache hit (50%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/vite-task.json deleted file mode 100644 index 1ba079d33..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/vite-task.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tasks": { - "read": { - "command": "vtt print-file real.txt && vtt print-file preload_test_short_circuit.txt", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/foo.txt b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/foo.txt deleted file mode 100644 index f2376e2ba..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/foo.txt +++ /dev/null @@ -1 +0,0 @@ -initial content diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/package.json deleted file mode 100644 index 184880a87..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "scripts": { - "script1": "vtt print-file foo.txt", - "script2": "vtt print-file foo.txt" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/snapshots.toml deleted file mode 100644 index 3cdf829b2..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/snapshots.toml +++ /dev/null @@ -1,34 +0,0 @@ -[[e2e]] -name = "shared_caching_input" -comment = """ -Tests that tasks with identical commands share cache entries -""" -steps = [ - { argv = [ - "vt", - "run", - "script1", - ], comment = "cache miss" }, - { argv = [ - "vt", - "run", - "script2", - ], comment = "cache hit, same command as script1" }, - { argv = [ - "vtt", - "replace-file-content", - "foo.txt", - "initial", - "modified", - ], comment = "modify shared input" }, - { argv = [ - "vt", - "run", - "script2", - ], comment = "cache miss, input changed" }, - { argv = [ - "vt", - "run", - "script1", - ], comment = "cache hit, script2 already warmed cache" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/snapshots/shared_caching_input.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/snapshots/shared_caching_input.md deleted file mode 100644 index 3fb2a2258..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/snapshots/shared_caching_input.md +++ /dev/null @@ -1,52 +0,0 @@ -# shared_caching_input - -Tests that tasks with identical commands share cache entries - -## `vt run script1` - -cache miss - -``` -$ vtt print-file foo.txt -initial content -``` - -## `vt run script2` - -cache hit, same command as script1 - -``` -$ vtt print-file foo.txt ◉ cache hit, replaying -initial content - ---- -vt run: cache hit. -``` - -## `vtt replace-file-content foo.txt initial modified` - -modify shared input - -``` -``` - -## `vt run script2` - -cache miss, input changed - -``` -$ vtt print-file foo.txt ○ cache miss: 'foo.txt' modified, executing -modified content -``` - -## `vt run script1` - -cache hit, script2 already warmed cache - -``` -$ vtt print-file foo.txt ◉ cache hit, replaying -modified content - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/shared_caching_input/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/package.json deleted file mode 100644 index 577830224..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "signal-exit-test" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/snapshots.toml deleted file mode 100644 index 783523aab..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/snapshots.toml +++ /dev/null @@ -1,8 +0,0 @@ -[[e2e]] -name = "signal_terminated_task_returns_non_zero_exit_code" -comment = """ -Tests exit code behavior for signal-terminated processes Unix-only: Windows doesn't have Unix signals, so exit codes differ -""" -platform = "unix" -ignore = true -steps = [{ argv = ["vt", "run", "abort"], comment = "SIGABRT -> exit code 134" }] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/snapshots/signal_terminated_task_returns_non_zero_exit_code.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/snapshots/signal_terminated_task_returns_non_zero_exit_code.md deleted file mode 100644 index 291479a8e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/snapshots/signal_terminated_task_returns_non_zero_exit_code.md +++ /dev/null @@ -1,13 +0,0 @@ -# signal_terminated_task_returns_non_zero_exit_code - -Tests exit code behavior for signal-terminated processes Unix-only: Windows doesn't have Unix signals, so exit codes differ - -## `vt run abort` - -SIGABRT -> exit code 134 - -**Exit code:** 134 - -``` -$ node -e "process.kill(process.pid, 6)" -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/vite-task.json deleted file mode 100644 index 538ef2dd0..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/signal_exit/vite-task.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "cache": true, - "tasks": { - "abort": { - "command": "node -e \"process.kill(process.pid, 6)\"" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/package.json deleted file mode 100644 index 99f164cec..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "summary-output-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/packages/a/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/packages/a/package.json deleted file mode 100644 index a724f1d89..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/packages/a/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@summary/a", - "scripts": { - "build": "vtt print built-a" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/packages/b/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/packages/b/package.json deleted file mode 100644 index aa4c23fae..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/packages/b/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@summary/b", - "scripts": { - "build": "vtt print built-b" - }, - "dependencies": { - "@summary/a": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots.toml deleted file mode 100644 index 3df60595f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots.toml +++ /dev/null @@ -1,134 +0,0 @@ -[[e2e]] -name = "single_task_cache_miss_shows_no_summary" -comment = """ -A single task on its first run (cache miss) should produce no summary line at all — only the task's own output. -""" -cwd = "packages/a" -steps = [["vt", "run", "build"]] - -[[e2e]] -name = "single_task_cache_hit_shows_compact_summary" -comment = """ -When a single task hits the cache on a re-run, the compact one-line summary should be emitted. -""" -cwd = "packages/a" -steps = [ - { argv = [ - "vt", - "run", - "build", - ], comment = "first run, cache miss" }, - { argv = [ - "vt", - "run", - "build", - ], comment = "second run, cache hit → compact summary" }, -] - -[[e2e]] -name = "multi_task_all_cache_miss_shows_compact_summary" -comment = """ -A multi-task (`-r`) run where every task misses should still print a compact summary with 0 hits. -""" -steps = [["vt", "run", "-r", "build"]] - -[[e2e]] -name = "multi_task_with_cache_hits_shows_compact_summary" -comment = """ -On the second multi-task (`-r`) run, the compact summary should report the correct hit count. -""" -steps = [ - { argv = [ - "vt", - "run", - "-r", - "build", - ], comment = "first run, all miss" }, - { argv = [ - "vt", - "run", - "-r", - "build", - ], comment = "second run, all hit" }, -] - -[[e2e]] -name = "single_task_verbose_shows_full_summary" -comment = """ -`vt run -v` on a single task should emit the full detailed summary, even on a first-run cache miss. -""" -cwd = "packages/a" -steps = [["vt", "run", "-v", "build"]] - -[[e2e]] -name = "multi_task_verbose_shows_full_summary" -comment = """ -`vt run -r -v` should emit the full detailed summary across all tasks. -""" -steps = [["vt", "run", "-r", "-v", "build"]] - -[[e2e]] -name = "multi_task_verbose_with_cache_hits_shows_full_summary" -comment = """ -With `-v` on a multi-task re-run, the full summary should report per-task cache hits (not just overall stats). -""" -steps = [ - { argv = [ - "vt", - "run", - "-r", - "build", - ], comment = "first run, populate cache" }, - { argv = [ - "vt", - "run", - "-r", - "-v", - "build", - ], comment = "second run, verbose with cache hits" }, -] - -[[e2e]] -name = "last_details_with_no_previous_run_shows_error" -comment = """ -`vt run --last-details` with no prior run in the workspace should produce a clear error rather than empty output. -""" -steps = [["vt", "run", "--last-details"]] - -[[e2e]] -name = "last_details_after_run_shows_saved_summary" -comment = """ -After a single-task run, `vt run --last-details` should display the saved summary from disk. -""" -cwd = "packages/a" -steps = [ - { argv = [ - "vt", - "run", - "build", - ], comment = "populate summary file" }, - { argv = [ - "vt", - "run", - "--last-details", - ], comment = "display saved summary" }, -] - -[[e2e]] -name = "last_details_after_multi_task_run_shows_saved_summary" -comment = """ -After a multi-task (`-r`) run, `vt run --last-details` should display the saved summary covering every task. -""" -steps = [ - { argv = [ - "vt", - "run", - "-r", - "build", - ], comment = "populate summary file" }, - { argv = [ - "vt", - "run", - "--last-details", - ], comment = "display saved summary" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/last_details_after_multi_task_run_shows_saved_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/last_details_after_multi_task_run_shows_saved_summary.md deleted file mode 100644 index fced8b735..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/last_details_after_multi_task_run_shows_saved_summary.md +++ /dev/null @@ -1,41 +0,0 @@ -# last_details_after_multi_task_run_shows_saved_summary - -After a multi-task (`-r`) run, `vt run --last-details` should display the saved summary covering every task. - -## `vt run -r build` - -populate summary file - -``` -~/packages/a$ vtt print built-a -built-a - -~/packages/b$ vtt print built-b -built-b - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` - -## `vt run --last-details` - -display saved summary - -``` - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Vite+ Task Runner • Execution Summary -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Statistics: 2 tasks • 0 cache hits • 2 cache misses -Performance: 0% cache hit rate - -Task Details: -──────────────────────────────────────────────── - [1] @summary/a#build: ~/packages/a$ vtt print built-a ✓ - → Cache miss: no previous cache entry found - ······················································· - [2] @summary/b#build: ~/packages/b$ vtt print built-b ✓ - → Cache miss: no previous cache entry found -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/last_details_after_run_shows_saved_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/last_details_after_run_shows_saved_summary.md deleted file mode 100644 index 035a754bc..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/last_details_after_run_shows_saved_summary.md +++ /dev/null @@ -1,32 +0,0 @@ -# last_details_after_run_shows_saved_summary - -After a single-task run, `vt run --last-details` should display the saved summary from disk. - -## `vt run build` - -populate summary file - -``` -~/packages/a$ vtt print built-a -built-a -``` - -## `vt run --last-details` - -display saved summary - -``` - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Vite+ Task Runner • Execution Summary -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Statistics: 1 tasks • 0 cache hits • 1 cache misses -Performance: 0% cache hit rate - -Task Details: -──────────────────────────────────────────────── - [1] @summary/a#build: ~/packages/a$ vtt print built-a ✓ - → Cache miss: no previous cache entry found -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/last_details_with_no_previous_run_shows_error.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/last_details_with_no_previous_run_shows_error.md deleted file mode 100644 index 990c2aae2..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/last_details_with_no_previous_run_shows_error.md +++ /dev/null @@ -1,11 +0,0 @@ -# last_details_with_no_previous_run_shows_error - -`vt run --last-details` with no prior run in the workspace should produce a clear error rather than empty output. - -## `vt run --last-details` - -**Exit code:** 1 - -``` -No previous run summary found. Run a task first to generate a summary. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_all_cache_miss_shows_compact_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_all_cache_miss_shows_compact_summary.md deleted file mode 100644 index bf41ff08f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_all_cache_miss_shows_compact_summary.md +++ /dev/null @@ -1,16 +0,0 @@ -# multi_task_all_cache_miss_shows_compact_summary - -A multi-task (`-r`) run where every task misses should still print a compact summary with 0 hits. - -## `vt run -r build` - -``` -~/packages/a$ vtt print built-a -built-a - -~/packages/b$ vtt print built-b -built-b - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_verbose_shows_full_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_verbose_shows_full_summary.md deleted file mode 100644 index 5d4ae1419..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_verbose_shows_full_summary.md +++ /dev/null @@ -1,30 +0,0 @@ -# multi_task_verbose_shows_full_summary - -`vt run -r -v` should emit the full detailed summary across all tasks. - -## `vt run -r -v build` - -``` -~/packages/a$ vtt print built-a -built-a - -~/packages/b$ vtt print built-b -built-b - - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Vite+ Task Runner • Execution Summary -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Statistics: 2 tasks • 0 cache hits • 2 cache misses -Performance: 0% cache hit rate - -Task Details: -──────────────────────────────────────────────── - [1] @summary/a#build: ~/packages/a$ vtt print built-a ✓ - → Cache miss: no previous cache entry found - ······················································· - [2] @summary/b#build: ~/packages/b$ vtt print built-b ✓ - → Cache miss: no previous cache entry found -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_verbose_with_cache_hits_shows_full_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_verbose_with_cache_hits_shows_full_summary.md deleted file mode 100644 index b03edebe3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_verbose_with_cache_hits_shows_full_summary.md +++ /dev/null @@ -1,47 +0,0 @@ -# multi_task_verbose_with_cache_hits_shows_full_summary - -With `-v` on a multi-task re-run, the full summary should report per-task cache hits (not just overall stats). - -## `vt run -r build` - -first run, populate cache - -``` -~/packages/a$ vtt print built-a -built-a - -~/packages/b$ vtt print built-b -built-b - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` - -## `vt run -r -v build` - -second run, verbose with cache hits - -``` -~/packages/a$ vtt print built-a ◉ cache hit, replaying -built-a - -~/packages/b$ vtt print built-b ◉ cache hit, replaying -built-b - - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Vite+ Task Runner • Execution Summary -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Statistics: 2 tasks • 2 cache hits • 0 cache misses -Performance: 100% cache hit rate - -Task Details: -──────────────────────────────────────────────── - [1] @summary/a#build: ~/packages/a$ vtt print built-a ✓ - → Cache hit - output replayed - - ······················································· - [2] @summary/b#build: ~/packages/b$ vtt print built-b ✓ - → Cache hit - output replayed - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_with_cache_hits_shows_compact_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_with_cache_hits_shows_compact_summary.md deleted file mode 100644 index 60ad19654..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_with_cache_hits_shows_compact_summary.md +++ /dev/null @@ -1,33 +0,0 @@ -# multi_task_with_cache_hits_shows_compact_summary - -On the second multi-task (`-r`) run, the compact summary should report the correct hit count. - -## `vt run -r build` - -first run, all miss - -``` -~/packages/a$ vtt print built-a -built-a - -~/packages/b$ vtt print built-b -built-b - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` - -## `vt run -r build` - -second run, all hit - -``` -~/packages/a$ vtt print built-a ◉ cache hit, replaying -built-a - -~/packages/b$ vtt print built-b ◉ cache hit, replaying -built-b - ---- -vt run: 2/2 cache hit (100%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_cache_hit_shows_compact_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_cache_hit_shows_compact_summary.md deleted file mode 100644 index 7ea87d5c1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_cache_hit_shows_compact_summary.md +++ /dev/null @@ -1,24 +0,0 @@ -# single_task_cache_hit_shows_compact_summary - -When a single task hits the cache on a re-run, the compact one-line summary should be emitted. - -## `vt run build` - -first run, cache miss - -``` -~/packages/a$ vtt print built-a -built-a -``` - -## `vt run build` - -second run, cache hit → compact summary - -``` -~/packages/a$ vtt print built-a ◉ cache hit, replaying -built-a - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_cache_miss_shows_no_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_cache_miss_shows_no_summary.md deleted file mode 100644 index 4d7cd06e4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_cache_miss_shows_no_summary.md +++ /dev/null @@ -1,10 +0,0 @@ -# single_task_cache_miss_shows_no_summary - -A single task on its first run (cache miss) should produce no summary line at all — only the task's own output. - -## `vt run build` - -``` -~/packages/a$ vtt print built-a -built-a -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_verbose_shows_full_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_verbose_shows_full_summary.md deleted file mode 100644 index 0954b6899..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_verbose_shows_full_summary.md +++ /dev/null @@ -1,24 +0,0 @@ -# single_task_verbose_shows_full_summary - -`vt run -v` on a single task should emit the full detailed summary, even on a first-run cache miss. - -## `vt run -v build` - -``` -~/packages/a$ vtt print built-a -built-a - - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Vite+ Task Runner • Execution Summary -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Statistics: 1 tasks • 0 cache hits • 1 cache misses -Performance: 0% cache hit rate - -Task Details: -──────────────────────────────────────────────── - [1] @summary/a#build: ~/packages/a$ vtt print built-a ✓ - → Cache miss: no previous cache entry found -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/package.json deleted file mode 100644 index 8858e5970..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "task-list-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/app/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/app/package.json deleted file mode 100644 index c8582bb8f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/app/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "app", - "dependencies": { - "lib": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/app/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/app/vite-task.json deleted file mode 100644 index 0ca9a1ed4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/app/vite-task.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo build app" - }, - "test": { - "command": "echo test app" - }, - "lint": { - "command": "echo lint app" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/lib/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/lib/package.json deleted file mode 100644 index f5ff35859..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/lib/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "lib" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/lib/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/lib/vite-task.json deleted file mode 100644 index bef5809cd..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/packages/lib/vite-task.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo build lib" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots.toml deleted file mode 100644 index 7b0e70397..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots.toml +++ /dev/null @@ -1,21 +0,0 @@ -[[e2e]] -name = "list_tasks_from_package_dir" -cwd = "packages/app" -steps = [["vtt", "pipe-stdin", "--", "vt", "run"]] - -[[e2e]] -name = "list_tasks_from_workspace_root" -steps = [["vtt", "pipe-stdin", "--", "vt", "run"]] - -[[e2e]] -name = "vt_run_in_script" -steps = [ - { argv = [ - "vt", - "run", - "list-tasks", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write-key" = "enter" }, - ] }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots/list_tasks_from_package_dir.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots/list_tasks_from_package_dir.md deleted file mode 100644 index 7b12d8e3a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots/list_tasks_from_package_dir.md +++ /dev/null @@ -1,12 +0,0 @@ -# list_tasks_from_package_dir - -## `vtt pipe-stdin -- vt run` - -``` - build: echo build app - lint: echo lint app - test: echo test app - lib#build: echo build lib - task-list-test#hello: echo hello from root - task-list-test#list-tasks: vt run -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots/list_tasks_from_workspace_root.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots/list_tasks_from_workspace_root.md deleted file mode 100644 index 80212c903..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots/list_tasks_from_workspace_root.md +++ /dev/null @@ -1,12 +0,0 @@ -# list_tasks_from_workspace_root - -## `vtt pipe-stdin -- vt run` - -``` - hello: echo hello from root - list-tasks: vt run - app#build: echo build app - app#lint: echo lint app - app#test: echo test app - lib#build: echo build lib -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots/vt_run_in_script.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots/vt_run_in_script.md deleted file mode 100644 index b5b15c1ee..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/snapshots/vt_run_in_script.md +++ /dev/null @@ -1,28 +0,0 @@ -# vt_run_in_script - -## `vt run list-tasks` - -**→ expect-milestone:** `task-select::0` - -``` -$ vt run ⊘ cache disabled -Select a task (↑/↓, Enter to run, type to search): - - › hello echo hello from root - list-tasks vt run - app (packages/app) - build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib -``` - -**← write-key:** `enter` - -``` -$ vt run ⊘ cache disabled -Selected task: hello -$ echo hello from root ⊘ cache disabled -hello from root -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/vite-task.json deleted file mode 100644 index 5bbc95c4e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_list/vite-task.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "tasks": { - "hello": { - "command": "echo hello from root" - }, - "list-tasks": { - "command": "vt run" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/package.json deleted file mode 100644 index 3807c88c5..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "hello": "echo -n foo && echo bar" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/snapshots.toml deleted file mode 100644 index c7f846da1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/snapshots.toml +++ /dev/null @@ -1,6 +0,0 @@ -[[e2e]] -name = "no_trailing_newline" -comment = """ -Tests output handling when task has no trailing newline -""" -steps = [{ argv = ["vt", "run", "hello"], comment = "runs echo -n hello" }] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/snapshots/no_trailing_newline.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/snapshots/no_trailing_newline.md deleted file mode 100644 index d7dc75d04..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/snapshots/no_trailing_newline.md +++ /dev/null @@ -1,17 +0,0 @@ -# no_trailing_newline - -Tests output handling when task has no trailing newline - -## `vt run hello` - -runs echo -n hello - -``` -$ echo -n foo ⊘ cache disabled -foo -$ echo bar ⊘ cache disabled -bar - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_no_trailing_newline/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/package.json deleted file mode 100644 index b3ea63889..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "task-select-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/app/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/app/package.json deleted file mode 100644 index 7b19151f5..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/app/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "app", - "private": true, - "dependencies": { - "lib": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/app/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/app/vite-task.json deleted file mode 100644 index 9f842f77d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/app/vite-task.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo build app" - }, - "lint": { - "command": "echo lint app" - }, - "test": { - "command": "echo test app" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/lib/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/lib/package.json deleted file mode 100644 index 42510612f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/lib/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "lib", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/lib/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/lib/vite-task.json deleted file mode 100644 index 93d1597c3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/packages/lib/vite-task.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo build lib" - }, - "lint": { - "command": "echo lint lib" - }, - "test": { - "command": "echo test lib" - }, - "typecheck": { - "command": "echo typecheck lib" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots.toml deleted file mode 100644 index eb046e7d8..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots.toml +++ /dev/null @@ -1,318 +0,0 @@ -[[e2e]] -name = "non_interactive_list_tasks" -comment = """ -With piped stdin (non-interactive), `vt run` with no task argument should list all available tasks instead of launching the picker. -""" -steps = [["vtt", "pipe-stdin", "--", "vt", "run"]] - -[[e2e]] -name = "non_interactive_did_you_mean" -comment = """ -In non-interactive mode, a typo that has close fuzzy matches should produce "did you mean" suggestions. -""" -steps = [["vtt", "pipe-stdin", "--", "vt", "run", "buid"]] - -[[e2e]] -name = "non_interactive_no_suggestions" -comment = """ -In non-interactive mode, a typo with no close fuzzy matches should omit the "did you mean" block entirely. -""" -steps = [["vtt", "pipe-stdin", "--", "vt", "run", "zzzzz"]] - -[[e2e]] -name = "non_interactive_recursive_typo_errors" -comment = """ -A typoed task name combined with `-r` (not cwd-only) should error without listing tasks — the `-r` signal rules out the interactive selector fallback. -""" -steps = [["vtt", "pipe-stdin", "--", "vt", "run", "-r", "buid"]] - -[[e2e]] -name = "interactive_select_task" -comment = """ -In interactive mode, navigating down one entry and pressing Enter should select the second task. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write-key" = "down" }, - { "expect-milestone" = "task-select::1" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "interactive_select_with_typo" -comment = """ -When a typoed task name launches the selector, the typo should be pre-filled as the initial search query. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - "buid", - ], interactions = [ - { "expect-milestone" = "task-select:buid:0" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "interactive_search_then_select" -comment = """ -Typing characters in the selector should filter the list in real time, then Enter should pick the highlighted result. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write" = "lin" }, - { "expect-milestone" = "task-select:lin:0" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "interactive_escape_clears_query" -comment = """ -Escape in the selector should clear the current query and restore the unfiltered list at cursor position 0. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write" = "lin" }, - { "expect-milestone" = "task-select:lin:0" }, - { "write-key" = "escape" }, - { "expect-milestone" = "task-select::0" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "recursive_without_task_errors" -comment = """ -`vt run -r` with no task argument is not treated as a bare run — it should error instead of opening the selector. -""" -cwd = "packages/app" -steps = [["vt", "run", "-r"]] - -[[e2e]] -name = "transitive_typo_errors" -comment = """ -`vt run -t ` is not cwd-only, so a typo should error rather than fall back to the interactive selector. -""" -cwd = "packages/app" -steps = [["vt", "run", "-t", "buid"]] - -[[e2e]] -name = "interactive_scroll_long_list" -comment = """ -With a list taller than the 8-row page, navigating past the viewport should scroll down, and navigating back should scroll the view up to index 0. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, # Navigate down to index 8 (past page_size=8, triggering scroll) - { "write-key" = "down" }, - { "write-key" = "down" }, - { "write-key" = "down" }, - { "write-key" = "down" }, - { "write-key" = "down" }, - { "write-key" = "down" }, - { "write-key" = "down" }, - { "write-key" = "down" }, - { "expect-milestone" = "task-select::8" }, # Scroll back up to the top - { "write-key" = "up" }, - { "write-key" = "up" }, - { "write-key" = "up" }, - { "write-key" = "up" }, - { "write-key" = "up" }, - { "write-key" = "up" }, - { "write-key" = "up" }, - { "write-key" = "up" }, - { "expect-milestone" = "task-select::0" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "non_interactive_list_tasks_from_lib" -comment = """ -When listing from a subpackage's cwd, that package's tasks should appear first and be listed unqualified. -""" -cwd = "packages/lib" -steps = [["vtt", "pipe-stdin", "--", "vt", "run"]] - -[[e2e]] -name = "interactive_select_task_from_lib" -comment = """ -In the interactive selector launched from `packages/lib`, the first entry should be a lib-owned task. -""" -cwd = "packages/lib" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "interactive_search_other_package_task" -comment = """ -A search query can match a task that lives only in another package; that task should become selectable. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write" = "typec" }, - { "expect-milestone" = "task-select:typec:0" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "interactive_search_with_hash_skips_reorder" -comment = """ -Including `#` in the query means "target a specific package#task" and should disable the current-package reordering in the filtered list. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write" = "lib#" }, - { "expect-milestone" = "task-select:lib#:0" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "interactive_search_preserves_rating_within_package" -comment = """ -When multiple current-package tasks match, the fuzzy-rating order between them should be preserved (not broken by current-package grouping). -""" -cwd = "packages/lib" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write" = "t" }, - { "expect-milestone" = "task-select:t:0" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "interactive_enter_with_no_results_does_nothing" -comment = """ -Pressing Enter while the filtered list is empty should be a no-op — the selector should remain open and accept new input. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write" = "zzzzz" }, - { "expect-milestone" = "task-select:zzzzz:0" }, - { "write-key" = "enter" }, - { "write-key" = "escape" }, - { "expect-milestone" = "task-select::0" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "interactive_select_from_other_package" -comment = """ -Navigating past the current-package group should expose tasks in other packages, which must also be selectable. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write-key" = "down" }, - { "write-key" = "down" }, - { "write-key" = "down" }, - { "expect-milestone" = "task-select::3" }, - { "write-key" = "enter" }, - ] }, -] - -[[e2e]] -name = "typo_in_task_script_fails_without_list" -comment = """ -A typo inside a task's own script (i.e. a nested `vp run` command) should surface the real failure, not launch the task picker. -""" -steps = [["vt", "run", "run-typo-task"]] - -[[e2e]] -name = "interactive_ctrl_c_cancels" -comment = """ -Ctrl+C inside the interactive selector should cancel and exit 130 without running any task. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write-key" = "ctrl-c" }, - ] }, -] - -[[e2e]] -name = "verbose_without_task_errors" -comment = """ -`vt run --verbose` with no task argument is not bare, so it should error with "no task specifier provided" rather than opening the selector. -""" -cwd = "packages/app" -steps = [["vt", "run", "--verbose"]] - -[[e2e]] -name = "verbose_with_typo_enters_selector" -comment = """ -`vt run --verbose ` still qualifies as cwd-only, so it should fall back to the interactive selector pre-filled with the typo. -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - "--verbose", - "buid", - ], interactions = [ - { "expect-milestone" = "task-select:buid:0" }, - { "write-key" = "enter" }, - ] }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_ctrl_c_cancels.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_ctrl_c_cancels.md deleted file mode 100644 index be2d1944f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_ctrl_c_cancels.md +++ /dev/null @@ -1,32 +0,0 @@ -# interactive_ctrl_c_cancels - -Ctrl+C inside the interactive selector should cancel and exit 130 without running any task. - -## `vt run` - -**Exit code:** 130 - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `ctrl-c` - -``` -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_enter_with_no_results_does_nothing.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_enter_with_no_results_does_nothing.md deleted file mode 100644 index 0cd6c59aa..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_enter_with_no_results_does_nothing.md +++ /dev/null @@ -1,67 +0,0 @@ -# interactive_enter_with_no_results_does_nothing - -Pressing Enter while the filtered list is empty should be a no-op — the selector should remain open and accept new input. - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write:** `zzzzz` - -**→ expect-milestone:** `task-select:zzzzz:0` - -``` -Select a task (↑/↓, Enter to run, type to search): zzzzz - - No matching tasks. Press Escape to clear search. -``` - -**← write-key:** `enter` - -**← write-key:** `escape` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `enter` - -``` -Selected task: build -~/packages/app$ echo build app ⊘ cache disabled -build app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_escape_clears_query.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_escape_clears_query.md deleted file mode 100644 index c4d70207d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_escape_clears_query.md +++ /dev/null @@ -1,67 +0,0 @@ -# interactive_escape_clears_query - -Escape in the selector should clear the current query and restore the unfiltered list at cursor position 0. - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write:** `lin` - -**→ expect-milestone:** `task-select:lin:0` - -``` -Select a task (↑/↓, Enter to run, type to search): lin - - › lint echo lint app - lib (packages/lib) - lint echo lint lib -``` - -**← write-key:** `escape` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `enter` - -``` -Selected task: build -~/packages/app$ echo build app ⊘ cache disabled -build app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_scroll_long_list.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_scroll_long_list.md deleted file mode 100644 index c62b59ccc..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_scroll_long_list.md +++ /dev/null @@ -1,105 +0,0 @@ -# interactive_scroll_long_list - -With a list taller than the 8-row page, navigating past the viewport should scroll down, and navigating back should scroll the view up to index 0. - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `down` - -**← write-key:** `down` - -**← write-key:** `down` - -**← write-key:** `down` - -**← write-key:** `down` - -**← write-key:** `down` - -**← write-key:** `down` - -**← write-key:** `down` - -**→ expect-milestone:** `task-select::8` - -``` -Select a task (↑/↓, Enter to run, type to search): - - build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - › clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `up` - -**← write-key:** `up` - -**← write-key:** `up` - -**← write-key:** `up` - -**← write-key:** `up` - -**← write-key:** `up` - -**← write-key:** `up` - -**← write-key:** `up` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `enter` - -``` -Selected task: build -~/packages/app$ echo build app ⊘ cache disabled -build app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_other_package_task.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_other_package_task.md deleted file mode 100644 index 1b01f0150..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_other_package_task.md +++ /dev/null @@ -1,44 +0,0 @@ -# interactive_search_other_package_task - -A search query can match a task that lives only in another package; that task should become selectable. - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write:** `typec` - -**→ expect-milestone:** `task-select:typec:0` - -``` -Select a task (↑/↓, Enter to run, type to search): typec - - lib (packages/lib) - › typecheck echo typecheck lib -``` - -**← write-key:** `enter` - -``` -Selected task: lib#typecheck -~/packages/lib$ echo typecheck lib ⊘ cache disabled -typecheck lib -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_preserves_rating_within_package.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_preserves_rating_within_package.md deleted file mode 100644 index 6da6fa006..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_preserves_rating_within_package.md +++ /dev/null @@ -1,55 +0,0 @@ -# interactive_search_preserves_rating_within_package - -When multiple current-package tasks match, the fuzzy-rating order between them should be preserved (not broken by current-package grouping). - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - app (packages/app) - build echo build app - lint echo lint app - test echo test app - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write:** `t` - -**→ expect-milestone:** `task-select:t:0` - -``` -Select a task (↑/↓, Enter to run, type to search): t - - › test echo test lib - typecheck echo typecheck lib - lint echo lint lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - docs echo docs root - format echo format root - hello echo hello from root - run-typo-task vt run nonexistent-xyz - validate echo validate root - (…2 more) -``` - -**← write-key:** `enter` - -``` -Selected task: test -~/packages/lib$ echo test lib ⊘ cache disabled -test lib -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_then_select.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_then_select.md deleted file mode 100644 index 3fcd7b0a1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_then_select.md +++ /dev/null @@ -1,45 +0,0 @@ -# interactive_search_then_select - -Typing characters in the selector should filter the list in real time, then Enter should pick the highlighted result. - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write:** `lin` - -**→ expect-milestone:** `task-select:lin:0` - -``` -Select a task (↑/↓, Enter to run, type to search): lin - - › lint echo lint app - lib (packages/lib) - lint echo lint lib -``` - -**← write-key:** `enter` - -``` -Selected task: lint -~/packages/app$ echo lint app ⊘ cache disabled -lint app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_with_hash_skips_reorder.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_with_hash_skips_reorder.md deleted file mode 100644 index cd5290bca..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_search_with_hash_skips_reorder.md +++ /dev/null @@ -1,47 +0,0 @@ -# interactive_search_with_hash_skips_reorder - -Including `#` in the query means "target a specific package#task" and should disable the current-package reordering in the filtered list. - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write:** `lib#` - -**→ expect-milestone:** `task-select:lib#:0` - -``` -Select a task (↑/↓, Enter to run, type to search): lib# - - lib (packages/lib) - › build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib -``` - -**← write-key:** `enter` - -``` -Selected task: lib#build -~/packages/lib$ echo build lib ⊘ cache disabled -build lib -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_from_other_package.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_from_other_package.md deleted file mode 100644 index 16f0a29b4..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_from_other_package.md +++ /dev/null @@ -1,59 +0,0 @@ -# interactive_select_from_other_package - -Navigating past the current-package group should expose tasks in other packages, which must also be selectable. - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `down` - -**← write-key:** `down` - -**← write-key:** `down` - -**→ expect-milestone:** `task-select::3` - -``` -Select a task (↑/↓, Enter to run, type to search): - - build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - › build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `enter` - -``` -Selected task: lib#build -~/packages/lib$ echo build lib ⊘ cache disabled -build lib -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_task.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_task.md deleted file mode 100644 index 6719ff982..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_task.md +++ /dev/null @@ -1,55 +0,0 @@ -# interactive_select_task - -In interactive mode, navigating down one entry and pressing Enter should select the second task. - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `down` - -**→ expect-milestone:** `task-select::1` - -``` -Select a task (↑/↓, Enter to run, type to search): - - build echo build app - › lint echo lint app - test echo test app - lib (packages/lib) - build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `enter` - -``` -Selected task: lint -~/packages/app$ echo lint app ⊘ cache disabled -lint app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_task_from_lib.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_task_from_lib.md deleted file mode 100644 index 4c230d3b3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_task_from_lib.md +++ /dev/null @@ -1,33 +0,0 @@ -# interactive_select_task_from_lib - -In the interactive selector launched from `packages/lib`, the first entry should be a lib-owned task. - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build lib - lint echo lint lib - test echo test lib - typecheck echo typecheck lib - app (packages/app) - build echo build app - lint echo lint app - test echo test app - task-select-test (workspace root) - check echo check root - clean echo clean root - deploy echo deploy root - (…5 more) -``` - -**← write-key:** `enter` - -``` -Selected task: build -~/packages/lib$ echo build lib ⊘ cache disabled -build lib -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_with_typo.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_with_typo.md deleted file mode 100644 index 20b76b415..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/interactive_select_with_typo.md +++ /dev/null @@ -1,24 +0,0 @@ -# interactive_select_with_typo - -When a typoed task name launches the selector, the typo should be pre-filled as the initial search query. - -## `vt run buid` - -**→ expect-milestone:** `task-select:buid:0` - -``` -Task "buid" not found. -Select a task (↑/↓, Enter to run, type to search): buid - - › build echo build app - lib (packages/lib) - build echo build lib -``` - -**← write-key:** `enter` - -``` -Selected task: build -~/packages/app$ echo build app ⊘ cache disabled -build app -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_did_you_mean.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_did_you_mean.md deleted file mode 100644 index ef60faac3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_did_you_mean.md +++ /dev/null @@ -1,13 +0,0 @@ -# non_interactive_did_you_mean - -In non-interactive mode, a typo that has close fuzzy matches should produce "did you mean" suggestions. - -## `vtt pipe-stdin -- vt run buid` - -**Exit code:** 1 - -``` -Task "buid" not found. Did you mean: - app#build: echo build app - lib#build: echo build lib -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_list_tasks.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_list_tasks.md deleted file mode 100644 index 1297a3ba6..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_list_tasks.md +++ /dev/null @@ -1,23 +0,0 @@ -# non_interactive_list_tasks - -With piped stdin (non-interactive), `vt run` with no task argument should list all available tasks instead of launching the picker. - -## `vtt pipe-stdin -- vt run` - -``` - check: echo check root - clean: echo clean root - deploy: echo deploy root - docs: echo docs root - format: echo format root - hello: echo hello from root - run-typo-task: vt run nonexistent-xyz - validate: echo validate root - app#build: echo build app - app#lint: echo lint app - app#test: echo test app - lib#build: echo build lib - lib#lint: echo lint lib - lib#test: echo test lib - lib#typecheck: echo typecheck lib -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_list_tasks_from_lib.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_list_tasks_from_lib.md deleted file mode 100644 index 0e14f0c3a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_list_tasks_from_lib.md +++ /dev/null @@ -1,23 +0,0 @@ -# non_interactive_list_tasks_from_lib - -When listing from a subpackage's cwd, that package's tasks should appear first and be listed unqualified. - -## `vtt pipe-stdin -- vt run` - -``` - build: echo build lib - lint: echo lint lib - test: echo test lib - typecheck: echo typecheck lib - app#build: echo build app - app#lint: echo lint app - app#test: echo test app - task-select-test#check: echo check root - task-select-test#clean: echo clean root - task-select-test#deploy: echo deploy root - task-select-test#docs: echo docs root - task-select-test#format: echo format root - task-select-test#hello: echo hello from root - task-select-test#run-typo-task: vt run nonexistent-xyz - task-select-test#validate: echo validate root -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_no_suggestions.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_no_suggestions.md deleted file mode 100644 index a33d866ab..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_no_suggestions.md +++ /dev/null @@ -1,11 +0,0 @@ -# non_interactive_no_suggestions - -In non-interactive mode, a typo with no close fuzzy matches should omit the "did you mean" block entirely. - -## `vtt pipe-stdin -- vt run zzzzz` - -**Exit code:** 1 - -``` -Task "zzzzz" not found. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_recursive_typo_errors.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_recursive_typo_errors.md deleted file mode 100644 index 69e2df4de..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/non_interactive_recursive_typo_errors.md +++ /dev/null @@ -1,11 +0,0 @@ -# non_interactive_recursive_typo_errors - -A typoed task name combined with `-r` (not cwd-only) should error without listing tasks — the `-r` signal rules out the interactive selector fallback. - -## `vtt pipe-stdin -- vt run -r buid` - -**Exit code:** 1 - -``` -error: Task "buid" not found -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/recursive_without_task_errors.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/recursive_without_task_errors.md deleted file mode 100644 index 818103276..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/recursive_without_task_errors.md +++ /dev/null @@ -1,11 +0,0 @@ -# recursive_without_task_errors - -`vt run -r` with no task argument is not treated as a bare run — it should error instead of opening the selector. - -## `vt run -r` - -**Exit code:** 1 - -``` -error: No task specifier provided for 'run' command -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/transitive_typo_errors.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/transitive_typo_errors.md deleted file mode 100644 index a79295b1f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/transitive_typo_errors.md +++ /dev/null @@ -1,11 +0,0 @@ -# transitive_typo_errors - -`vt run -t ` is not cwd-only, so a typo should error rather than fall back to the interactive selector. - -## `vt run -t buid` - -**Exit code:** 1 - -``` -error: Task "buid" not found -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/typo_in_task_script_fails_without_list.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/typo_in_task_script_fails_without_list.md deleted file mode 100644 index 386668976..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/typo_in_task_script_fails_without_list.md +++ /dev/null @@ -1,12 +0,0 @@ -# typo_in_task_script_fails_without_list - -A typo inside a task's own script (i.e. a nested `vp run` command) should surface the real failure, not launch the task picker. - -## `vt run run-typo-task` - -**Exit code:** 1 - -``` -error: Failed to plan tasks from `vt run nonexistent-xyz` in task task-select-test#run-typo-task -* Task "nonexistent-xyz" not found -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/verbose_with_typo_enters_selector.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/verbose_with_typo_enters_selector.md deleted file mode 100644 index 495aba425..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/verbose_with_typo_enters_selector.md +++ /dev/null @@ -1,38 +0,0 @@ -# verbose_with_typo_enters_selector - -`vt run --verbose ` still qualifies as cwd-only, so it should fall back to the interactive selector pre-filled with the typo. - -## `vt run --verbose buid` - -**→ expect-milestone:** `task-select:buid:0` - -``` -Task "buid" not found. -Select a task (↑/↓, Enter to run, type to search): buid - - › build echo build app - lib (packages/lib) - build echo build lib -``` - -**← write-key:** `enter` - -``` -Selected task: build -~/packages/app$ echo build app ⊘ cache disabled -build app - - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Vite+ Task Runner • Execution Summary -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Statistics: 1 tasks • 0 cache hits • 0 cache misses • 1 cache disabled -Performance: 0% cache hit rate - -Task Details: -──────────────────────────────────────────────── - [1] app#build: ~/packages/app$ echo build app ✓ - → Cache disabled for built-in command -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/verbose_without_task_errors.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/verbose_without_task_errors.md deleted file mode 100644 index 083134214..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/snapshots/verbose_without_task_errors.md +++ /dev/null @@ -1,11 +0,0 @@ -# verbose_without_task_errors - -`vt run --verbose` with no task argument is not bare, so it should error with "no task specifier provided" rather than opening the selector. - -## `vt run --verbose` - -**Exit code:** 1 - -``` -error: No task specifier provided for 'run' command -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/vite-task.json deleted file mode 100644 index d7ef8b9b6..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select/vite-task.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "tasks": { - "check": { - "command": "echo check root" - }, - "clean": { - "command": "echo clean root" - }, - "deploy": { - "command": "echo deploy root" - }, - "docs": { - "command": "echo docs root" - }, - "format": { - "command": "echo format root" - }, - "hello": { - "command": "echo hello from root" - }, - "run-typo-task": { - "command": "vt run nonexistent-xyz" - }, - "validate": { - "command": "echo validate root" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/package.json deleted file mode 100644 index 33d52e30c..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "task-select-truncate-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/packages/app/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/packages/app/package.json deleted file mode 100644 index 0a2d4152f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/packages/app/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "app", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/packages/app/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/packages/app/vite-task.json deleted file mode 100644 index 4eddc58ca..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/packages/app/vite-task.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo build app" - }, - "lint": { - "command": "echo lint app" - }, - "long-cmd": { - "command": "echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - }, - "test": { - "command": "echo test app" - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/snapshots.toml deleted file mode 100644 index 82a621668..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/snapshots.toml +++ /dev/null @@ -1,23 +0,0 @@ -[[e2e]] -name = "interactive_long_command_truncated" -comment = """ -Interactive: long commands are truncated to terminal width (no line wrapping) -""" -cwd = "packages/app" -steps = [ - { argv = [ - "vt", - "run", - ], interactions = [ - { "expect-milestone" = "task-select::0" }, - { "write-key" = "down" }, - { "expect-milestone" = "task-select::1" }, - { "write-key" = "down" }, - { "expect-milestone" = "task-select::2" }, - { "write-key" = "down" }, - { "expect-milestone" = "task-select::3" }, - { "write-key" = "up" }, - { "expect-milestone" = "task-select::2" }, - { "write-key" = "enter" }, - ] }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/snapshots/interactive_long_command_truncated.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/snapshots/interactive_long_command_truncated.md deleted file mode 100644 index f66aef35b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/snapshots/interactive_long_command_truncated.md +++ /dev/null @@ -1,76 +0,0 @@ -# interactive_long_command_truncated - -Interactive: long commands are truncated to terminal width (no line wrapping) - -## `vt run` - -**→ expect-milestone:** `task-select::0` - -``` -Select a task (↑/↓, Enter to run, type to search): - - › build echo build app - lint echo lint app - long-cmd echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… - test echo test app -``` - -**← write-key:** `down` - -**→ expect-milestone:** `task-select::1` - -``` -Select a task (↑/↓, Enter to run, type to search): - - build echo build app - › lint echo lint app - long-cmd echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… - test echo test app -``` - -**← write-key:** `down` - -**→ expect-milestone:** `task-select::2` - -``` -Select a task (↑/↓, Enter to run, type to search): - - build echo build app - lint echo lint app - › long-cmd echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… - test echo test app -``` - -**← write-key:** `down` - -**→ expect-milestone:** `task-select::3` - -``` -Select a task (↑/↓, Enter to run, type to search): - - build echo build app - lint echo lint app - long-cmd echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… - › test echo test app -``` - -**← write-key:** `up` - -**→ expect-milestone:** `task-select::2` - -``` -Select a task (↑/↓, Enter to run, type to search): - - build echo build app - lint echo lint app - › long-cmd echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… - test echo test app -``` - -**← write-key:** `enter` - -``` -Selected task: long-cmd -~/packages/app$ echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ⊘ cache disabled -aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/vite-task.json deleted file mode 100644 index 90faa728a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select_truncate/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "tasks": {} -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/package.json deleted file mode 100644 index 7a6b40e10..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "topological-execution-order-test", - "private": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/packages/app/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/packages/app/package.json deleted file mode 100644 index 6398dfd8e..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/packages/app/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@topo/app", - "scripts": { - "build": "echo 'Building app'" - }, - "dependencies": { - "@topo/lib": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/packages/core/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/packages/core/package.json deleted file mode 100644 index 1ae14806b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/packages/core/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@topo/core", - "scripts": { - "build": "echo 'Building core'" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/packages/lib/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/packages/lib/package.json deleted file mode 100644 index 5b4e06802..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/packages/lib/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@topo/lib", - "scripts": { - "build": "echo 'Building lib'" - }, - "dependencies": { - "@topo/core": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots.toml deleted file mode 100644 index 8d64cd807..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots.toml +++ /dev/null @@ -1,22 +0,0 @@ -[[e2e]] -name = "recursive_build_runs_dependencies_before_dependents" -comment = """ -`vt run -r build` across the workspace should execute packages in topological order: `@topo/core` before `@topo/lib` before `@topo/app`. -""" -steps = [{ argv = ["vt", "run", "-r", "build"], comment = "core -> lib -> app" }] - -[[e2e]] -name = "transitive_build_from_app_runs_all_dependencies" -comment = """ -`vt run -t build` from the leaf package should include the full transitive closure of its dependencies in topological order. -""" -cwd = "packages/app" -steps = [{ argv = ["vt", "run", "-t", "build"], comment = "core -> lib -> app" }] - -[[e2e]] -name = "transitive_build_from_lib_runs_only_its_dependencies" -comment = """ -`vt run -t build` from an intermediate package should limit the run to that package and its own dependencies — `@topo/app` must not run. -""" -cwd = "packages/lib" -steps = [{ argv = ["vt", "run", "-t", "build"], comment = "core -> lib" }] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots/recursive_build_runs_dependencies_before_dependents.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots/recursive_build_runs_dependencies_before_dependents.md deleted file mode 100644 index 955d3653b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots/recursive_build_runs_dependencies_before_dependents.md +++ /dev/null @@ -1,21 +0,0 @@ -# recursive_build_runs_dependencies_before_dependents - -`vt run -r build` across the workspace should execute packages in topological order: `@topo/core` before `@topo/lib` before `@topo/app`. - -## `vt run -r build` - -core -> lib -> app - -``` -~/packages/core$ echo 'Building core' ⊘ cache disabled -Building core - -~/packages/lib$ echo 'Building lib' ⊘ cache disabled -Building lib - -~/packages/app$ echo 'Building app' ⊘ cache disabled -Building app - ---- -vt run: 0/3 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots/transitive_build_from_app_runs_all_dependencies.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots/transitive_build_from_app_runs_all_dependencies.md deleted file mode 100644 index df364dba0..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots/transitive_build_from_app_runs_all_dependencies.md +++ /dev/null @@ -1,21 +0,0 @@ -# transitive_build_from_app_runs_all_dependencies - -`vt run -t build` from the leaf package should include the full transitive closure of its dependencies in topological order. - -## `vt run -t build` - -core -> lib -> app - -``` -~/packages/core$ echo 'Building core' ⊘ cache disabled -Building core - -~/packages/lib$ echo 'Building lib' ⊘ cache disabled -Building lib - -~/packages/app$ echo 'Building app' ⊘ cache disabled -Building app - ---- -vt run: 0/3 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots/transitive_build_from_lib_runs_only_its_dependencies.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots/transitive_build_from_lib_runs_only_its_dependencies.md deleted file mode 100644 index 3f4a166a6..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/snapshots/transitive_build_from_lib_runs_only_its_dependencies.md +++ /dev/null @@ -1,18 +0,0 @@ -# transitive_build_from_lib_runs_only_its_dependencies - -`vt run -t build` from an intermediate package should limit the run to that package and its own dependencies — `@topo/app` must not run. - -## `vt run -t build` - -core -> lib - -``` -~/packages/core$ echo 'Building core' ⊘ cache disabled -Building core - -~/packages/lib$ echo 'Building lib' ⊘ cache disabled -Building lib - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/topological_execution_order/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/index.html b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/index.html deleted file mode 100644 index 20fc85a43..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - vp-run-vite-cache - - - - - diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/package.json deleted file mode 100644 index e4e3497f2..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "vite-build-cache-fixture", - "private": true, - "type": "module" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/index.html b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/index.html deleted file mode 100644 index 74acba4fa..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/index.html +++ /dev/null @@ -1,2 +0,0 @@ -
- diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/package.json deleted file mode 100644 index 4fac7bfad..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "vite-build-cache-portable-fixture", - "private": true, - "type": "module" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/src/main.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/src/main.js deleted file mode 100644 index 79dcec2cd..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/src/main.js +++ /dev/null @@ -1 +0,0 @@ -console.log('plain vite build'); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/vite-task.json deleted file mode 100644 index f66542655..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/vite-task.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tasks": { - "build": { - "command": "vite build", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/vite.config.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/vite.config.js deleted file mode 100644 index f5e879584..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_clone/vite.config.js +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vite'; - -export default defineConfig({ - logLevel: 'silent', - build: { - rollupOptions: { - output: { - entryFileNames: 'assets/index.js', - chunkFileNames: 'assets/[name].js', - assetFileNames: 'assets/[name][extname]', - }, - }, - }, -}); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/index.html b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/index.html deleted file mode 100644 index 74acba4fa..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/index.html +++ /dev/null @@ -1,2 +0,0 @@ -
- diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/package.json deleted file mode 100644 index 4fac7bfad..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "vite-build-cache-portable-fixture", - "private": true, - "type": "module" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/src/main.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/src/main.js deleted file mode 100644 index 79dcec2cd..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/src/main.js +++ /dev/null @@ -1 +0,0 @@ -console.log('plain vite build'); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/vite-task.json deleted file mode 100644 index f66542655..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/vite-task.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tasks": { - "build": { - "command": "vite build", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/vite.config.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/vite.config.js deleted file mode 100644 index f5e879584..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/portable_origin/vite.config.js +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vite'; - -export default defineConfig({ - logLevel: 'silent', - build: { - rollupOptions: { - output: { - entryFileNames: 'assets/index.js', - chunkFileNames: 'assets/[name].js', - assetFileNames: 'assets/[name][extname]', - }, - }, - }, -}); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots.toml deleted file mode 100644 index 4248aa961..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots.toml +++ /dev/null @@ -1,181 +0,0 @@ -[[e2e]] -name = "vite_build_caches_and_restores_outputs" -comment = """ -`vt run --cache build` must produce a cache hit on the second run without any manual input/output configuration. Vite reports non-semantic reads and writes through `@voidzero-dev/vite-task-client`, so fspy can infer real inputs and outputs while the cache restores `dist/` on hit. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "--cache", - "build", - ], comment = "first run: cache miss, emits dist/" }, - { argv = [ - "vtt", - "stat-file", - "dist/assets/main.js", - ], comment = "existence check — content can drift across Vite versions" }, - { argv = [ - "vtt", - "rm", - "dist/assets/main.js", - ], comment = "remove the artefact so the cache-hit restore is observable" }, - { argv = [ - "vt", - "run", - "--cache", - "build", - ], comment = "cache hit: outputs restored without manual config" }, - { argv = [ - "vtt", - "stat-file", - "dist/assets/main.js", - ], comment = "restored from the cache archive" }, -] - -[[e2e]] -name = "vite_prefix_env_change_invalidates_cache" -comment = """ -`VITE_CACHE_LABEL` is picked up by Vite's patched `loadEnv`, which asks the runner for every `VITE_*` env via `getEnvs(pattern, { tracked: true })`. Flipping its value between runs must invalidate the cache AND change the build output because Vite substitutes `import.meta.env.VITE_CACHE_LABEL` at build time. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "--cache", - "build", - ], envs = [ - [ - "VITE_CACHE_LABEL", - "cache-alpha", - ], - ], comment = "first run: cache-alpha label" }, - { argv = [ - "vtt", - "grep-file", - "dist/assets/main.js", - "cache-alpha", - ], comment = "cache-alpha label is in the bundle" }, - { argv = [ - "vtt", - "grep-file", - "dist/assets/main.js", - "cache-bravo", - ], comment = "cache-bravo label is not in the bundle yet" }, - { argv = [ - "vt", - "run", - "--cache", - "build", - ], envs = [ - [ - "VITE_CACHE_LABEL", - "cache-alpha", - ], - ], comment = "cache hit: VITE_CACHE_LABEL unchanged" }, - { argv = [ - "vt", - "run", - "--cache", - "build", - ], envs = [ - [ - "VITE_CACHE_LABEL", - "cache-bravo", - ], - ], comment = "cache miss: envs changed — VITE_CACHE_LABEL value changed" }, - { argv = [ - "vtt", - "grep-file", - "dist/assets/main.js", - "cache-alpha", - ], comment = "cache-alpha label is gone after the rebuild" }, - { argv = [ - "vtt", - "grep-file", - "dist/assets/main.js", - "cache-bravo", - ], comment = "cache-bravo label is now in the bundle" }, -] - -[[e2e]] -name = "vite_build_cache_is_portable_across_workspace_roots" -comment = """ -Two identical plain Vite workspaces run from different absolute roots. The test copies the origin root's default task cache directory into the clone root to simulate uploading and downloading cache data; the clone should hit that copied cache and restore build outputs. -""" -ignore = true -steps = [ - { cwd = "portable_origin", argv = [ - "vt", - "run", - "--cache", - "build", - ], comment = "origin workspace root: cache miss populates its default cache" }, - { cwd = "portable_origin", argv = [ - "vtt", - "stat-file", - "dist/assets/index.js", - ], comment = "origin emitted the non-hashed build asset" }, - { argv = [ - "vtt", - "cp", - "-r", - "portable_origin/node_modules/.vite/task-cache", - "portable_clone/node_modules/.vite/task-cache", - ], comment = "copy-paste the cache directory to simulate upload/download" }, - { cwd = "portable_clone", argv = [ - "vt", - "run", - "--cache", - "build", - ], comment = "clone workspace root: cache hit from the origin root" }, - { cwd = "portable_clone", argv = [ - "vtt", - "stat-file", - "dist/assets/index.js", - ], comment = "clone restored the non-hashed asset from the portable cache archive" }, -] - -[[e2e]] -name = "vite_node_env_change_invalidates_cache" -comment = """ -`NODE_ENV` enters the build's cache fingerprint via Vite's `getEnv('NODE_ENV')` call in `resolveConfig`. Same value → cache hit; different value → cache miss with `envs changed`. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "--cache", - "build", - ], envs = [ - [ - "NODE_ENV", - "production", - ], - ], comment = "first run: NODE_ENV=production" }, - { argv = [ - "vt", - "run", - "--cache", - "build", - ], envs = [ - [ - "NODE_ENV", - "production", - ], - ], comment = "cache hit: NODE_ENV unchanged" }, - { argv = [ - "vt", - "run", - "--cache", - "build", - ], envs = [ - [ - "NODE_ENV", - "development", - ], - ], comment = "cache miss: envs changed (NODE_ENV changed)" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_cache_is_portable_across_workspace_roots.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_cache_is_portable_across_workspace_roots.md deleted file mode 100644 index d569bafeb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_cache_is_portable_across_workspace_roots.md +++ /dev/null @@ -1,45 +0,0 @@ -# vite_build_cache_is_portable_across_workspace_roots - -Two identical plain Vite workspaces run from different absolute roots. The test copies the origin root's default task cache directory into the clone root to simulate uploading and downloading cache data; the clone should hit that copied cache and restore build outputs. - -## `cd portable_origin && vt run --cache build` - -origin workspace root: cache miss populates its default cache - -``` -$ vite build -``` - -## `cd portable_origin && vtt stat-file dist/assets/index.js` - -origin emitted the non-hashed build asset - -``` -dist/assets/index.js: exists -``` - -## `vtt cp -r portable_origin/node_modules/.vite/task-cache portable_clone/node_modules/.vite/task-cache` - -copy-paste the cache directory to simulate upload/download - -``` -``` - -## `cd portable_clone && vt run --cache build` - -clone workspace root: cache hit from the origin root - -``` -$ vite build ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `cd portable_clone && vtt stat-file dist/assets/index.js` - -clone restored the non-hashed asset from the portable cache archive - -``` -dist/assets/index.js: exists -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_caches_and_restores_outputs.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_caches_and_restores_outputs.md deleted file mode 100644 index 996d8f1f2..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_build_caches_and_restores_outputs.md +++ /dev/null @@ -1,45 +0,0 @@ -# vite_build_caches_and_restores_outputs - -`vt run --cache build` must produce a cache hit on the second run without any manual input/output configuration. Vite reports non-semantic reads and writes through `@voidzero-dev/vite-task-client`, so fspy can infer real inputs and outputs while the cache restores `dist/` on hit. - -## `vt run --cache build` - -first run: cache miss, emits dist/ - -``` -$ vite build -``` - -## `vtt stat-file dist/assets/main.js` - -existence check — content can drift across Vite versions - -``` -dist/assets/main.js: exists -``` - -## `vtt rm dist/assets/main.js` - -remove the artefact so the cache-hit restore is observable - -``` -``` - -## `vt run --cache build` - -cache hit: outputs restored without manual config - -``` -$ vite build ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `vtt stat-file dist/assets/main.js` - -restored from the cache archive - -``` -dist/assets/main.js: exists -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_node_env_change_invalidates_cache.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_node_env_change_invalidates_cache.md deleted file mode 100644 index 116973efb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_node_env_change_invalidates_cache.md +++ /dev/null @@ -1,30 +0,0 @@ -# vite_node_env_change_invalidates_cache - -`NODE_ENV` enters the build's cache fingerprint via Vite's `getEnv('NODE_ENV')` call in `resolveConfig`. Same value → cache hit; different value → cache miss with `envs changed`. - -## `NODE_ENV=production vt run --cache build` - -first run: NODE_ENV=production - -``` -$ vite build -``` - -## `NODE_ENV=production vt run --cache build` - -cache hit: NODE_ENV unchanged - -``` -$ vite build ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `NODE_ENV=development vt run --cache build` - -cache miss: envs changed (NODE_ENV changed) - -``` -$ vite build ○ cache miss: env 'NODE_ENV' changed, executing -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_prefix_env_change_invalidates_cache.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_prefix_env_change_invalidates_cache.md deleted file mode 100644 index e14d40a3d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/snapshots/vite_prefix_env_change_invalidates_cache.md +++ /dev/null @@ -1,62 +0,0 @@ -# vite_prefix_env_change_invalidates_cache - -`VITE_CACHE_LABEL` is picked up by Vite's patched `loadEnv`, which asks the runner for every `VITE_*` env via `getEnvs(pattern, { tracked: true })`. Flipping its value between runs must invalidate the cache AND change the build output because Vite substitutes `import.meta.env.VITE_CACHE_LABEL` at build time. - -## `VITE_CACHE_LABEL=cache-alpha vt run --cache build` - -first run: cache-alpha label - -``` -$ vite build -``` - -## `vtt grep-file dist/assets/main.js cache-alpha` - -cache-alpha label is in the bundle - -``` -dist/assets/main.js: found "cache-alpha" -``` - -## `vtt grep-file dist/assets/main.js cache-bravo` - -cache-bravo label is not in the bundle yet - -``` -dist/assets/main.js: missing "cache-bravo" -``` - -## `VITE_CACHE_LABEL=cache-alpha vt run --cache build` - -cache hit: VITE_CACHE_LABEL unchanged - -``` -$ vite build ◉ cache hit, replaying - ---- -vt run: cache hit. -``` - -## `VITE_CACHE_LABEL=cache-bravo vt run --cache build` - -cache miss: envs changed — VITE_CACHE_LABEL value changed - -``` -$ vite build ○ cache miss: env 'VITE_CACHE_LABEL' changed, executing -``` - -## `vtt grep-file dist/assets/main.js cache-alpha` - -cache-alpha label is gone after the rebuild - -``` -dist/assets/main.js: missing "cache-alpha" -``` - -## `vtt grep-file dist/assets/main.js cache-bravo` - -cache-bravo label is now in the bundle - -``` -dist/assets/main.js: found "cache-bravo" -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/src/main.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/src/main.js deleted file mode 100644 index 918439bfb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/src/main.js +++ /dev/null @@ -1,3 +0,0 @@ -// Vite replaces this at build time from keys matching `envPrefix` (`VITE_` by -// default), so the e2e can verify that glob-tracked env changes rebuild output. -console.log(import.meta.env.VITE_CACHE_LABEL); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite-task.json deleted file mode 100644 index 81571aeaf..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite-task.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tasks": { - "build": { - "command": "vite build", - // No `"env": [...]` — vite's patched `loadEnv` asks the runner for - // every `VITE_*` env via `getEnvs`, so the glob + match-set are - // fingerprinted automatically. - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite.config.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite.config.js deleted file mode 100644 index cc83efec1..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_build_cache/vite.config.js +++ /dev/null @@ -1,15 +0,0 @@ -import { defineConfig } from 'vite'; - -export default defineConfig({ - logLevel: 'silent', - build: { - rollupOptions: { - output: { - // Stable filenames make cache behaviour deterministic across runs. - entryFileNames: 'assets/main.js', - chunkFileNames: 'assets/chunk.js', - assetFileNames: 'assets/[name][extname]', - }, - }, - }, -}); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/dev.mjs b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/dev.mjs deleted file mode 100644 index 3cf79b82a..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/dev.mjs +++ /dev/null @@ -1,14 +0,0 @@ -// Programmatic Vite dev server bring-up: middleware mode skips the HTTP -// listen entirely (Windows runners refuse the 127.0.0.1 bind with -// `listen UNKNOWN`), but `_createServer` still calls `disableCache()` -// via `@voidzero-dev/vite-task-client` on its first line — so even -// though this process exits 0 the runner is told not to store the run -// and the next invocation must miss. -import { createServer } from 'vite'; - -const server = await createServer({ - configFile: false, - logLevel: 'silent', - server: { middlewareMode: true }, -}); -await server.close(); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/package.json deleted file mode 100644 index 23ec17d56..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "vite-dev-disable-cache-fixture", - "private": true, - "type": "module" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/snapshots.toml deleted file mode 100644 index 5b79f9912..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/snapshots.toml +++ /dev/null @@ -1,23 +0,0 @@ -[[e2e]] -name = "vite_dev_disable_cache_noop_allows_cache_hit" -comment = """ -`vt run --cache dev` brings up a Vite dev server programmatically on an -ephemeral port and closes it immediately. Vite calls `disableCache()` via -`@voidzero-dev/vite-task-client`, but the client temporarily ignores that -request, so the next invocation hits the cache. -""" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "--cache", - "dev", - ], comment = "first run — Vite dev calls disableCache, currently ignored by the client" }, - { argv = [ - "vt", - "run", - "--cache", - "dev", - ], comment = "cache hit because disableCache is temporarily a no-op" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/snapshots/vite_dev_disable_cache_noop_allows_cache_hit.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/snapshots/vite_dev_disable_cache_noop_allows_cache_hit.md deleted file mode 100644 index 3eb6ad423..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/snapshots/vite_dev_disable_cache_noop_allows_cache_hit.md +++ /dev/null @@ -1,25 +0,0 @@ -# vite_dev_disable_cache_noop_allows_cache_hit - -`vt run --cache dev` brings up a Vite dev server programmatically on an -ephemeral port and closes it immediately. Vite calls `disableCache()` via -`@voidzero-dev/vite-task-client`, but the client temporarily ignores that -request, so the next invocation hits the cache. - -## `vt run --cache dev` - -first run — Vite dev calls disableCache, currently ignored by the client - -``` -$ node dev.mjs -``` - -## `vt run --cache dev` - -cache hit because disableCache is temporarily a no-op - -``` -$ node dev.mjs ◉ cache hit, replaying - ---- -vt run: cache hit. -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/vite-task.json deleted file mode 100644 index 1b8982a59..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/vite-task.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tasks": { - "dev": { - "command": "node dev.mjs", - "cache": true - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/main.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/main.js deleted file mode 100644 index 81afa3157..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/main.js +++ /dev/null @@ -1 +0,0 @@ -console.log('foo'); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/package.json deleted file mode 100644 index 18efcde70..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "vite-task-smoke", - "private": true, - "scripts": { - "test-task": "echo hello && vtt print-file main.js" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/pnpm-workspace.yaml deleted file mode 100644 index 3334c0e43..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/pnpm-workspace.yaml +++ /dev/null @@ -1 +0,0 @@ -packages: [] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/snapshots.toml deleted file mode 100644 index 4f2fa9b32..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/snapshots.toml +++ /dev/null @@ -1,24 +0,0 @@ -[[e2e]] -name = "cache_hit_after_file_modification" -comment = """ -Tests cache miss when input file is modified -""" -steps = [ - { argv = [ - "vt", - "run", - "test-task", - ], comment = "cache miss" }, - { argv = [ - "vtt", - "replace-file-content", - "main.js", - "foo", - "bar", - ], comment = "modify input file" }, - { argv = [ - "vt", - "run", - "test-task", - ], comment = "cache miss, main.js changed" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/snapshots/cache_hit_after_file_modification.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/snapshots/cache_hit_after_file_modification.md deleted file mode 100644 index 1cd3caa64..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/snapshots/cache_hit_after_file_modification.md +++ /dev/null @@ -1,40 +0,0 @@ -# cache_hit_after_file_modification - -Tests cache miss when input file is modified - -## `vt run test-task` - -cache miss - -``` -$ echo hello ⊘ cache disabled -hello - -$ vtt print-file main.js -console.log('foo'); - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` - -## `vtt replace-file-content main.js foo bar` - -modify input file - -``` -``` - -## `vt run test-task` - -cache miss, main.js changed - -``` -$ echo hello ⊘ cache disabled -hello - -$ vtt print-file main.js ○ cache miss: 'main.js' modified, executing -console.log('bar'); - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/vite-task.json deleted file mode 100644 index f8e740b3f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_task_smoke/vite-task.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - // Smoke test: enables caching for all package.json scripts. - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/package.json deleted file mode 100644 index 57ae719f5..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "vitest-browser-cache-fixture", - "private": true, - "type": "module" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/snapshots.toml deleted file mode 100644 index ec07793da..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/snapshots.toml +++ /dev/null @@ -1,57 +0,0 @@ -[[e2e]] -name = "vitest_browser_caches_inputs_and_restores_outputs" -comment = """ -Vitest browser mode runs in headless Chromium with explicit automatic input and output tracking. A source change must invalidate the cache, while an unchanged run must restore the browser command's generated output without rerunning Vitest. Playwright's bundled Chromium is unavailable on musl, so this browser-specific case is skipped only there. -""" -platform = "non-musl" -ignore = true -steps = [ - { argv = [ - "vt", - "run", - "--cache", - "test", - ], comment = "first browser run: cache miss writes dist/result.json" }, - { argv = [ - "vtt", - "grep-file", - "dist/result.json", - "hello browser alpha", - ], comment = "Vitest's JSON report contains the initial imported value" }, - { argv = [ - "vtt", - "rm", - "dist/result.json", - ], comment = "remove the generated output so restoration is observable" }, - { argv = [ - "vt", - "run", - "--cache", - "test", - ], comment = "unchanged inputs: cache hit restores dist/result.json" }, - { argv = [ - "vtt", - "grep-file", - "dist/result.json", - "hello browser alpha", - ], comment = "the automatic output archive restored Vitest's report" }, - { argv = [ - "vtt", - "replace-file-content", - "src/greeting.js", - "hello browser alpha", - "hello browser bravo", - ], comment = "modify a module loaded by the browser" }, - { argv = [ - "vt", - "run", - "--cache", - "test", - ], comment = "automatic input changed: cache miss reruns the browser test" }, - { argv = [ - "vtt", - "grep-file", - "dist/result.json", - "hello browser bravo", - ], comment = "the rerun's JSON report contains the modified imported value" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/snapshots/vitest_browser_caches_inputs_and_restores_outputs.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/snapshots/vitest_browser_caches_inputs_and_restores_outputs.md deleted file mode 100644 index f6abafc85..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/snapshots/vitest_browser_caches_inputs_and_restores_outputs.md +++ /dev/null @@ -1,83 +0,0 @@ -# vitest_browser_caches_inputs_and_restores_outputs - -Vitest browser mode runs in headless Chromium with explicit automatic input and output tracking. A source change must invalidate the cache, while an unchanged run must restore the browser command's generated output without rerunning Vitest. Playwright's bundled Chromium is unavailable on musl, so this browser-specific case is skipped only there. - -## `vt run --cache test` - -first browser run: cache miss writes dist/result.json - -``` -$ vitest run - - RUN v4.1.10 - - ✓ chromium src/greeting.test.js (1 test) -JSON report written to /dist/result.json -``` - -## `vtt grep-file dist/result.json 'hello browser alpha'` - -Vitest's JSON report contains the initial imported value - -``` -dist/result.json: found "hello browser alpha" -``` - -## `vtt rm dist/result.json` - -remove the generated output so restoration is observable - -``` -``` - -## `vt run --cache test` - -unchanged inputs: cache hit restores dist/result.json - -``` -$ vitest run ◉ cache hit, replaying - - RUN v4.1.10 - - ✓ chromium src/greeting.test.js (1 test) -JSON report written to /dist/result.json - ---- -vt run: cache hit. -``` - -## `vtt grep-file dist/result.json 'hello browser alpha'` - -the automatic output archive restored Vitest's report - -``` -dist/result.json: found "hello browser alpha" -``` - -## `vtt replace-file-content src/greeting.js 'hello browser alpha' 'hello browser bravo'` - -modify a module loaded by the browser - -``` -``` - -## `vt run --cache test` - -automatic input changed: cache miss reruns the browser test - -``` -$ vitest run ○ cache miss: 'src/greeting.js' modified, executing - - RUN v4.1.10 - - ✓ chromium src/greeting.test.js (1 test) -JSON report written to /dist/result.json -``` - -## `vtt grep-file dist/result.json 'hello browser bravo'` - -the rerun's JSON report contains the modified imported value - -``` -dist/result.json: found "hello browser bravo" -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/src/greeting.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/src/greeting.js deleted file mode 100644 index 1dfeaaa8b..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/src/greeting.js +++ /dev/null @@ -1 +0,0 @@ -export const greeting = 'hello browser alpha'; diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/src/greeting.test.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/src/greeting.test.js deleted file mode 100644 index 0dfee04a9..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/src/greeting.test.js +++ /dev/null @@ -1,11 +0,0 @@ -import { expect, test } from 'vitest'; -import { page, server } from 'vitest/browser'; -import { greeting } from './greeting.js'; - -test(greeting, async () => { - document.body.innerHTML = `

${greeting}

`; - - await expect.element(page.getByRole('heading')).toHaveTextContent(greeting); - expect(server.browser).toBe('chromium'); - expect(server.provider).toBe('playwright'); -}); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/vite-task.json deleted file mode 100644 index db1934188..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/vite-task.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "tasks": { - "test": { - "command": "vitest run", - "cache": true, - "input": [{ "auto": true }], - // TODO: Add @voidzero-dev/vite-task-client and have Vitest report this path as ignored. - "output": [{ "auto": true }, "!node_modules/.vite/vitest/**"], - "untrackedEnv": ["PLAYWRIGHT_HOST_PLATFORM_OVERRIDE"] - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/vitest.config.js b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/vitest.config.js deleted file mode 100644 index 1604a770d..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/vitest_browser_cache/vitest.config.js +++ /dev/null @@ -1,20 +0,0 @@ -import { playwright } from '@vitest/browser-playwright'; -import { defineConfig } from 'vitest/config'; -import { DefaultReporter } from 'vitest/node'; - -class NoTestSummaryReporter extends DefaultReporter { - reportTestSummary() {} -} - -export default defineConfig({ - test: { - reporters: [new NoTestSummaryReporter({ summary: false }), 'json'], - outputFile: { json: 'dist/result.json' }, - browser: { - enabled: true, - headless: true, - provider: playwright(), - instances: [{ browser: 'chromium' }], - }, - }, -}); diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/.gitignore b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/.gitignore deleted file mode 100644 index 3d2ee9543..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# This fixture intentionally tracks package-shim files under node_modules/.bin. -!node_modules -!node_modules/** diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/node_modules/.bin/probe-cli.cmd b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/node_modules/.bin/probe-cli.cmd deleted file mode 100644 index 756f5a301..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/node_modules/.bin/probe-cli.cmd +++ /dev/null @@ -1,4 +0,0 @@ -@ECHO off -REM Use the .exe suffix intentionally: PowerShell process launch still depends on PATHEXT here. -vtt.exe write-file marker.txt shim-ran -vtt.exe print shim-ran diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/node_modules/.bin/probe-cli.ps1 b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/node_modules/.bin/probe-cli.ps1 deleted file mode 100644 index 8d6e98fdb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/node_modules/.bin/probe-cli.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -# Use the .exe suffix intentionally: PowerShell process launch still depends on PATHEXT here. -vtt.exe write-file marker.txt shim-ran -vtt.exe print shim-ran -exit $LASTEXITCODE diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/package.json deleted file mode 100644 index ec33c4439..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "windows-powershell-shim-pathext" -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/snapshots.toml deleted file mode 100644 index 7f4b0fc77..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/snapshots.toml +++ /dev/null @@ -1,18 +0,0 @@ -[[e2e]] -name = "powershell_package_shim_keeps_pathext" -platform = "windows" -comment = """ -Cached Windows tasks run with a sanitized environment, and pnpm-style package `.cmd` shims in `node_modules/.bin` are rewritten at plan time to invoke their `.ps1` sibling via `powershell.exe -File`. PowerShell needs `PATHEXT` in that environment to launch native executables — even when the executable name is written out with a `.exe` extension — so `PATHEXT` must be preserved in the default untracked env passthrough. -""" -steps = [ - { argv = [ - "vt", - "run", - "probe", - ], comment = "rewritten to `powershell.exe -File probe-cli.ps1`; the shim launches `vtt.exe write-file` to write `marker.txt`" }, - { argv = [ - "vtt", - "print-file", - "marker.txt", - ], comment = "marker is present only if the shim's `vtt.exe` launch succeeded" }, -] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/snapshots/powershell_package_shim_keeps_pathext.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/snapshots/powershell_package_shim_keeps_pathext.md deleted file mode 100644 index 9a61392a7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/snapshots/powershell_package_shim_keeps_pathext.md +++ /dev/null @@ -1,20 +0,0 @@ -# powershell_package_shim_keeps_pathext - -Cached Windows tasks run with a sanitized environment, and pnpm-style package `.cmd` shims in `node_modules/.bin` are rewritten at plan time to invoke their `.ps1` sibling via `powershell.exe -File`. PowerShell needs `PATHEXT` in that environment to launch native executables — even when the executable name is written out with a `.exe` extension — so `PATHEXT` must be preserved in the default untracked env passthrough. - -## `vt run probe` - -rewritten to `powershell.exe -File probe-cli.ps1`; the shim launches `vtt.exe write-file` to write `marker.txt` - -``` -$ probe-cli -shim-ran -``` - -## `vtt print-file marker.txt` - -marker is present only if the shim's `vtt.exe` launch succeeded - -``` -shim-ran -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/vite-task.json deleted file mode 100644 index 62272d975..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/windows_powershell_shim_pathext/vite-task.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "cache": true, - "tasks": { - "probe": { - "command": "probe-cli", - "cache": true, - "input": [] - } - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/package.json deleted file mode 100644 index ea186e967..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "test-workspace", - "private": true, - "scripts": { - "build": "vt run -r build" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/packages/a/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/packages/a/package.json deleted file mode 100644 index e0c6c3f2f..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/packages/a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/a", - "version": "1.0.0", - "scripts": { - "build": "echo building-a" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/packages/b/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/packages/b/package.json deleted file mode 100644 index aa937a790..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/packages/b/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@test/b", - "version": "1.0.0", - "scripts": { - "build": "echo building-b" - }, - "dependencies": { - "@test/a": "workspace:*" - } -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/pnpm-workspace.yaml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/snapshots.toml deleted file mode 100644 index d03b0fd49..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/snapshots.toml +++ /dev/null @@ -1,13 +0,0 @@ -[[e2e]] -name = "recursive_build_skips_root_self_reference" -comment = """ -`vt run -r build` from the workspace root, when root's own `build` script is itself `vt run -r build`, should hit the skip rule: the nested expansion is the same query, so root's step is skipped and only packages a and b run. -""" -steps = [{ argv = ["vt", "run", "-r", "build"], comment = "only a and b run, root is skipped" }] - -[[e2e]] -name = "build_from_root_prunes_root_from_nested_expansion" -comment = """ -`vt run build` from root produces a `ContainingPackage` query, while root's script (`vt run -r build`) produces an `All` query, so the skip rule does not apply. The prune rule should instead remove root from the nested result, again leaving only a and b. -""" -steps = [{ argv = ["vt", "run", "build"], comment = "only a and b run under root, root is pruned" }] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/snapshots/build_from_root_prunes_root_from_nested_expansion.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/snapshots/build_from_root_prunes_root_from_nested_expansion.md deleted file mode 100644 index dcca4a5e3..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/snapshots/build_from_root_prunes_root_from_nested_expansion.md +++ /dev/null @@ -1,18 +0,0 @@ -# build_from_root_prunes_root_from_nested_expansion - -`vt run build` from root produces a `ContainingPackage` query, while root's script (`vt run -r build`) produces an `All` query, so the skip rule does not apply. The prune rule should instead remove root from the nested result, again leaving only a and b. - -## `vt run build` - -only a and b run under root, root is pruned - -``` -~/packages/a$ echo building-a ⊘ cache disabled -building-a - -~/packages/b$ echo building-b ⊘ cache disabled -building-b - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/snapshots/recursive_build_skips_root_self_reference.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/snapshots/recursive_build_skips_root_self_reference.md deleted file mode 100644 index 57de325f7..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/snapshots/recursive_build_skips_root_self_reference.md +++ /dev/null @@ -1,18 +0,0 @@ -# recursive_build_skips_root_self_reference - -`vt run -r build` from the workspace root, when root's own `build` script is itself `vt run -r build`, should hit the skip rule: the nested expansion is the same query, so root's step is skipped and only packages a and b run. - -## `vt run -r build` - -only a and b run, root is skipped - -``` -~/packages/a$ echo building-a ⊘ cache disabled -building-a - -~/packages/b$ echo building-b ⊘ cache disabled -building-b - ---- -vt run: 0/2 cache hit (0%). (Run `vt run --last-details` for full details) -``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/workspace_root_self_reference/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/main.rs b/crates/vite_task_bin/tests/e2e_snapshots/main.rs deleted file mode 100644 index 2546bffcb..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/main.rs +++ /dev/null @@ -1,701 +0,0 @@ -mod redact; - -use std::{ - env::{self, join_paths, split_paths}, - ffi::OsStr, - io::Write, - sync::{Arc, Mutex, mpsc}, - time::Duration, -}; - -use cp_r::CopyOptions; -use pty_terminal::{geo::ScreenSize, terminal::CommandBuilder}; -use pty_terminal_test::TestTerminal; -use redact::redact_e2e_output; -use vec1::Vec1; -use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf}; -use vite_str::Str; -use vite_workspace::find_workspace_root; - -/// Timeout for each step in e2e tests. -/// Windows CI and `x86_64` macOS CI need longer for process and browser startup. -const STEP_TIMEOUT: Duration = - if cfg!(any(windows, all(target_os = "macos", target_arch = "x86_64"))) { - Duration::from_secs(60) - } else { - Duration::from_secs(20) - }; - -/// Screen size for the PTY terminal. Large enough to avoid line wrapping. -const SCREEN_SIZE: ScreenSize = ScreenSize { rows: 500, cols: 500 }; - -#[derive(serde::Deserialize, Debug)] -#[serde(untagged)] -enum Step { - /// Shorthand: `["vt", "run", "build"]` - Simple(Vec1), - /// Detailed: `{ argv = ["vt", "run"], comment = "cache miss", ... }` - Detailed(StepConfig), -} - -#[derive(serde::Deserialize, Debug)] -#[serde(deny_unknown_fields)] -struct StepConfig { - argv: Vec1, - /// Optional working directory for this step, relative to the staged fixture root. - /// Defaults to the case-level `cwd`. - #[serde(default)] - cwd: Option, - /// Appended as `# comment` in the snapshot display line. - #[serde(default)] - comment: Option, - /// Extra environment variables set for this step. - #[serde(default)] - envs: Vec<(Str, Str)>, - #[serde(default)] - interactions: Vec, - /// When true, render the terminal snapshot with inline ANSI escape codes - /// (made visible as `\e[…m`) so colour/style attributes are part of the - /// assertion. Default `false` keeps the plain-text behaviour. - #[serde(default, rename = "formatted-snapshot")] - formatted_snapshot: bool, -} - -impl Step { - fn argv(&self) -> &[Str] { - match self { - Self::Simple(argv) => argv, - Self::Detailed(config) => &config.argv, - } - } - - /// Shell-escaped command line including any env-var prefix and non-default - /// cwd, without the comment (e.g. `cd packages/a && MY_ENV=1 vt run test`). - /// The comment is surfaced separately by [`Self::comment`]. - #[expect(clippy::disallowed_types, reason = "String required by join/format")] - fn display_command_line(&self, default_cwd: &RelativePathBuf) -> String { - let argv_str = self - .argv() - .iter() - .map(|a| { - let s = a.as_str(); - if s.contains(|c: char| c.is_whitespace() || c == '"') { - shell_escape::escape(s.into()) - } else { - s.into() - } - }) - .collect::>() - .join(" "); - - let command = match self { - Self::Simple(_) => argv_str, - Self::Detailed(config) => { - let mut parts = String::new(); - for (k, v) in &config.envs { - parts.push_str(vite_str::format!("{k}={v} ").as_str()); - } - parts.push_str(&argv_str); - parts - } - }; - - let cwd = self.cwd().unwrap_or(default_cwd); - if cwd == default_cwd { - command - } else { - let cwd = if cwd.as_str().is_empty() { "." } else { cwd.as_str() }; - let mut display = String::new(); - display.push_str("cd "); - display.push_str(shell_escape::escape(cwd.into()).as_ref()); - display.push_str(" && "); - display.push_str(&command); - display - } - } - - fn comment(&self) -> Option<&str> { - match self { - Self::Detailed(config) => config.comment.as_deref(), - Self::Simple(_) => None, - } - } - - fn interactions(&self) -> &[Interaction] { - match self { - Self::Detailed(config) => &config.interactions, - Self::Simple(_) => &[], - } - } - - fn envs(&self) -> &[(Str, Str)] { - match self { - Self::Detailed(config) => &config.envs, - Self::Simple(_) => &[], - } - } - - const fn cwd(&self) -> Option<&RelativePathBuf> { - match self { - Self::Detailed(config) => config.cwd.as_ref(), - Self::Simple(_) => None, - } - } - - const fn formatted_snapshot(&self) -> bool { - match self { - Self::Detailed(config) => config.formatted_snapshot, - Self::Simple(_) => false, - } - } -} - -#[derive(serde::Deserialize, Debug, Clone)] -#[serde(untagged)] -enum Interaction { - ExpectMilestone(ExpectMilestoneInteraction), - Write(WriteInteraction), - WriteLine(WriteLineInteraction), - WriteKey(WriteKeyInteraction), -} - -#[derive(serde::Deserialize, Debug, Clone)] -#[serde(deny_unknown_fields)] -struct ExpectMilestoneInteraction { - #[serde(rename = "expect-milestone")] - expect_milestone: Str, -} - -#[derive(serde::Deserialize, Debug, Clone)] -#[serde(deny_unknown_fields)] -struct WriteInteraction { - write: Str, -} - -#[derive(serde::Deserialize, Debug, Clone)] -#[serde(deny_unknown_fields)] -struct WriteLineInteraction { - #[serde(rename = "write-line")] - write_line: Str, -} - -#[derive(serde::Deserialize, Debug, Clone)] -#[serde(deny_unknown_fields)] -struct WriteKeyInteraction { - #[serde(rename = "write-key")] - write_key: WriteKey, -} - -#[derive(serde::Deserialize, Debug, Clone, Copy)] -#[serde(rename_all = "kebab-case")] -enum WriteKey { - Up, - Down, - Enter, - Escape, - Backspace, - CtrlC, -} - -impl WriteKey { - const fn as_str(self) -> &'static str { - match self { - Self::Up => "up", - Self::Down => "down", - Self::Enter => "enter", - Self::Escape => "escape", - Self::Backspace => "backspace", - Self::CtrlC => "ctrl-c", - } - } - - const fn bytes(self) -> &'static [u8] { - match self { - Self::Up => b"\x1b[A", - Self::Down => b"\x1b[B", - Self::Enter => b"\r", - Self::Escape => b"\x1b", - Self::Backspace => b"\x7f", - Self::CtrlC => b"\x03", - } - } -} - -#[derive(serde::Deserialize, Debug)] -struct E2e { - pub name: Str, - /// Free-form description rendered under the H1 heading of the generated snapshot. - #[serde(default)] - pub comment: Option, - #[serde(default)] - pub cwd: RelativePathBuf, - pub steps: Vec, - /// Optional platform filter: "unix", "linux", "linux-gnu", "non-musl", - /// "macos", or "windows". If set, test only runs on that platform. - #[serde(default)] - pub platform: Option, - /// When true, the generated libtest-mimic trial is marked `#[ignore]` - /// (skipped by default, runnable with `cargo test -- --ignored`). - #[serde(default)] - pub ignore: bool, -} - -#[derive(serde::Deserialize, Default)] -struct SnapshotsFile { - #[serde(rename = "e2e", default)] // toml usually uses singular for arrays - pub e2e_cases: Vec, -} - -/// Fixture folder names and `[[e2e]].name` values must be made of -/// `[A-Za-z0-9_]` only so trial names round-trip through shell filters -/// and snapshot filenames don't carry whitespace or special characters. -fn assert_identifier_like(kind: &str, value: &str) { - assert!( - !value.is_empty() && value.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_'), - "{kind} '{value}' must contain only ASCII letters, digits, and '_'" - ); -} - -#[expect(clippy::disallowed_types, reason = "Path required for fixture path handling")] -fn load_snapshots_file(fixture_path: &std::path::Path) -> SnapshotsFile { - let cases_toml_path = fixture_path.join("snapshots.toml"); - match std::fs::read(&cases_toml_path) { - Ok(content) => toml::from_slice(&content).unwrap(), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => SnapshotsFile::default(), - Err(err) => { - let fixture_name = fixture_path.file_name().unwrap().to_str().unwrap(); - panic!("Failed to read cases.toml for fixture {fixture_name}: {err}"); - } - } -} - -enum TerminationState { - Exited(i64), - TimedOut, -} - -/// Substitutes sentinels in step env values with values only known at -/// test-run time. Keeps the raw sentinel in the snapshot's displayed command -/// line, so snapshots stay machine-independent. -fn resolve_env_placeholder(raw: &str) -> std::borrow::Cow<'_, OsStr> { - if raw == "" { - let path = env::var_os("CARGO_CDYLIB_FILE_PRELOAD_TEST_LIB").unwrap_or_else(|| { - panic!( - "CARGO_CDYLIB_FILE_PRELOAD_TEST_LIB not set; the e2e harness requires \ - the preload_test_lib cdylib artifact to be built by cargo" - ) - }); - std::borrow::Cow::Owned(path) - } else { - std::borrow::Cow::Borrowed(OsStr::new(raw)) - } -} - -/// Render the byte stream produced by `screen_contents_formatted` (which uses -/// `vt100::Screen::rows_formatted` — see [`pty_terminal`]) into a -/// snapshot-friendly string. Newlines (added as row separators by the PTY -/// helper) stay literal so the snapshot remains multi-line; SGR escapes and -/// other bytes outside printable ASCII come out as `\xNN`, `\t`, etc. -#[expect(clippy::disallowed_types, reason = "String required for snapshot rendering")] -fn render_formatted_screen(bytes: &[u8]) -> String { - let mut out = String::with_capacity(bytes.len()); - for &b in bytes { - match b { - b'\n' => out.push('\n'), - _ => out.extend(std::ascii::escape_default(b).map(char::from)), - } - } - out -} - -/// `packages/tools` is the Node environment for e2e fixtures: link its -/// pnpm-resolved `node_modules` into the tempdir that holds every staged -/// fixture workspace, so Node's upward resolution walk lets any fixture import -/// the packages declared in `packages/tools/package.json` (`vite`, -/// `@voidzero-dev/vite-task-client`, ...) by bare specifier — the user-facing -/// flow — without writing a `node_modules` into any staged workspace. -/// Best-effort: skipped when pnpm install hasn't run; only the ignored Node -/// e2e fixtures resolve through it. -#[expect(clippy::disallowed_types, reason = "std::path::Path required for filesystem operations")] -fn link_tools_node_modules(tmp_dir: &std::path::Path) { - let manifest_dir = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()); - let repo_root = manifest_dir.parent().unwrap().parent().unwrap(); - let src = repo_root.join("packages/tools/node_modules"); - if !src.is_dir() { - return; - } - let link = tmp_dir.join("node_modules"); - #[cfg(unix)] - std::os::unix::fs::symlink(&src, &link).unwrap(); - #[cfg(windows)] - std::os::windows::fs::symlink_dir(&src, &link).unwrap(); -} - -/// Append a fenced markdown block containing `body`. The opening and closing -/// fences sit on their own lines, and trailing whitespace inside `body` is -/// trimmed so the close fence isn't preceded by blank lines. -#[expect(clippy::disallowed_types, reason = "String required by mutable appender")] -fn push_fenced_block(out: &mut String, body: &str) { - let trimmed = body.trim_end_matches(['\n', ' ', '\t']); - out.push_str("```\n"); - if !trimmed.is_empty() { - out.push_str(trimmed); - out.push('\n'); - } - out.push_str("```\n"); -} - -#[expect( - clippy::too_many_lines, - reason = "e2e test runner with process management necessarily has many lines" -)] -#[expect( - clippy::disallowed_types, - reason = "Path required for fixture handling; String required by from_utf8_lossy and string accumulation" -)] -fn run_case( - tmpdir: &AbsolutePath, - fixture_path: &std::path::Path, - fixture_name: &str, - case_index: usize, - e2e: &E2e, -) -> Result<(), String> { - let snapshots = snapshot_test::Snapshots::new(fixture_path.join("snapshots")); - - // Copy the fixture to a per-case staging directory so the test runs in - // isolation and workspace-root discovery doesn't walk past the fixture. - let e2e_stage_path = tmpdir.join(vite_str::format!("{fixture_name}_case_{case_index}")); - CopyOptions::new().copy_tree(fixture_path, e2e_stage_path.as_path()).unwrap(); - - let (workspace_root, _cwd) = find_workspace_root(&e2e_stage_path).unwrap(); - assert_eq!( - &e2e_stage_path, &*workspace_root.path, - "folder '{fixture_name}' should be a workspace root" - ); - - // Prepare PATH for e2e tests: include vt and vtt binary directories. - let bin_dirs: [Arc; 2] = ["CARGO_BIN_EXE_vt", "CARGO_BIN_EXE_vtt"].map(|var| { - let bin_path = env::var_os(var).unwrap_or_else(|| panic!("{var} not set")); - let bin = AbsolutePathBuf::new(std::path::PathBuf::from(bin_path)).unwrap(); - Arc::::from(bin.parent().unwrap().as_path().as_os_str()) - }); - - // Also expose tool bins installed under packages/tools/node_modules/.bin - // (e.g. `vite`) so ignored e2e fixtures can exercise real toolchains. - #[expect(clippy::disallowed_types, reason = "PathBuf needed for workspace path arithmetic")] - let tools_bin_dir: Option> = { - let manifest_dir = std::path::PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); - let repo_root = manifest_dir.parent().unwrap().parent().unwrap(); - let tools_bin = repo_root.join("packages/tools/node_modules/.bin"); - tools_bin.is_dir().then(|| Arc::::from(tools_bin.into_os_string())) - }; - - let e2e_env_path = join_paths( - bin_dirs.iter().cloned().chain(tools_bin_dir.iter().cloned()).chain( - // the existing PATH - split_paths(&env::var_os("PATH").unwrap()) - .map(|path| Arc::::from(path.into_os_string())), - ), - ) - .unwrap(); - - let e2e_stage_path_str = e2e_stage_path.as_path().to_str().unwrap(); - - let mut e2e_outputs = String::new(); - e2e_outputs.push_str(vite_str::format!("# {}\n", e2e.name).as_str()); - if let Some(comment) = e2e.comment.as_deref() { - // Normalize CRLF → LF; on Windows, git checkouts with autocrlf embed - // `\r\n` inside TOML multi-line strings, which would make `actual` - // diverge from the stored `.md` (loaded via `\r\n` → `\n` normalization). - let normalized = { - use cow_utils::CowUtils as _; - comment.cow_replace("\r\n", "\n").into_owned() - }; - let trimmed = normalized.trim_matches('\n'); - if !trimmed.is_empty() { - e2e_outputs.push('\n'); - e2e_outputs.push_str(trimmed); - e2e_outputs.push('\n'); - } - } - { - for step in &e2e.steps { - let step_display = step.display_command_line(&e2e.cwd); - let step_comment = step.comment().map(str::to_owned); - - let argv = step.argv(); - - // Only vt and vtt are allowed as step programs. - let program = argv[0].as_str(); - assert!( - program == "vt" || program == "vtt", - "step program must be 'vt' or 'vtt', got '{program}'" - ); - let exe_env = vite_str::format!("CARGO_BIN_EXE_{program}"); - let resolved = - env::var_os(exe_env.as_str()).unwrap_or_else(|| panic!("{exe_env} not set")); - let mut cmd = CommandBuilder::new(resolved); - for arg in &argv[1..] { - cmd.arg(arg.as_str()); - } - cmd.env_clear(); - cmd.env("PATH", &e2e_env_path); - // Use `xterm-256color` and report color support so the runner does - // NOT wrap its output writers with [`anstream::StripStream`]. The - // strip layer would otherwise eat OSC title sequences that - // the test harness relies on to synchronise with the child. ANSI - // escapes emitted by the runner still get flattened to plain text - // by vt100's `Screen::contents()` when the snapshot is rendered, - // so this does not introduce colour-noise into existing snapshots. - cmd.env("TERM", "xterm-256color"); - // The macOS x64 CI shard needs Playwright to select an x64 browser - // while running under Rosetta on Apple Silicon. - if let Some(value) = env::var_os("PLAYWRIGHT_HOST_PLATFORM_OVERRIDE") { - cmd.env("PLAYWRIGHT_HOST_PLATFORM_OVERRIDE", value); - } - // On Windows, ensure common executable extensions are included in PATHEXT for command resolution in subprocesses. - if cfg!(windows) { - cmd.env("PATHEXT", ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC"); - // Forward the Windows env vars Node needs to find a real - // temp directory. Without these, Node's compile cache and - // similar helpers fall back to a workspace-relative path - // and break cached runs. Values come from cargo-test's - // own env when present. - for name in [ - "TMP", - "TEMP", - "APPDATA", - "LOCALAPPDATA", - "PROGRAMDATA", - "USERPROFILE", - "HOMEDRIVE", - "HOMEPATH", - "WINDIR", - "SYSTEMROOT", - "SYSTEMDRIVE", - "ProgramFiles", - "ProgramFiles(x86)", - ] { - if let Some(value) = env::var_os(name) { - cmd.env(name, value); - } - } - } - for (k, v) in step.envs() { - let resolved = resolve_env_placeholder(v.as_str()); - cmd.env(k.as_str(), AsRef::::as_ref(&resolved)); - } - let step_cwd = step.cwd().unwrap_or(&e2e.cwd); - cmd.cwd(e2e_stage_path.join(step_cwd).as_path()); - - let terminal = TestTerminal::spawn(SCREEN_SIZE, cmd).unwrap(); - let mut killer = terminal.child_handle.clone(); - let interactions = step.interactions().to_vec(); - let formatted_snapshot = step.formatted_snapshot(); - let output = Arc::new(Mutex::new(String::new())); - let output_for_thread = Arc::clone(&output); - let (tx, rx) = mpsc::channel(); - std::thread::spawn(move || { - let mut terminal = terminal; - - for interaction in interactions { - match interaction { - Interaction::ExpectMilestone(expect) => { - output_for_thread.lock().unwrap().push_str( - vite_str::format!( - "**→ expect-milestone:** `{}`\n\n", - expect.expect_milestone - ) - .as_str(), - ); - let milestone_screen = - terminal.reader.expect_milestone(expect.expect_milestone.as_str()); - let mut output = output_for_thread.lock().unwrap(); - push_fenced_block(&mut output, &milestone_screen); - output.push('\n'); - } - Interaction::Write(write) => { - output_for_thread.lock().unwrap().push_str( - vite_str::format!("**← write:** `{}`\n\n", write.write).as_str(), - ); - terminal.writer.write_all(write.write.as_str().as_bytes()).unwrap(); - terminal.writer.flush().unwrap(); - } - Interaction::WriteLine(write_line) => { - output_for_thread.lock().unwrap().push_str( - vite_str::format!( - "**← write-line:** `{}`\n\n", - write_line.write_line - ) - .as_str(), - ); - terminal - .writer - .write_line(write_line.write_line.as_str().as_bytes()) - .unwrap(); - } - Interaction::WriteKey(write_key) => { - let key_name = write_key.write_key.as_str(); - output_for_thread.lock().unwrap().push_str( - vite_str::format!("**← write-key:** `{key_name}`\n\n").as_str(), - ); - terminal.writer.write_all(write_key.write_key.bytes()).unwrap(); - terminal.writer.flush().unwrap(); - } - } - } - - let status = terminal.reader.wait_for_exit().unwrap(); - let screen = if formatted_snapshot { - render_formatted_screen(&terminal.reader.screen_contents_formatted()) - } else { - terminal.reader.screen_contents() - }; - - { - let mut output = output_for_thread.lock().unwrap(); - push_fenced_block(&mut output, &screen); - } - - let _ = tx.send(i64::from(status.exit_code())); - }); - - let (termination_state, output) = match rx.recv_timeout(STEP_TIMEOUT) { - Ok(exit_code) => { - let output = output.lock().unwrap().clone(); - (TerminationState::Exited(exit_code), output) - } - Err(mpsc::RecvTimeoutError::Timeout) => { - let _ = killer.kill(); - let output = output.lock().unwrap().clone(); - (TerminationState::TimedOut, output) - } - Err(mpsc::RecvTimeoutError::Disconnected) => { - panic!("Terminal thread panicked"); - } - }; - - // Blank line separator before every `##` (between the file's `#` - // heading and the first step, and between consecutive steps). - e2e_outputs.push('\n'); - - e2e_outputs.push_str("## `"); - e2e_outputs.push_str(&step_display); - e2e_outputs.push_str("`\n\n"); - - if let Some(comment) = &step_comment { - e2e_outputs.push_str(comment); - e2e_outputs.push_str("\n\n"); - } - - match &termination_state { - TerminationState::TimedOut => { - e2e_outputs.push_str("**Exit code:** timeout\n\n"); - } - TerminationState::Exited(exit_code) => { - if *exit_code != 0 { - e2e_outputs - .push_str(vite_str::format!("**Exit code:** {exit_code}\n\n").as_str()); - } - } - } - - e2e_outputs.push_str(&redact_e2e_output(output, e2e_stage_path_str)); - - // Skip remaining steps if timed out - if matches!(termination_state, TerminationState::TimedOut) { - break; - } - } - } - snapshots.check_snapshot(vite_str::format!("{}.md", e2e.name).as_str(), &e2e_outputs)?; - Ok(()) -} - -#[expect(clippy::disallowed_types, reason = "Path required for CARGO_MANIFEST_DIR path traversal")] -fn main() { - let tmp_dir = tempfile::tempdir().unwrap(); - let tmp_dir_path = AbsolutePathBuf::new(tmp_dir.path().canonicalize().unwrap()).unwrap(); - - let manifest_dir = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()); - - // Copy .node-version to the tmp dir so version manager shims can resolve the correct - // Node.js binary when running task commands. - let repo_root = manifest_dir.parent().unwrap().parent().unwrap(); - std::fs::copy(repo_root.join(".node-version"), tmp_dir.path().join(".node-version")).unwrap(); - - // Shared by all staged fixture workspaces via Node's upward resolution walk. - link_tools_node_modules(tmp_dir.path()); - - let fixtures_dir = manifest_dir.join("tests/e2e_snapshots/fixtures"); - - let mut fixture_paths = std::fs::read_dir(fixtures_dir) - .unwrap() - .map(|entry| entry.unwrap().path()) - .filter(|p| p.file_name().and_then(|n| n.to_str()).is_some_and(|n| !n.starts_with('.'))) - .collect::>(); - fixture_paths.sort(); - - let mut args = libtest_mimic::Arguments::from_args(); - // On Linux, running e2e fixtures in parallel causes PTY and signal-routing - // contention (ctrl-c test intermittently fails). macOS and Windows are - // unaffected, so only force sequential execution on Linux. - if cfg!(target_os = "linux") && args.test_threads.is_none() { - args.test_threads = Some(1); - } - - let tests: Vec = fixture_paths - .into_iter() - .flat_map(|fixture_path| { - let fixture_path = Arc::::from(fixture_path); - let fixture_name: Arc = - Arc::from(fixture_path.file_name().unwrap().to_str().unwrap()); - assert_identifier_like("fixture folder", &fixture_name); - let cases_file = load_snapshots_file(&fixture_path); - cases_file.e2e_cases.into_iter().enumerate().filter_map({ - let fixture_path = Arc::clone(&fixture_path); - let fixture_name = Arc::clone(&fixture_name); - let tmp_dir_path = tmp_dir_path.clone(); - move |(case_index, e2e)| { - assert_identifier_like("e2e case name", e2e.name.as_str()); - // Skip cases whose platform filter doesn't match this build. - if let Some(platform) = &e2e.platform { - let should_run = match platform.as_str() { - "unix" => cfg!(unix), - "windows" => cfg!(windows), - "linux" => cfg!(target_os = "linux"), - "macos" => cfg!(target_os = "macos"), - // fspy's LD_PRELOAD injection path is only active - // on glibc-Linux; on musl, fspy switches to - // seccomp-unotify and strips LD_PRELOAD from - // spawned children, which breaks fixtures that - // depend on interposer ordering. - "linux-gnu" => cfg!(target_os = "linux") && !cfg!(target_env = "musl"), - // Playwright's bundled browser binaries do not - // support musl targets. - "non-musl" => !cfg!(target_env = "musl"), - other => panic!("Unknown platform '{}' in test '{}'", other, e2e.name), - }; - if !should_run { - return None; - } - } - let trial_name = vite_str::format!("{fixture_name}::{}", e2e.name); - let ignored = e2e.ignore; - let fixture_path = Arc::clone(&fixture_path); - let fixture_name = Arc::clone(&fixture_name); - let tmp_dir_path = tmp_dir_path.clone(); - Some( - libtest_mimic::Trial::test(trial_name.as_str(), move || { - run_case(&tmp_dir_path, &fixture_path, &fixture_name, case_index, &e2e) - .map_err(Into::into) - }) - .with_ignored_flag(ignored), - ) - } - }) - }) - .collect(); - - libtest_mimic::run(&args, tests).exit(); -} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/redact.rs b/crates/vite_task_bin/tests/e2e_snapshots/redact.rs deleted file mode 100644 index fea4a0b92..000000000 --- a/crates/vite_task_bin/tests/e2e_snapshots/redact.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::borrow::Cow; - -#[expect( - clippy::disallowed_types, - reason = "String mutation required by regex replace and cow_replace APIs" -)] -fn redact_string(s: &mut String, redactions: &[(&str, &str)]) { - use cow_utils::CowUtils as _; - for (from, to) in redactions { - if let Cow::Owned(mut replaced) = s.as_str().cow_replace(from, to) { - if cfg!(windows) { - // Normalize backslashes to forward slashes on Windows - replaced = replaced.cow_replace("\\", "/").into_owned(); - // Collapse double slashes that arise when an escaped path separator (\\) - // is only partially replaced (e.g., Debug-format paths end with \\") - while replaced.contains("//") { - replaced = replaced.cow_replace("//", "/").into_owned(); - } - } - *s = replaced; - } - } -} - -#[expect( - clippy::disallowed_types, - reason = "String required by regex replace_all and cow_replace APIs; Path required for CARGO_MANIFEST_DIR path manipulation" -)] -pub fn redact_e2e_output(mut output: String, workspace_root: &str) -> String { - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - // On Windows, canonicalize() may produce verbatim paths (\\?\C:\...) while - // child processes report paths without the prefix. Try both variants. - let workspace_root_stripped = workspace_root.strip_prefix(r"\\?\").unwrap_or(workspace_root); - - // On Windows, paths displayed via Debug format ({:?}) have backslashes escaped - // to double-backslashes. Create escaped variants to match Debug-format output. - // The full escaped variant (with \\?\ prefix) must be tried first since it's - // the longest match and prevents leaving a stray "\\?\" in the output. - let workspace_root_full_escaped = { - use cow_utils::CowUtils as _; - workspace_root.cow_replace('\\', r"\\").into_owned() - }; - let workspace_root_stripped_escaped = { - use cow_utils::CowUtils as _; - workspace_root_stripped.cow_replace('\\', r"\\").into_owned() - }; - let workspace_root_forward_slashes = { - use cow_utils::CowUtils as _; - workspace_root_stripped.cow_replace('\\', "/").into_owned() - }; - - let mut redactions: Vec<(&str, &str)> = vec![ - (workspace_root, ""), - (workspace_root_stripped, ""), - (manifest_dir.as_str(), ""), - ]; - - // Add escaped variants (longest first for correct matching) - if workspace_root_full_escaped != workspace_root { - redactions.insert(0, (&workspace_root_full_escaped, "")); - } - if workspace_root_stripped_escaped != workspace_root_stripped - && workspace_root_stripped_escaped != workspace_root_full_escaped - { - redactions.insert(1, (&workspace_root_stripped_escaped, "")); - } - if workspace_root_forward_slashes != workspace_root_stripped { - redactions.push((&workspace_root_forward_slashes, "")); - } - - redact_string(&mut output, &redactions); - - // Redact UUIDs (e.g. cache archive filenames `.tar.zst`) to "" - let uuid_regex = - regex::Regex::new(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}").unwrap(); - output = uuid_regex.replace_all(&output, "").into_owned(); - - // Redact durations like "0ns", "123ms" or "1.23s" to "" - let duration_regex = regex::Regex::new(r"\d+(\.\d+)?(ns|ms|s)").unwrap(); - output = duration_regex.replace_all(&output, "").into_owned(); - - // Normalize the ", saved" suffix in cache hit summaries. - // When tools are fast (e.g., Rust binaries), saved time may be 0ns and the - // runner omits the suffix entirely. Stripping it ensures stable snapshots. - let saved_regex = regex::Regex::new(r",? saved").unwrap(); - output = saved_regex.replace_all(&output, "").into_owned(); - - // Strip "in total" from verbose performance summary (includes time details - // that may be omitted when saved time is 0). - { - use cow_utils::CowUtils as _; - if let Cow::Owned(replaced) = output.as_str().cow_replace(" in total", "") { - output = replaced; - } - } - - // Redact thread counts like "using 10 threads" to "using threads" - let thread_regex = regex::Regex::new(r"using \d+ threads").unwrap(); - output = thread_regex.replace_all(&output, "using threads").into_owned(); - - // Remove Node.js experimental warnings (e.g., Type Stripping warnings) - let node_warning_regex = - regex::Regex::new(r"(?m)^\(node:\d+\) ExperimentalWarning:.*\n?").unwrap(); - output = node_warning_regex.replace_all(&output, "").into_owned(); - let node_trace_warning_regex = regex::Regex::new( - r"(?m)^\(Use `node --trace-warnings \.\.\.` to show where the warning was created\)\n?", - ) - .unwrap(); - output = node_trace_warning_regex.replace_all(&output, "").into_owned(); - - // Remove nondeterministic mise warnings from shell startup in cross-platform runners. - let mise_warning_regex = regex::Regex::new(r"(?m)^mise WARN\s+.*\n?").unwrap(); - output = mise_warning_regex.replace_all(&output, "").into_owned(); - - // Remove ^C echo that Unix terminal drivers emit when ETX (0x03) is written - // to the PTY. Windows ConPTY does not echo it. - { - use cow_utils::CowUtils as _; - if let Cow::Owned(replaced) = output.as_str().cow_replace("^C", "") { - output = replaced; - } - } - - // Sort consecutive diagnostic blocks to handle non-deterministic tool output - // (e.g., oxlint reports warnings in arbitrary order due to multi-threading). - // Each block starts with " ! " and ends at the next empty line. - output = sort_diagnostic_blocks(&output); - - output -} - -#[expect( - clippy::disallowed_types, - reason = "String return required because join produces a String" -)] -fn sort_diagnostic_blocks(output: &str) -> String { - let parts: Vec<&str> = output.split('\n').collect(); - let mut result: Vec<&str> = Vec::new(); - let mut i = 0; - - while i < parts.len() { - if parts[i].starts_with(" ! ") { - let mut blocks: Vec> = Vec::new(); - - loop { - if i >= parts.len() || !parts[i].starts_with(" ! ") { - break; - } - let mut block: Vec<&str> = Vec::new(); - while i < parts.len() && !parts[i].is_empty() { - block.push(parts[i]); - i += 1; - } - blocks.push(block); - // Skip the empty line separator between blocks - if i < parts.len() && parts[i].is_empty() { - i += 1; - } - } - - blocks.sort(); - - for (j, block) in blocks.iter().enumerate() { - result.extend_from_slice(block); - // Restore empty line separators (between blocks + trailing) - if j < blocks.len() - 1 || i <= parts.len() { - result.push(""); - } - } - } else { - result.push(parts[i]); - i += 1; - } - } - - result.join("\n") -} diff --git a/crates/vite_task_client/Cargo.toml b/crates/vite_task_client/Cargo.toml deleted file mode 100644 index b926bc01f..000000000 --- a/crates/vite_task_client/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "vite_task_client" -version = "0.0.0" -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -native_str = { workspace = true } -rustc-hash = { workspace = true } -vite_path = { workspace = true } -vite_task_ipc_shared = { workspace = true } -wincode = { workspace = true, features = ["derive"] } - -[target.'cfg(windows)'.dependencies] -winapi = { workspace = true, features = ["namedpipeapi"] } - -[lints] -workspace = true - -[lib] -doctest = false -test = false diff --git a/crates/vite_task_client/README.md b/crates/vite_task_client/README.md deleted file mode 100644 index f1ab5ee44..000000000 --- a/crates/vite_task_client/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# vite_task_client - -IPC client that connects from tool processes to the task runner to report inputs/outputs, request env values, and disable caching. diff --git a/crates/vite_task_client/src/lib.rs b/crates/vite_task_client/src/lib.rs deleted file mode 100644 index 715de5c08..000000000 --- a/crates/vite_task_client/src/lib.rs +++ /dev/null @@ -1,242 +0,0 @@ -use std::{ - cell::RefCell, - ffi::OsStr, - io::{self, Read, Write}, - sync::Arc, -}; - -use native_str::NativeStr; -use rustc_hash::FxHashMap; -use vite_path::{self, AbsolutePath}; -use vite_task_ipc_shared::{ - EnvQuery as IpcEnvQuery, GetEnvResponse, GetEnvsResponse, IPC_ENV_NAME, Request, -}; -use wincode::{SchemaRead, config::DefaultConfig}; - -#[cfg(unix)] -type Stream = std::os::unix::net::UnixStream; -#[cfg(windows)] -type Stream = std::fs::File; - -pub struct Client { - stream: RefCell, - scratch: RefCell>, -} - -#[derive(Debug, Clone, Copy)] -pub enum GetEnvsQuery<'a> { - Glob(&'a str), - Prefix(&'a str), -} - -impl Client { - /// Scans `envs` for the runner's IPC connection info and connects if - /// present. Typical callers pass `std::env::vars_os()`. - /// - /// Returns `Ok(None)` if the IPC env is absent (running outside the runner). - /// `Err(..)` if the env is set but connecting fails. - /// - /// # Errors - /// - /// Returns an error if the env var is set but the server cannot be reached. - pub fn from_envs( - envs: impl Iterator, impl AsRef)>, - ) -> io::Result> { - for (name, value) in envs { - if name.as_ref() == IPC_ENV_NAME { - let stream = connect(value.as_ref())?; - return Ok(Some(Self::from_stream(stream))); - } - } - Ok(None) - } - - const fn from_stream(stream: Stream) -> Self { - Self { stream: RefCell::new(stream), scratch: RefCell::new(Vec::new()) } - } - - /// `path` can be a file or a directory; for a directory, all files inside - /// it are ignored. Relative paths are resolved against the current working - /// directory before being sent to the runner. - /// - /// Fire-and-forget: the call returns once the request is flushed to the - /// kernel pipe buffer; the runner processes it during its drain phase - /// after this process exits. See the `Request` type in the IPC protocol - /// crate for why this is safe. - /// - /// # Errors - /// - /// Returns an error if the request fails to send, or if a relative `path` - /// cannot be resolved against the current working directory. - pub fn ignore_input(&self, path: &OsStr) -> io::Result<()> { - let ns = resolve_path(path)?; - self.send(&Request::IgnoreInput(&ns)) - } - - /// Fire-and-forget — see [`Self::ignore_input`]. - /// - /// # Errors - /// - /// Returns an error if the request fails to send, or if a relative `path` - /// cannot be resolved against the current working directory. - pub fn ignore_output(&self, path: &OsStr) -> io::Result<()> { - let ns = resolve_path(path)?; - self.send(&Request::IgnoreOutput(&ns)) - } - - /// Temporary no-op. - /// - /// `disableCache` currently causes too many false opt-outs because tools - /// call it during configuration, before they know whether they will start - /// an uncached operation such as listening on a port or watching the - /// filesystem. - /// - /// # Errors - /// - /// This temporary no-op does not currently return errors. - #[expect( - clippy::missing_const_for_fn, - clippy::unnecessary_wraps, - clippy::unused_self, - reason = "temporary no-op preserves the public API until disableCache semantics are redesigned" - )] - pub fn disable_cache(&self) -> io::Result<()> { - Ok(()) - } - - /// Requests an env value from the runner. Returns `None` if the runner - /// reports the env is not available. - /// - /// # Errors - /// - /// Returns an error if the request or response fails. - pub fn get_env(&self, name: &OsStr, tracked: bool) -> io::Result>> { - let name = Box::::from(name); - - self.send(&Request::GetEnv { name: &name, tracked })?; - let response: GetEnvResponse = self.recv()?; - Ok(response - .env_value - .map(|env_value| Arc::::from(env_value.to_cow_os_str().as_ref()))) - } - - /// Requests every env whose name matches `query` from the runner. The - /// returned map is keyed by env name with its value. - /// - /// # Errors - /// - /// Returns an error if the request or response fails, or if the server - /// rejects a glob query as an invalid glob. - pub fn get_envs( - &self, - query: GetEnvsQuery<'_>, - tracked: bool, - ) -> io::Result, Arc>> { - let query = match query { - GetEnvsQuery::Glob(pattern) => IpcEnvQuery::Glob(pattern), - GetEnvsQuery::Prefix(prefix) => IpcEnvQuery::Prefix(prefix), - }; - self.send(&Request::GetEnvs { query, tracked })?; - let response: GetEnvsResponse = self.recv()?; - Ok(response - .entries - .into_iter() - .map(|(name, value)| { - ( - Arc::::from(name.to_cow_os_str().as_ref()), - Arc::::from(value.to_cow_os_str().as_ref()), - ) - }) - .collect()) - } - - fn send(&self, request: &Request<'_>) -> io::Result<()> { - let bytes = wincode::serialize(request) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; - let len = u32::try_from(bytes.len()) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "request too large"))?; - let mut stream = self.stream.borrow_mut(); - stream.write_all(&len.to_le_bytes())?; - stream.write_all(&bytes)?; - stream.flush()?; - Ok(()) - } - - fn recv(&self) -> io::Result - where - for<'de> T: SchemaRead<'de, DefaultConfig, Dst = T>, - { - let mut stream = self.stream.borrow_mut(); - let mut scratch = self.scratch.borrow_mut(); - let mut len_bytes = [0u8; 4]; - stream.read_exact(&mut len_bytes)?; - let len = u32::from_le_bytes(len_bytes) as usize; - scratch.clear(); - scratch.resize(len, 0); - stream.read_exact(&mut scratch)?; - wincode::deserialize_exact(&scratch) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) - } -} - -fn resolve_path(path: &OsStr) -> io::Result> { - if let Some(abs) = AbsolutePath::new(path) { - return Ok(Box::::from(abs.as_path().as_os_str())); - } - - let mut absolute = vite_path::current_dir()?; - absolute.push(path); - Ok(Box::::from(absolute.as_absolute_path().as_path().as_os_str())) -} - -#[cfg(unix)] -fn connect(name: &OsStr) -> io::Result { - std::os::unix::net::UnixStream::connect(name) -} - -/// Open a Windows named pipe as a client. -/// -/// `OpenOptions::open` on a named-pipe path fails with `ERROR_PIPE_BUSY` when -/// the server's only pending instance has just been claimed by another client -/// — the brief window between the server accepting one connection and creating -/// the next instance. On `ERROR_PIPE_BUSY` we hand off to the kernel via -/// `WaitNamedPipeW`, which blocks until an instance becomes available (or -/// fails if the named pipe is gone). No polling and no arbitrary timeouts. -/// -/// This matches what the `interprocess` crate does internally. -#[cfg(windows)] -fn connect(name: &OsStr) -> io::Result { - use std::{fs::OpenOptions, os::windows::ffi::OsStrExt}; - - use winapi::um::namedpipeapi::WaitNamedPipeW; - - // ERROR_PIPE_BUSY — see WinError.h. `std::io::Error` does not expose a - // typed constant for this, so the raw OS code is the cleanest test. - const ERROR_PIPE_BUSY: i32 = 231; - // NMPWAIT_WAIT_FOREVER — see WinBase.h. winapi 0.3 doesn't define the - // NMPWAIT_* constants yet (only the comment placeholder). - const NMPWAIT_WAIT_FOREVER: u32 = 0xFFFF_FFFF; - - // `WaitNamedPipeW` needs a NUL-terminated UTF-16 path. - let mut wide: Vec = name.encode_wide().collect(); - wide.push(0); - - loop { - match OpenOptions::new().read(true).write(true).open(name) { - Ok(file) => return Ok(file), - Err(err) if err.raw_os_error() == Some(ERROR_PIPE_BUSY) => { - // SAFETY: `wide` is NUL-terminated; pointer stays valid for - // the call's duration. `NMPWAIT_WAIT_FOREVER` makes this a - // bounded kernel wait (server's pipe wait-timeout is the - // upper bound on each retry; default ~50ms, then we loop). - let ok = unsafe { WaitNamedPipeW(wide.as_ptr(), NMPWAIT_WAIT_FOREVER) }; - if ok == 0 { - return Err(io::Error::last_os_error()); - } - // Loop and re-open — another client may have raced us to the - // newly-available instance. - } - Err(err) => return Err(err), - } - } -} diff --git a/crates/vite_task_client_napi/Cargo.toml b/crates/vite_task_client_napi/Cargo.toml deleted file mode 100644 index c7d702311..000000000 --- a/crates/vite_task_client_napi/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "vite_task_client_napi" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true - -[lib] -crate-type = ["cdylib"] -test = false -doctest = false - -[dependencies] -napi = { workspace = true, features = ["napi6", "tracing"] } -napi-derive = { workspace = true } -vite_str = { workspace = true } -vite_task_client = { workspace = true } - -[build-dependencies] -napi-build = { workspace = true } - -[lints] -workspace = true diff --git a/crates/vite_task_client_napi/README.md b/crates/vite_task_client_napi/README.md deleted file mode 100644 index c85a17f99..000000000 --- a/crates/vite_task_client_napi/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# vite_task_client_napi - -Node addon that lets JS/TS tools running inside a `vp run` task talk to the runner over IPC via `vite_task_client`. diff --git a/crates/vite_task_client_napi/build.rs b/crates/vite_task_client_napi/build.rs deleted file mode 100644 index 12c9f88b3..000000000 --- a/crates/vite_task_client_napi/build.rs +++ /dev/null @@ -1,42 +0,0 @@ -#![expect( - clippy::disallowed_types, - reason = "build.rs interfaces with std::path and cargo's env-var API" -)] - -extern crate napi_build; - -use std::{env, fs, path::PathBuf}; - -fn main() { - napi_build::setup(); - - // Keep this crate's napi-derive type-defs out of any consumer's generated - // binding. - // - // `vite_task_client_napi` is embedded as a cdylib *artifact* dependency of - // `vite_task`. napi-derive's `type-def` feature is force-enabled by feature - // unification with consumers that need it (e.g. vite-plus's CLI binding), so - // disabling the feature here has no effect. By default napi-derive then - // writes this crate's `#[napi]` items (`RunnerClient`/`load`) into the - // consumer's shared `NAPI_TYPE_DEF_TMP_FOLDER`, which `@napi-rs/cli` sweeps - // into the consumer's `index.cjs`/`index.d.cts` as dead exports (the symbols - // live in the separately-loaded addon, not the consumer's `.node`). The - // public JS surface is the hand-written `@voidzero-dev/vite-task-client` - // package, so these generated defs are never needed. - // - // `@napi-rs/cli` reuses that folder across builds without pruning it, so - // first remove any entry a pre-redirect build left there, then redirect this - // crate's emission to an isolated, clearly-named sink. The override applies - // only to this crate's rustc invocation, where the napi-derive proc-macro - // reads the env at expansion time, so consumers' own type-defs are - // unaffected. - println!("cargo::rerun-if-env-changed=NAPI_TYPE_DEF_TMP_FOLDER"); - let pkg = env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME not set"); - if let Ok(consumer_folder) = env::var("NAPI_TYPE_DEF_TMP_FOLDER") { - let _ = fs::remove_file(PathBuf::from(consumer_folder).join(&pkg)); - } - let sink = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")) - .join("discarded-napi-type-defs"); - fs::create_dir_all(&sink).expect("failed to create napi type-def sink dir"); - println!("cargo::rustc-env=NAPI_TYPE_DEF_TMP_FOLDER={}", sink.display()); -} diff --git a/crates/vite_task_client_napi/src/lib.rs b/crates/vite_task_client_napi/src/lib.rs deleted file mode 100644 index 72e170c35..000000000 --- a/crates/vite_task_client_napi/src/lib.rs +++ /dev/null @@ -1,165 +0,0 @@ -//! Node addon that exposes a `load()` factory which returns a -//! `RunnerClient` JS class instance bound to the runner's IPC connection. -//! Not intended to be published directly — the runner hands the compiled -//! `.node` file to child processes via the `VP_RUN_NODE_CLIENT_PATH` env -//! var, and the JS wrapper in `@voidzero-dev/vite-task-client` -//! `require()`s it lazily. -//! -//! The factory shape (`load() -> RunnerClient`, rather than methods -//! exported at the top level) is a deliberate layer of indirection so -//! the addon can evolve over time: a future wrapper can pass an options -//! argument (e.g. a version field) and receive a differently-shaped -//! addon, without breaking older addons that ignore the argument. -//! -//! `load()` is callable only inside a runner-spawned task: when the IPC -//! env is absent or the connection refuses, `load()` throws and the JS -//! wrapper falls into no-op mode. - -// The napi boundary forces std `String` through function signatures; clippy's -// blanket bans on disallowed types / needless-pass-by-value / missing Errors -// sections are all about pure-Rust call sites and don't apply here (JS never -// reads rustdoc). `disallowed_macros` is allowed because `napi-derive` expands -// to `std::format!` inside `check_status!`, and the macro output isn't ours -// to rewrite. -#![expect( - clippy::disallowed_macros, - clippy::disallowed_types, - clippy::missing_errors_doc, - clippy::needless_pass_by_value, - reason = "napi bindings require owned std String + std::format! at the JS boundary" -)] -use std::{collections::HashMap, ffi::OsStr}; - -use napi::{Either, Error, Result, bindgen_prelude::Undefined}; -use napi_derive::napi; -use vite_task_client::{Client, GetEnvsQuery}; - -/// Options for [`RunnerClient::get_env`] and [`RunnerClient::get_envs`]. -/// -/// Modeled as a JS plain object rather than a positional boolean so future -/// knobs (e.g. a `default` value) can be added without an ABI break on the -/// JS wrapper side. -/// -/// Every field is optional so the napi addon — the cross-version API -/// stability boundary between the runner-shipped `.node` and the -/// separately-npm-published JS wrapper — can fill in defaults and let old -/// wrappers keep working against new runners (and vice versa). -#[napi(object)] -pub struct GetEnvOptions { - /// Whether the runner should record this env as a cache-key dependency. - /// Defaults to `true`. - pub tracked: Option, -} - -#[napi(object)] -pub struct GetEnvsPrefixQuery { - pub prefix: String, -} - -/// Handle returned by [`load`]. Holds the IPC connection and exposes the -/// runner-side operations as instance methods. -#[napi] -pub struct RunnerClient { - client: Client, -} - -#[napi] -impl RunnerClient { - #[napi] - pub fn ignore_input(&self, path: String) -> Result<()> { - self.client - .ignore_input(OsStr::new(&path)) - .map_err(|err| err_string(vite_str::format!("{err}"))) - } - - #[napi] - pub fn ignore_output(&self, path: String) -> Result<()> { - self.client - .ignore_output(OsStr::new(&path)) - .map_err(|err| err_string(vite_str::format!("{err}"))) - } - - #[napi] - pub fn disable_cache(&self) -> Result<()> { - self.client.disable_cache().map_err(|err| err_string(vite_str::format!("{err}"))) - } - - #[napi] - pub fn get_env( - &self, - name: String, - options: Option, - ) -> Result> { - let tracked = options.and_then(|o| o.tracked).unwrap_or(true); - let value = self - .client - .get_env(OsStr::new(&name), tracked) - .map_err(|err| err_string(vite_str::format!("{err}")))?; - let value = value - .map(|value| { - value.to_str().map(str::to_owned).ok_or_else(|| { - err_string(vite_str::format!("env value for {name} is not valid UTF-8")) - }) - }) - .transpose()?; - Ok(value.into()) - } - - #[napi] - pub fn get_envs( - &self, - query: Either, - options: Option, - ) -> Result> { - let tracked = options.and_then(|o| o.tracked).unwrap_or(true); - let matches = match &query { - Either::A(pattern) => { - self.client.get_envs(GetEnvsQuery::Glob(pattern.as_str()), tracked) - } - Either::B(prefix) => { - self.client.get_envs(GetEnvsQuery::Prefix(&prefix.prefix), tracked) - } - } - .map_err(|err| err_string(vite_str::format!("{err}")))?; - let mut result = HashMap::with_capacity(matches.len()); - for (name, value) in matches { - let name = name.to_str().ok_or_else(|| { - err_static("env name matched by getEnvs query is not valid UTF-8") - })?; - let value = value.to_str().ok_or_else(|| { - err_string(vite_str::format!("env value for {name} is not valid UTF-8")) - })?; - result.insert(name.to_owned(), value.to_owned()); - } - Ok(result) - } -} - -/// Connect to the runner and return a [`RunnerClient`]. Throws when the -/// IPC env is missing or the connection fails. -#[napi] -pub fn load() -> Result { - #[expect( - clippy::disallowed_methods, - reason = "client bootstrap reads the live process env to find runner IPC handoff" - )] - let client = Client::from_envs(std::env::vars_os()) - .map_err(|err| { - err_string(vite_str::format!("vp run client: failed to connect to runner IPC: {err}")) - })? - .ok_or_else(|| { - err_static( - "vp run client: runner IPC env is not set; this module is only usable \ - inside a `vp run` task", - ) - })?; - Ok(RunnerClient { client }) -} - -fn err_static(msg: &'static str) -> Error { - Error::from_reason(msg) -} - -fn err_string(msg: vite_str::Str) -> Error { - Error::from_reason(msg.as_str()) -} diff --git a/crates/vite_task_graph/Cargo.toml b/crates/vite_task_graph/Cargo.toml deleted file mode 100644 index 5d18848ab..000000000 --- a/crates/vite_task_graph/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "vite_task_graph" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -anyhow = { workspace = true } -async-trait = { workspace = true } -wincode = { workspace = true, features = ["derive"] } -monostate = { workspace = true } -petgraph = { workspace = true } -rustc-hash = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -thiserror = { workspace = true } -tracing = { workspace = true } -vec1 = { workspace = true, features = ["serde"] } -vite_path = { workspace = true } -vite_str = { workspace = true } -vite_workspace = { workspace = true } -wax = { workspace = true } - -[dev-dependencies] -pretty_assertions = { workspace = true } -ts-rs = { workspace = true, features = ["no-serde-warnings"] } -vite_path = { workspace = true, features = ["ts-rs"] } -vite_str = { workspace = true, features = ["ts-rs"] } - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/vite_task_graph/README.md b/crates/vite_task_graph/README.md deleted file mode 100644 index afd404194..000000000 --- a/crates/vite_task_graph/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# vite_task_graph - -Crate for building task graphs based on package graphs and task configurations. diff --git a/crates/vite_task_graph/run-config.ts b/crates/vite_task_graph/run-config.ts deleted file mode 100644 index cd780fce5..000000000 --- a/crates/vite_task_graph/run-config.ts +++ /dev/null @@ -1,143 +0,0 @@ -// This file is auto-generated by `cargo test`. Do not edit manually. - -export type AutoTracking = { -/** - * Enable automatic file tracking for this input or output list. - */ -auto: boolean, }; - -export type Command = string | Array; - -export type DependencyType = "dependencies" | "devDependencies" | "peerDependencies"; - -export type DependsOnEntry = string | UserPackageDependency; - -export type DependsOnFrom = DependencyType | Array; - -export type GlobWithBase = { -/** - * The glob pattern (positive or negative starting with `!`) - */ -pattern: string, -/** - * The base directory for resolving the pattern - */ -base: InputBase, }; - -export type InputBase = "package" | "workspace"; - -export type Task = { -/** - * Command to run, or an array of commands to run in order. - */ -command: Command, -/** - * The working directory for the task, relative to the package root (not workspace root). - */ -cwd?: string, -/** - * Tasks that must run before this task. - * - * - A string runs one named task, such as `"build"` in the same package or - * `"package-name#build"` in another package. - * - An object runs a task in direct workspace dependency packages selected - * from package.json fields. For example, - * `{ "task": "build", "from": "dependencies" }` runs `build` in each - * direct workspace dependency that defines a `build` task. - */ -dependsOn?: Array, } & ({ -/** - * Whether to cache the task - */ -cache?: true, -/** - * Environment variable names to be fingerprinted and passed to the task. - */ -env?: Array, -/** - * Environment variable names to be passed to the task without fingerprinting. - */ -untrackedEnv?: Array, -/** - * Files to include in the cache fingerprint. - * - * - Omitted: automatically tracks which files the task reads - * - `[]` (empty): disables file tracking entirely - * - Glob patterns (e.g. `"src/**"`) select specific files, relative to the package directory - * - `{pattern: "...", base: "workspace" | "package"}` specifies a glob with an explicit base directory - * - `{auto: true}` enables automatic file tracking - * - Negative patterns (e.g. `"!dist/**"`) exclude matched files - */ -input?: Array, -/** - * Output files to archive and restore on cache hit. - * - * - Omitted: automatically tracks which files the task writes - * - `[]` (empty): disables output restoration entirely - * - Glob patterns (e.g. `"dist/**"`) select specific output files, relative to the package directory - * - `{pattern: "...", base: "workspace" | "package"}` specifies a glob with an explicit base directory - * - `{auto: true}` enables automatic output tracking - * - Negative patterns (e.g. `"!dist/cache/**"`) exclude matched files - */ -output?: Array, } | { -/** - * Whether to cache the task - */ -cache: false, }); - -export type TaskDefinition = Task | Command; - -export type UserGlobalCacheConfig = boolean | { -/** - * Enable caching for package.json scripts not defined in the `tasks` map. - * - * When `false`, package.json scripts will not be cached. - * When `true`, package.json scripts will be cached with default settings. - * - * Default: `false` - */ -scripts?: boolean, -/** - * Global cache kill switch for task entries. - * - * When `false`, overrides all tasks to disable caching, even tasks with `cache: true`. - * When `true`, respects each task's individual `cache` setting - * (each task's `cache` defaults to `true` if omitted). - * - * Default: `true` - */ -tasks?: boolean, }; - -export type UserPackageDependency = { -/** - * Task name to run in dependency packages. - */ -task: string, -/** - * Package.json dependency field or fields to use when selecting direct dependency packages. - */ -from: DependsOnFrom, }; - -export type RunConfig = { -/** - * Root-level cache configuration. - * - * This option can only be set in the workspace root's config file. - * Setting it in a package's config will result in an error. - */ -cache?: UserGlobalCacheConfig, -/** - * Task definitions: full task objects, command strings, or command string arrays. - */ -tasks?: { [key in string]: TaskDefinition }, -/** - * Whether to automatically run `preX`/`postX` package.json scripts as - * lifecycle hooks when script `X` is executed. - * - * When `true` (the default), running script `test` will automatically - * run `pretest` before and `posttest` after, if they exist. - * - * This option can only be set in the workspace root's config file. - * Setting it in a package's config will result in an error. - */ -enablePrePostScripts?: boolean, }; diff --git a/crates/vite_task_graph/src/config/mod.rs b/crates/vite_task_graph/src/config/mod.rs deleted file mode 100644 index 61358ec5b..000000000 --- a/crates/vite_task_graph/src/config/mod.rs +++ /dev/null @@ -1,708 +0,0 @@ -pub mod user; - -use std::{collections::BTreeSet, sync::Arc}; - -use monostate::MustBe; -use rustc_hash::FxHashSet; -use serde::Serialize; -pub use user::{ - AutoTracking, Command, EnabledCacheConfig, GlobWithBase, InputBase, ResolvedGlobalCacheConfig, - UserCacheConfig, UserDependencyType, UserDependsOnEntry, UserDependsOnFrom, - UserGlobalCacheConfig, UserInputEntry, UserInputsConfig, UserOutputEntry, - UserPackageDependency, UserRunConfig, UserTaskConfig, UserTaskDefinition, -}; -use vite_path::AbsolutePath; -use vite_str::Str; -use wincode::{SchemaRead, SchemaWrite}; - -use crate::config::user::UserTaskOptions; - -/// Task configuration resolved from `package.json` scripts and/or `vite.config.ts` tasks, -/// without considering external factors like additional args from cli or environment variables. -/// -/// It should resolve as much as possible to the final form to save duplicated work when it's further resolved into a spawnable command later. -/// but must be independent of external factors. -/// -/// For example, `cwd` is resolved to absolute ones (no external factor can change it), -/// but `command` is not parsed into program and args yet because environment variables in it may need to be expanded. -/// -/// `depends_on` is not included here because it's represented by the edges of the task graph. -#[derive(Debug, Serialize)] -pub struct ResolvedTaskConfig { - /// The command or commands to run for this task. - /// - /// Commands may contain environment variables that need to be expanded later. - pub commands: Arc<[Str]>, - - pub resolved_options: ResolvedTaskOptions, -} - -#[derive(Debug, Serialize)] -pub struct ResolvedTaskOptions { - /// The working directory for the task - pub cwd: Arc, - /// Cache-related config. None means caching is disabled. - pub cache_config: Option, -} - -impl ResolvedTaskOptions { - /// Resolves from user-defined options and the directory path where the options are defined. - /// For user-defined tasks or scripts in package.json, `dir` is the package directory - /// For synthetic tasks, `dir` is the cwd of the original command (e.g. the cwd of `vp lint`). - /// - /// # Errors - /// - /// Returns [`ResolveTaskConfigError`] if a glob pattern is invalid or resolves - /// outside the workspace root. - pub fn resolve( - user_options: UserTaskOptions, - dir: &Arc, - workspace_root: &AbsolutePath, - ) -> Result { - let cwd: Arc = match user_options.cwd_relative_to_package { - Some(ref cwd) if !cwd.as_str().is_empty() => dir.join(cwd).into(), - _ => Arc::clone(dir), - }; - let cache_config = match user_options.cache_config { - UserCacheConfig::Disabled { cache: MustBe!(false) } => None, - UserCacheConfig::Enabled { cache: _, enabled_cache_config } => { - let mut untracked_env: FxHashSet = - enabled_cache_config.untracked_env.unwrap_or_default().into_iter().collect(); - untracked_env.extend(DEFAULT_UNTRACKED_ENV.iter().copied().map(Str::from)); - - let input_config = ResolvedGlobConfig::from_user_config( - enabled_cache_config.input.as_ref(), - dir, - workspace_root, - )?; - - let output_config = ResolvedGlobConfig::from_user_output_config( - enabled_cache_config.output.as_ref(), - dir, - workspace_root, - )?; - - Some(CacheConfig { - env_config: EnvConfig { - fingerprinted_envs: enabled_cache_config - .env - .map(|e| e.into_vec().into_iter().collect()) - .unwrap_or_default(), - untracked_env, - }, - input_config, - output_config, - }) - } - }; - Ok(Self { cwd, cache_config }) - } -} - -#[derive(Debug, Clone, Serialize)] -#[expect( - clippy::struct_field_names, - reason = "env_config, input_config, output_config are distinct config categories, not a naming smell" -)] -pub struct CacheConfig { - pub env_config: EnvConfig, - pub input_config: ResolvedGlobConfig, - pub output_config: ResolvedGlobConfig, -} - -/// Resolved input configuration for cache fingerprinting. -/// -/// This is the normalized form after parsing user config. -/// - `includes_auto`: Whether automatic file tracking is enabled -/// - `positive_globs`: Glob patterns for files to include (without `!` prefix) -/// - `negative_globs`: Glob patterns for files to exclude (without `!` prefix) -#[derive(Debug, Clone, PartialEq, Eq, Serialize, SchemaWrite, SchemaRead)] -pub struct ResolvedGlobConfig { - /// Whether automatic file tracking is enabled - pub includes_auto: bool, - - /// Positive glob patterns (files to include), relative to the workspace root. - /// Sorted for deterministic cache keys. - pub positive_globs: BTreeSet, - - /// Negative glob patterns (files to exclude, without the `!` prefix), relative to the workspace root. - /// Sorted for deterministic cache keys. - pub negative_globs: BTreeSet, -} - -impl ResolvedGlobConfig { - /// Default configuration: auto-inference enabled, no explicit patterns - #[must_use] - pub const fn default_auto() -> Self { - Self { - includes_auto: true, - positive_globs: BTreeSet::new(), - negative_globs: BTreeSet::new(), - } - } - - /// Resolve from user configuration, making glob patterns workspace-root-relative. - /// - /// - `None`: defaults to auto-inference (`[{auto: true}]`) - /// - `Some([])`: no inputs, inference disabled - /// - `Some([...])`: explicit patterns resolved to workspace-root-relative - /// - /// # Errors - /// - /// Returns [`ResolveTaskConfigError`] if a glob pattern is invalid or resolves - /// outside the workspace root. - pub fn from_user_config( - user_inputs: Option<&UserInputsConfig>, - package_dir: &AbsolutePath, - workspace_root: &AbsolutePath, - ) -> Result { - let Some(entries) = user_inputs else { - // None means default to auto-inference - return Ok(Self::default_auto()); - }; - - let mut includes_auto = false; - let mut positive_globs = BTreeSet::new(); - let mut negative_globs = BTreeSet::new(); - - for entry in entries { - match entry { - UserInputEntry::Auto(AutoTracking { auto: true }) => includes_auto = true, - UserInputEntry::Auto(AutoTracking { auto: false }) => {} // Ignore {auto: false} - UserInputEntry::Glob(pattern) => { - Self::insert_glob( - pattern.as_str(), - package_dir, - workspace_root, - &mut positive_globs, - &mut negative_globs, - )?; - } - UserInputEntry::GlobWithBase(GlobWithBase { pattern, base }) => { - let base_dir = match base { - InputBase::Package => package_dir, - InputBase::Workspace => workspace_root, - }; - Self::insert_glob( - pattern.as_str(), - base_dir, - workspace_root, - &mut positive_globs, - &mut negative_globs, - )?; - } - } - } - - Ok(Self { includes_auto, positive_globs, negative_globs }) - } - - /// Resolve from user output configuration, making glob patterns workspace-root-relative. - /// - /// Unlike [`Self::from_user_config`], `None` defaults to automatic output - /// tracking while `Some([])` disables output archiving. - /// - /// # Errors - /// - /// Returns [`ResolveTaskConfigError`] if a glob pattern is invalid or resolves - /// outside the workspace root. - pub fn from_user_output_config( - user_outputs: Option<&Vec>, - package_dir: &AbsolutePath, - workspace_root: &AbsolutePath, - ) -> Result { - let mut positive_globs = BTreeSet::new(); - let mut negative_globs = BTreeSet::new(); - let mut includes_auto = false; - - let Some(entries) = user_outputs else { - return Ok(Self::default_auto()); - }; - - for entry in entries { - match entry { - UserOutputEntry::Glob(pattern) => { - Self::insert_glob( - pattern.as_str(), - package_dir, - workspace_root, - &mut positive_globs, - &mut negative_globs, - )?; - } - UserOutputEntry::GlobWithBase(GlobWithBase { pattern, base }) => { - let base_dir = match base { - InputBase::Package => package_dir, - InputBase::Workspace => workspace_root, - }; - Self::insert_glob( - pattern.as_str(), - base_dir, - workspace_root, - &mut positive_globs, - &mut negative_globs, - )?; - } - UserOutputEntry::Auto(AutoTracking { auto: true }) => includes_auto = true, - UserOutputEntry::Auto(AutoTracking { auto: false }) => {} - } - } - - Ok(Self { includes_auto, positive_globs, negative_globs }) - } - - /// Insert a glob pattern into the appropriate set (positive or negative), - /// resolving it relative to the given base directory. - fn insert_glob( - pattern: &str, - base_dir: &AbsolutePath, - workspace_root: &AbsolutePath, - positive_globs: &mut BTreeSet, - negative_globs: &mut BTreeSet, - ) -> Result<(), ResolveTaskConfigError> { - if let Some(negated) = pattern.strip_prefix('!') { - let resolved = resolve_glob_to_workspace_relative(negated, base_dir, workspace_root)?; - negative_globs.insert(resolved); - } else { - let resolved = resolve_glob_to_workspace_relative(pattern, base_dir, workspace_root)?; - positive_globs.insert(resolved); - } - Ok(()) - } -} - -/// Resolve a single glob pattern to be workspace-root-relative. -/// -/// The algorithm: -/// 1. Partition the glob into an invariant prefix and a variant part -/// 2. Join the invariant prefix with `package_dir` and clean the path -/// 3. Strip the `workspace_root` prefix from the cleaned path -/// 4. Re-escape the stripped prefix and rejoin with the variant -fn resolve_glob_to_workspace_relative( - pattern: &str, - package_dir: &AbsolutePath, - workspace_root: &AbsolutePath, -) -> Result { - // A trailing `/` is shorthand for all files under that directory - let expanded: Str; - let pattern = if pattern.ends_with('/') { - expanded = vite_str::format!("{pattern}**"); - expanded.as_str() - } else { - pattern - }; - - let glob = wax::Glob::new(pattern).map_err(|source| ResolveTaskConfigError::InvalidGlob { - pattern: Str::from(pattern), - source: Box::new(source), - })?; - let (invariant_prefix, variant) = glob.partition(); - - let joined = package_dir.join(&invariant_prefix).clean(); - let stripped = joined.strip_prefix(workspace_root).map_err(|_| { - ResolveTaskConfigError::GlobOutsideWorkspace { pattern: Str::from(pattern) } - })?; - - // Re-escape the prefix path for use in a glob pattern - let stripped = stripped.ok_or_else(|| ResolveTaskConfigError::GlobOutsideWorkspace { - pattern: Str::from(pattern), - })?; - - let escaped_prefix = wax::escape(stripped.as_str()); - - let result = match variant { - Some(variant_glob) if escaped_prefix.is_empty() => { - Str::from(variant_glob.to_string().as_str()) - } - Some(variant_glob) => vite_str::format!("{escaped_prefix}/{variant_glob}"), - None if escaped_prefix.is_empty() => Str::from("**"), - None => Str::from(escaped_prefix.as_ref()), - }; - - Ok(result) -} - -#[derive(Debug, Clone, Serialize)] -pub struct EnvConfig { - /// environment variable names to be fingerprinted and passed to the task, with defaults populated - pub fingerprinted_envs: FxHashSet, - /// environment variable names to be passed to the task without fingerprinting, with defaults populated - pub untracked_env: FxHashSet, -} - -#[derive(Debug, thiserror::Error)] -pub enum ResolveTaskConfigError { - /// A glob pattern resolves to a path outside the workspace root - #[error("glob pattern '{pattern}' resolves outside the workspace root")] - GlobOutsideWorkspace { pattern: Str }, - - /// A glob pattern is invalid - #[error("invalid glob pattern '{pattern}'")] - InvalidGlob { - pattern: Str, - #[source] - source: Box, - }, -} - -impl ResolvedTaskConfig { - /// Resolve from package.json script only (no config entry for this task). - /// - /// Always resolves with caching enabled (default settings). - /// The global cache config is applied at plan time, not here. - /// - /// # Errors - /// - /// Returns [`ResolveTaskConfigError`] if glob resolution fails. - pub fn resolve_package_json_script( - package_dir: &Arc, - package_json_script: &str, - workspace_root: &AbsolutePath, - ) -> Result { - Ok(Self { - commands: vec![package_json_script.into()].into(), - resolved_options: ResolvedTaskOptions::resolve( - UserTaskOptions::default(), - package_dir, - workspace_root, - )?, - }) - } - - /// Resolves from user config and package dir. - /// - /// # Errors - /// - /// Returns [`ResolveTaskConfigError`] if glob resolution fails. - pub fn resolve( - user_config: UserTaskConfig, - package_dir: &Arc, - workspace_root: &AbsolutePath, - ) -> Result { - let UserTaskConfig { command, options } = user_config; - let commands = match command { - Command::Single(command) => Arc::from([command]), - Command::Array(commands) => commands, - }; - Ok(Self { - commands, - resolved_options: ResolvedTaskOptions::resolve(options, package_dir, workspace_root)?, - }) - } -} - -// Exact matches for common environment variables -// Referenced from Turborepo's implementation: -// https://github.com/vercel/turborepo/blob/06ba8e2f7b8d7c7ff99edff7114e2584713e18c4/crates/turborepo-env/src/lib.rs#L20 -#[doc(hidden)] // exported for redacting snapshots in tests -pub const DEFAULT_UNTRACKED_ENV: &[&str] = &[ - // System and shell - "HOME", - "USER", - "TZ", - "LANG", - "SHELL", - "PWD", - "PATH", - // Linux/X11 session - "XDG_RUNTIME_DIR", - "XAUTHORITY", - "DBUS_SESSION_BUS_ADDRESS", - // CI/CD environments - "CI", - // Node.js specific - "NODE_OPTIONS", - "COREPACK_*", - "NPM_CONFIG_STORE_DIR", - "PNPM_HOME", - // Library paths - "LD_LIBRARY_PATH", - "LD_PRELOAD", - "DYLD_FALLBACK_LIBRARY_PATH", - "DYLD_INSERT_LIBRARIES", - "LIBPATH", - // Terminal/display - // - // No color-related vars are included by default. The planner ensures - // `FORCE_COLOR=1` is set on the child after env resolution (as a fallback - // when neither the parent env nor task config provides one), so cached - // output is always colored. The reporter strips colors at the writer - // level when the user's terminal cannot render them. Users wanting to - // pass through `NO_COLOR`, `COLORTERM`, `TERM`, `TERM_PROGRAM`, or - // override `FORCE_COLOR` can opt in via a task's `env`/`untrackedEnv`. - "DISPLAY", - // Temporary directories - "TMP", - "TEMP", - // Vercel specific - "VERCEL", - "VERCEL_*", - "NEXT_*", - "USE_OUTPUT_FOR_EDGE_FUNCTIONS", - "NOW_BUILDER", - "VC_MICROFRONTENDS_CONFIG_FILE_NAME", - // GitHub Actions - "GITHUB_*", - "RUNNER_*", - // Windows specific - "APPDATA", - // Node's compile cache uses LOCALAPPDATA to pick its cache directory - // on Windows. Without it the cache lands inside the workspace and - // breaks every cached task that opts into the compile cache. See - // the `node_compile_cache_outside_workspace` e2e fixture. - "LOCALAPPDATA", - "PROGRAMDATA", - "SYSTEMROOT", - "SYSTEMDRIVE", - "USERPROFILE", - "HOMEDRIVE", - "HOMEPATH", - "WINDIR", - "PATHEXT", - "ProgramFiles", - "ProgramFiles[(]x86[)]", // Parens escaped for glob syntax (Turborepo uses literal `ProgramFiles(x86)`) - // IDE specific (exact matches) - "ELECTRON_RUN_AS_NODE", - "JB_INTERPRETER", - "_JETBRAINS_TEST_RUNNER_RUN_SCOPE_TYPE", - "JB_IDE_*", - // VSCode specific - "VSCODE_*", - // Docker specific - "DOCKER_*", - "BUILDKIT_*", - "COMPOSE_*", - // Playwright specific - "PLAYWRIGHT_*", - // Vite+ internal (not fingerprinted — internal state, not build-affecting) - "VP_*", - // Token patterns - "*_TOKEN", -]; - -#[cfg(test)] -mod tests { - use vite_path::AbsolutePathBuf; - - use super::*; - - fn test_paths() -> (AbsolutePathBuf, AbsolutePathBuf) { - if cfg!(windows) { - ( - AbsolutePathBuf::new("C:\\workspace\\packages\\my-pkg".into()).unwrap(), - AbsolutePathBuf::new("C:\\workspace".into()).unwrap(), - ) - } else { - ( - AbsolutePathBuf::new("/workspace/packages/my-pkg".into()).unwrap(), - AbsolutePathBuf::new("/workspace".into()).unwrap(), - ) - } - } - - #[test] - fn test_resolved_input_config_default_auto() { - let config = ResolvedGlobConfig::default_auto(); - assert!(config.includes_auto); - assert!(config.positive_globs.is_empty()); - assert!(config.negative_globs.is_empty()); - } - - #[test] - fn test_resolved_input_config_from_none() { - let (pkg, ws) = test_paths(); - // None means default to auto-inference - let config = ResolvedGlobConfig::from_user_config(None, &pkg, &ws).unwrap(); - assert!(config.includes_auto); - assert!(config.positive_globs.is_empty()); - assert!(config.negative_globs.is_empty()); - } - - #[test] - fn test_resolved_input_config_empty_array() { - let (pkg, ws) = test_paths(); - // Empty array means no inputs, inference disabled - let user_inputs = vec![]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert!(!config.includes_auto); - assert!(config.positive_globs.is_empty()); - assert!(config.negative_globs.is_empty()); - } - - #[test] - fn test_resolved_input_config_auto_only() { - let (pkg, ws) = test_paths(); - let user_inputs = vec![UserInputEntry::Auto(AutoTracking { auto: true })]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert!(config.includes_auto); - assert!(config.positive_globs.is_empty()); - assert!(config.negative_globs.is_empty()); - } - - #[test] - fn test_resolved_input_config_auto_false_ignored() { - let (pkg, ws) = test_paths(); - let user_inputs = vec![UserInputEntry::Auto(AutoTracking { auto: false })]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert!(!config.includes_auto); - assert!(config.positive_globs.is_empty()); - assert!(config.negative_globs.is_empty()); - } - - #[test] - fn test_resolved_input_config_globs_only() { - let (pkg, ws) = test_paths(); - // Globs without auto means inference disabled - let user_inputs = vec![ - UserInputEntry::Glob("src/**/*.ts".into()), - UserInputEntry::Glob("package.json".into()), - ]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert!(!config.includes_auto); - assert_eq!(config.positive_globs.len(), 2); - // Globs should now be workspace-root-relative - assert!(config.positive_globs.contains("packages/my-pkg/src/**/*.ts")); - assert!(config.positive_globs.contains("packages/my-pkg/package.json")); - assert!(config.negative_globs.is_empty()); - } - - #[test] - fn test_resolved_input_config_negative_globs() { - let (pkg, ws) = test_paths(); - let user_inputs = vec![ - UserInputEntry::Glob("src/**".into()), - UserInputEntry::Glob("!src/**/*.test.ts".into()), - ]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert!(!config.includes_auto); - assert_eq!(config.positive_globs.len(), 1); - assert!(config.positive_globs.contains("packages/my-pkg/src/**")); - assert_eq!(config.negative_globs.len(), 1); - assert!(config.negative_globs.contains("packages/my-pkg/src/**/*.test.ts")); - } - - #[test] - fn test_resolved_input_config_mixed() { - let (pkg, ws) = test_paths(); - let user_inputs = vec![ - UserInputEntry::Glob("package.json".into()), - UserInputEntry::Auto(AutoTracking { auto: true }), - UserInputEntry::Glob("!node_modules/**".into()), - ]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert!(config.includes_auto); - assert_eq!(config.positive_globs.len(), 1); - assert!(config.positive_globs.contains("packages/my-pkg/package.json")); - assert_eq!(config.negative_globs.len(), 1); - assert!(config.negative_globs.contains("packages/my-pkg/node_modules/**")); - } - - #[test] - fn test_resolved_input_config_globs_with_auto() { - let (pkg, ws) = test_paths(); - // Globs with auto keeps inference enabled - let user_inputs = vec![ - UserInputEntry::Glob("src/**/*.ts".into()), - UserInputEntry::Auto(AutoTracking { auto: true }), - ]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert!(config.includes_auto); - } - - #[test] - fn test_resolved_input_config_dotdot_resolution() { - let (pkg, ws) = test_paths(); - let user_inputs = vec![UserInputEntry::Glob("../shared/src/**".into())]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert_eq!(config.positive_globs.len(), 1); - assert!( - config.positive_globs.contains("packages/shared/src/**"), - "expected 'packages/shared/src/**', got {:?}", - config.positive_globs - ); - } - - #[test] - fn test_resolved_input_config_outside_workspace_error() { - let (pkg, ws) = test_paths(); - let user_inputs = vec![UserInputEntry::Glob("../../../outside/**".into())]; - let result = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws); - assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), ResolveTaskConfigError::GlobOutsideWorkspace { .. })); - } - - #[test] - fn test_resolved_input_config_glob_with_workspace_base() { - let (pkg, ws) = test_paths(); - let user_inputs = vec![UserInputEntry::GlobWithBase(GlobWithBase { - pattern: "configs/tsconfig.json".into(), - base: InputBase::Workspace, - })]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert!(!config.includes_auto); - assert_eq!(config.positive_globs.len(), 1); - // Workspace-base: should NOT have the package prefix - assert!( - config.positive_globs.contains("configs/tsconfig.json"), - "expected 'configs/tsconfig.json', got {:?}", - config.positive_globs - ); - } - - #[test] - fn test_resolved_input_config_negative_glob_with_workspace_base() { - let (pkg, ws) = test_paths(); - let user_inputs = vec![UserInputEntry::GlobWithBase(GlobWithBase { - pattern: "!dist/**".into(), - base: InputBase::Workspace, - })]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert_eq!(config.negative_globs.len(), 1); - assert!( - config.negative_globs.contains("dist/**"), - "expected 'dist/**', got {:?}", - config.negative_globs - ); - } - - #[test] - fn test_resolved_input_config_glob_with_package_base_explicit() { - let (pkg, ws) = test_paths(); - // Explicit "package" base should behave same as bare string - let user_inputs = vec![UserInputEntry::GlobWithBase(GlobWithBase { - pattern: "src/**/*.ts".into(), - base: InputBase::Package, - })]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert_eq!(config.positive_globs.len(), 1); - assert!( - config.positive_globs.contains("packages/my-pkg/src/**/*.ts"), - "expected 'packages/my-pkg/src/**/*.ts', got {:?}", - config.positive_globs - ); - } - - #[test] - fn test_resolved_input_config_mixed_bases() { - let (pkg, ws) = test_paths(); - let user_inputs = vec![ - UserInputEntry::Glob("src/**".into()), - UserInputEntry::GlobWithBase(GlobWithBase { - pattern: "configs/**".into(), - base: InputBase::Workspace, - }), - UserInputEntry::Auto(AutoTracking { auto: true }), - UserInputEntry::GlobWithBase(GlobWithBase { - pattern: "!dist/**".into(), - base: InputBase::Workspace, - }), - ]; - let config = ResolvedGlobConfig::from_user_config(Some(&user_inputs), &pkg, &ws).unwrap(); - assert!(config.includes_auto); - assert_eq!(config.positive_globs.len(), 2); - assert!(config.positive_globs.contains("packages/my-pkg/src/**")); - assert!(config.positive_globs.contains("configs/**")); - assert_eq!(config.negative_globs.len(), 1); - assert!(config.negative_globs.contains("dist/**")); - } -} diff --git a/crates/vite_task_graph/src/config/user.rs b/crates/vite_task_graph/src/config/user.rs deleted file mode 100644 index 487ccec2a..000000000 --- a/crates/vite_task_graph/src/config/user.rs +++ /dev/null @@ -1,984 +0,0 @@ -//! Configuration structures for user-defined tasks in `vite.config.*` - -use std::sync::Arc; - -use monostate::MustBe; -use rustc_hash::FxHashMap; -use serde::Deserialize; -#[cfg(all(test, not(clippy)))] -use ts_rs::TS; -use vec1::Vec1; -use vite_path::RelativePathBuf; -use vite_str::Str; - -/// The base directory for resolving a glob pattern. -#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] -#[cfg_attr(all(test, not(clippy)), derive(TS))] -#[serde(rename_all = "lowercase")] -pub enum InputBase { - /// Resolve relative to the package directory (where `package.json` is located) - Package, - /// Resolve relative to the workspace root - Workspace, -} - -/// Glob pattern with explicit base directory for resolution. -#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] -#[cfg_attr(all(test, not(clippy)), derive(TS))] -#[serde(deny_unknown_fields)] -pub struct GlobWithBase { - /// The glob pattern (positive or negative starting with `!`) - pub pattern: Str, - /// The base directory for resolving the pattern - pub base: InputBase, -} - -/// Automatic file-tracking directive for input fingerprinting or output archiving. -#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] -#[cfg_attr(all(test, not(clippy)), derive(TS))] -#[serde(deny_unknown_fields)] -pub struct AutoTracking { - /// Enable automatic file tracking for this input or output list. - pub auto: bool, -} - -/// A single input entry in the `input` array. -/// -/// Inputs can be: -/// - Glob patterns as strings (resolved relative to the package directory) -/// - Object form with explicit base: `{ "pattern": "...", "base": "workspace" | "package" }` -/// - Automatic tracking directives: `{ "auto": true }` -#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS))] -#[serde(untagged)] -pub enum UserInputEntry { - /// Glob pattern (positive or negative starting with `!`), resolved relative to package dir - Glob(Str), - /// Glob pattern with explicit base directory - GlobWithBase(GlobWithBase), - /// Automatic tracking directive - Auto(AutoTracking), -} - -/// The inputs configuration for cache fingerprinting. -/// -/// Default (when field omitted): `[{auto: true}]` - infer from file accesses. -pub type UserInputsConfig = Vec; - -/// A supported package.json dependency field for package dependency selection. -#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS), ts(rename = "DependencyType"))] -#[serde(rename_all = "camelCase")] -pub enum UserDependencyType { - /// Traverse dependencies declared in the package.json `dependencies` field. - Dependencies, - /// Traverse dependencies declared in the package.json `devDependencies` field. - DevDependencies, - /// Traverse dependencies declared in the package.json `peerDependencies` field. - PeerDependencies, -} - -/// The `from` selector for object-form `dependsOn` entries. -#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS), ts(rename = "DependsOnFrom"))] -#[serde(untagged)] -pub enum UserDependsOnFrom { - /// Select one package.json dependency field. - Single(UserDependencyType), - /// Select the union of multiple package.json dependency fields. - Multiple( - #[cfg_attr(all(test, not(clippy)), ts(as = "Vec"))] - Vec1, - ), -} - -impl UserDependsOnFrom { - #[must_use] - pub fn as_slice(&self) -> &[UserDependencyType] { - match self { - Self::Single(dependency_type) => std::slice::from_ref(dependency_type), - Self::Multiple(dependency_types) => dependency_types, - } - } -} - -/// Object form for `dependsOn` entries that select direct workspace package dependencies. -#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS))] -#[serde(deny_unknown_fields)] -pub struct UserPackageDependency { - /// Task name to run in dependency packages. - pub task: Str, - - /// Package.json dependency field or fields to use when selecting direct dependency packages. - pub from: UserDependsOnFrom, -} - -/// A single `dependsOn` entry. -#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS), ts(rename = "DependsOnEntry"))] -#[serde(untagged)] -pub enum UserDependsOnEntry { - /// Same-package task or `package#task` specifier. - Task(Str), - /// Direct package dependency selection entry. - Package(UserPackageDependency), -} - -/// A single output entry in the `output` array. -/// -/// Outputs can be: -/// - Glob patterns as strings (resolved relative to the package directory) -/// - Object form with explicit base: `{ "pattern": "...", "base": "workspace" | "package" }` -/// - Automatic tracking directive: `{ "auto": true }` -#[derive(Debug, Deserialize, PartialEq, Eq, Clone)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS))] -#[serde(untagged)] -pub enum UserOutputEntry { - /// Glob pattern (positive or negative starting with `!`), resolved relative to package dir - Glob(Str), - /// Glob pattern with explicit base directory - GlobWithBase(GlobWithBase), - /// Automatic tracking directive - Auto(AutoTracking), -} - -/// Cache-related fields of a task defined by user in `vite.config.*` -#[derive(Debug, Deserialize, PartialEq, Eq)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS), ts(optional_fields))] -#[serde(untagged, deny_unknown_fields, rename_all = "camelCase")] -pub enum UserCacheConfig { - /// Cache is enabled - Enabled { - /// Whether to cache the task - #[cfg_attr(all(test, not(clippy)), ts(type = "true", optional))] - cache: Option, - - #[serde(flatten)] - enabled_cache_config: EnabledCacheConfig, - }, - /// Cache is disabled - Disabled { - /// Whether to cache the task - #[cfg_attr(all(test, not(clippy)), ts(type = "false"))] - cache: MustBe!(false), - }, -} - -impl UserCacheConfig { - /// Create an enabled cache config with the given `EnabledCacheConfig`. - #[must_use] - pub const fn with_config(config: EnabledCacheConfig) -> Self { - Self::Enabled { cache: Some(MustBe!(true)), enabled_cache_config: config } - } - - /// Create a disabled cache config. - #[must_use] - pub const fn disabled() -> Self { - Self::Disabled { cache: MustBe!(false) } - } -} - -/// Cache configuration fields when caching is enabled -#[derive(Debug, Deserialize, PartialEq, Eq)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS), ts(optional_fields))] -#[serde(rename_all = "camelCase")] -pub struct EnabledCacheConfig { - /// Environment variable names to be fingerprinted and passed to the task. - pub env: Option>, - - /// Environment variable names to be passed to the task without fingerprinting. - pub untracked_env: Option>, - - /// Files to include in the cache fingerprint. - /// - /// - Omitted: automatically tracks which files the task reads - /// - `[]` (empty): disables file tracking entirely - /// - Glob patterns (e.g. `"src/**"`) select specific files, relative to the package directory - /// - `{pattern: "...", base: "workspace" | "package"}` specifies a glob with an explicit base directory - /// - `{auto: true}` enables automatic file tracking - /// - Negative patterns (e.g. `"!dist/**"`) exclude matched files - #[serde(default)] - #[cfg_attr(all(test, not(clippy)), ts(inline))] - pub input: Option, - - /// Output files to archive and restore on cache hit. - /// - /// - Omitted: automatically tracks which files the task writes - /// - `[]` (empty): disables output restoration entirely - /// - Glob patterns (e.g. `"dist/**"`) select specific output files, relative to the package directory - /// - `{pattern: "...", base: "workspace" | "package"}` specifies a glob with an explicit base directory - /// - `{auto: true}` enables automatic output tracking - /// - Negative patterns (e.g. `"!dist/cache/**"`) exclude matched files - #[serde(default)] - #[cfg_attr(all(test, not(clippy)), ts(inline))] - pub output: Option>, -} - -/// Options for user-defined tasks in `vite.config.*`, excluding the command. -#[derive(Debug, Deserialize, PartialEq, Eq)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS), ts(optional_fields))] -#[serde(rename_all = "camelCase")] -pub struct UserTaskOptions { - /// The working directory for the task, relative to the package root (not workspace root). - #[serde(rename = "cwd")] - pub cwd_relative_to_package: Option, - - /// Tasks that must run before this task. - /// - /// - A string runs one named task, such as `"build"` in the same package or - /// `"package-name#build"` in another package. - /// - An object runs a task in direct workspace dependency packages selected - /// from package.json fields. For example, - /// `{ "task": "build", "from": "dependencies" }` runs `build` in each - /// direct workspace dependency that defines a `build` task. - pub depends_on: Option>, - - /// Cache-related fields - #[serde(flatten)] - pub cache_config: UserCacheConfig, -} - -impl Default for UserTaskOptions { - /// The default user task options for package.json scripts. - fn default() -> Self { - Self { - // Runs in the package root - cwd_relative_to_package: None, - // No dependencies - depends_on: None, - // Caching enabled with no fingerprinted env - cache_config: UserCacheConfig::Enabled { - cache: None, - enabled_cache_config: EnabledCacheConfig { - env: None, - untracked_env: None, - input: None, - output: None, - }, - }, - } - } -} -/// The command to run for a task: a single string or a sequence of strings. -#[derive(Debug, Deserialize, PartialEq, Eq)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS))] -#[serde(untagged)] -pub enum Command { - /// A single command string. - Single(Str), - /// A sequence of command strings, run in order. - Array(Arc<[Str]>), -} - -/// Full user-defined task configuration in `vite.config.*`, including the command and options. -#[derive(Debug, Deserialize, PartialEq, Eq)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS), ts(optional_fields, rename = "Task"))] -#[serde(rename_all = "camelCase")] -pub struct UserTaskConfig { - /// Command to run, or an array of commands to run in order. - pub command: Command, - - /// Fields other than the command. - #[serde(flatten)] - pub options: UserTaskOptions, -} - -/// User-defined task configuration or command-only shorthand in `vite.config.*`. -#[derive(Debug, Deserialize, PartialEq, Eq)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS), ts(rename = "TaskDefinition"))] -#[serde(untagged)] -pub enum UserTaskDefinition { - /// Full task object form. - Object(UserTaskConfig), - /// Command-only shorthand form using default task options. - CommandShorthand(Command), -} - -/// Root-level cache configuration. -/// -/// Controls caching behavior for the entire workspace. -/// -/// - `true` is equivalent to `{ scripts: true, tasks: true }` — enables caching for both -/// package.json scripts and task entries. -/// - `false` is equivalent to `{ scripts: false, tasks: false }` — disables all caching. -/// - When omitted, defaults to `{ scripts: false, tasks: true }`. -/// -/// This option can only be set in the workspace root's config file. -/// Setting it in a package's config will result in an error. -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS), ts(optional_fields))] -#[serde(untagged, deny_unknown_fields)] -pub enum UserGlobalCacheConfig { - Bool(bool), - /// Detailed cache configuration with separate control for scripts and tasks. - Detailed { - /// Enable caching for package.json scripts not defined in the `tasks` map. - /// - /// When `false`, package.json scripts will not be cached. - /// When `true`, package.json scripts will be cached with default settings. - /// - /// Default: `false` - scripts: Option, - - /// Global cache kill switch for task entries. - /// - /// When `false`, overrides all tasks to disable caching, even tasks with `cache: true`. - /// When `true`, respects each task's individual `cache` setting - /// (each task's `cache` defaults to `true` if omitted). - /// - /// Default: `true` - tasks: Option, - }, -} - -/// Resolved global cache configuration with concrete boolean values. -#[derive(Debug, Clone, Copy)] -pub struct ResolvedGlobalCacheConfig { - pub scripts: bool, - pub tasks: bool, -} - -impl ResolvedGlobalCacheConfig { - /// Resolve from an optional user config, using defaults when `None`. - /// - /// Default: `{ scripts: false, tasks: true }` - #[must_use] - pub fn resolve_from(config: Option<&UserGlobalCacheConfig>) -> Self { - match config { - None => Self { scripts: false, tasks: true }, - Some(UserGlobalCacheConfig::Bool(true)) => Self { scripts: true, tasks: true }, - Some(UserGlobalCacheConfig::Bool(false)) => Self { scripts: false, tasks: false }, - Some(UserGlobalCacheConfig::Detailed { scripts, tasks }) => { - Self { scripts: scripts.unwrap_or(false), tasks: tasks.unwrap_or(true) } - } - } - } -} - -/// User configuration structure for `run` field in `vite.config.*` -#[derive(Debug, Default, Deserialize)] -// TS derive macro generates code using std types that clippy disallows; skip derive during linting -#[cfg_attr(all(test, not(clippy)), derive(TS), ts(optional_fields, rename = "RunConfig"))] -#[serde(deny_unknown_fields, rename_all = "camelCase")] -pub struct UserRunConfig { - /// Root-level cache configuration. - /// - /// This option can only be set in the workspace root's config file. - /// Setting it in a package's config will result in an error. - pub cache: Option, - - /// Task definitions: full task objects, command strings, or command string arrays. - pub tasks: Option>, - - /// Whether to automatically run `preX`/`postX` package.json scripts as - /// lifecycle hooks when script `X` is executed. - /// - /// When `true` (the default), running script `test` will automatically - /// run `pretest` before and `posttest` after, if they exist. - /// - /// This option can only be set in the workspace root's config file. - /// Setting it in a package's config will result in an error. - pub enable_pre_post_scripts: Option, -} - -impl UserRunConfig { - /// TypeScript type definitions for user run configuration. - pub const TS_TYPE: &str = include_str!("../../run-config.ts"); - - /// Generates TypeScript type definitions for user task configuration. - #[cfg(all(test, not(clippy)))] - #[must_use] - // test code: uses std types for convenience - #[expect(clippy::disallowed_types, reason = "test code uses std types for convenience")] - pub fn generate_ts_definition() -> String { - use std::{any::TypeId, collections::HashSet}; - - use ts_rs::TypeVisitor; - - struct DeclCollector { - decls: Vec, - visited: HashSet, - } - - impl TypeVisitor for DeclCollector { - fn visit(&mut self) { - if !self.visited.insert(TypeId::of::()) { - return; - } - // Only collect declarations from types that are exportable - // (i.e., have an output path - built-in types like HashMap don't) - if T::output_path().is_some() { - self.decls.push(T::decl(&ts_rs::Config::default())); - } - // Recursively visit dependencies of T - T::visit_dependencies(self); - } - } - - let mut collector = DeclCollector { decls: Vec::new(), visited: HashSet::new() }; - Self::visit_dependencies(&mut collector); - - // Sort declarations for deterministic output order - collector.decls.sort(); - - // Header - let mut types: String = - "// This file is auto-generated by `cargo test`. Do not edit manually.\n\n".into(); - - // Export all types - let dep_types: String = collector - .decls - .iter() - .map(|decl| vite_str::format!("export {decl}")) - .collect::>() - .join("\n\n"); - types.push_str(&dep_types); - - // Export the main type - types.push_str("\n\nexport "); - types.push_str(&Self::decl(&ts_rs::Config::default())); - - types.lines().map(str::trim_end).collect::>().join("\n") + "\n" - } -} - -#[cfg(all(test, not(clippy)))] -mod ts_tests { - // test code: uses std types for convenience - #[expect(clippy::disallowed_types, reason = "test code uses std types for convenience")] - use std::{env, path::PathBuf}; - - use super::UserRunConfig; - - #[test] - // test code: uses std types for convenience - #[expect( - clippy::disallowed_methods, - clippy::disallowed_types, - reason = "test code uses std types for convenience" - )] - fn typescript_generation() { - let file_path = - PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("run-config.ts"); - let ts = UserRunConfig::generate_ts_definition().replace('\r', ""); - - if env::var("VT_UPDATE_TS_TYPES").unwrap_or_default() == "1" { - std::fs::write(&file_path, ts).unwrap(); - } else { - let existing_ts = - std::fs::read_to_string(&file_path).unwrap_or_default().replace('\r', ""); - pretty_assertions::assert_eq!( - ts, - existing_ts, - "Generated TypeScript types do not match the existing ones. If you made changes to the types, please set VT_UPDATE_TS_TYPES=1 and run the tests again to update the TypeScript definitions." - ); - } - } -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - #[test] - fn test_command_required() { - let user_config_json = json!({}); - assert!( - serde_json::from_value::(user_config_json).is_err(), - "task config without command should fail to deserialize" - ); - } - - #[test] - fn test_command_with_defaults() { - let user_config_json = json!({ - "command": "echo hello" - }); - let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - user_config, - UserTaskConfig { - command: Command::Single("echo hello".into()), - options: UserTaskOptions::default() - } - ); - } - - #[test] - fn test_command_array() { - let user_config_json = json!({ - "command": ["echo one", "echo two", "echo three"] - }); - let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - user_config.command, - Command::Array(Arc::from(["echo one".into(), "echo two".into(), "echo three".into()])) - ); - assert_eq!(user_config.options, UserTaskOptions::default()); - } - - #[test] - fn test_task_string_shorthand() { - let user_config_json = json!({ - "tasks": { - "build": "echo build" - } - }); - let mut user_config: UserRunConfig = serde_json::from_value(user_config_json).unwrap(); - let task = user_config.tasks.as_mut().unwrap().remove("build").unwrap(); - assert_eq!( - task, - UserTaskDefinition::CommandShorthand(Command::Single("echo build".into())) - ); - } - - #[test] - fn test_task_array_shorthand() { - let user_config_json = json!({ - "tasks": { - "build": ["echo one", "echo two", "echo three"] - } - }); - let mut user_config: UserRunConfig = serde_json::from_value(user_config_json).unwrap(); - let task = user_config.tasks.as_mut().unwrap().remove("build").unwrap(); - assert_eq!( - task, - UserTaskDefinition::CommandShorthand(Command::Array(Arc::from([ - "echo one".into(), - "echo two".into(), - "echo three".into() - ]))) - ); - } - - #[test] - fn test_command_array_with_options() { - let user_config_json = json!({ - "command": ["echo one", "echo two"], - "cwd": "src", - "dependsOn": ["build"], - "cache": false - }); - let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - user_config.command, - Command::Array(Arc::from(["echo one".into(), "echo two".into()])) - ); - let options = user_config.options; - assert_eq!(options.cwd_relative_to_package.as_ref().unwrap().as_str(), "src"); - assert_eq!( - options.depends_on.as_ref().unwrap().as_ref(), - [UserDependsOnEntry::Task(Str::from("build"))] - ); - assert_eq!(options.cache_config, UserCacheConfig::Disabled { cache: MustBe!(false) }); - } - - #[test] - fn test_depends_on_package_dependency_single_from() { - let user_config_json = json!({ - "command": "echo test", - "dependsOn": [{ "task": "build", "from": "dependencies" }] - }); - let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - user_config.options.depends_on.as_ref().unwrap().as_ref(), - [UserDependsOnEntry::Package(UserPackageDependency { - task: "build".into(), - from: UserDependsOnFrom::Single(UserDependencyType::Dependencies), - })] - ); - } - - #[test] - fn test_depends_on_package_dependency_array_from() { - let user_config_json = json!({ - "command": "echo test", - "dependsOn": [{ - "task": "build", - "from": ["dependencies", "devDependencies", "peerDependencies"] - }] - }); - let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - user_config.options.depends_on.as_ref().unwrap().as_ref(), - [UserDependsOnEntry::Package(UserPackageDependency { - task: "build".into(), - from: UserDependsOnFrom::Multiple( - Vec1::try_from_vec(vec![ - UserDependencyType::Dependencies, - UserDependencyType::DevDependencies, - UserDependencyType::PeerDependencies, - ]) - .unwrap() - ), - })] - ); - } - - #[test] - fn test_depends_on_package_dependency_empty_from_error() { - let user_config_json = json!({ - "command": "echo test", - "dependsOn": [{ "task": "build", "from": [] }] - }); - assert!(serde_json::from_value::(user_config_json).is_err()); - } - - #[test] - fn test_depends_on_package_dependency_unknown_from_error() { - let user_config_json = json!({ - "command": "echo test", - "dependsOn": [{ "task": "build", "from": "optionalDependencies" }] - }); - assert!(serde_json::from_value::(user_config_json).is_err()); - } - - #[test] - fn test_task_invalid_shorthand_error() { - let user_config_json = json!({ - "tasks": { - "build": 123 - } - }); - assert!(serde_json::from_value::(user_config_json).is_err()); - } - - #[test] - fn test_command_array_invalid_item_error() { - let user_config_json = json!({ - "command": ["echo one", 123] - }); - assert!(serde_json::from_value::(user_config_json).is_err()); - } - - #[test] - fn test_cwd_rename() { - let user_config_json = json!({ - "command": "echo test", - "cwd": "src" - }); - let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!(user_config.options.cwd_relative_to_package.as_ref().unwrap().as_str(), "src"); - } - - #[test] - fn test_cache_disabled() { - let user_config_json = json!({ - "command": "echo test", - "cache": false - }); - let user_config: UserTaskConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - user_config.options.cache_config, - UserCacheConfig::Disabled { cache: MustBe!(false) } - ); - } - - #[test] - fn test_cache_explicitly_enabled() { - let user_config_json = json!({ - "cache": true, - "env": ["NODE_ENV"], - "untrackedEnv": ["FOO"], - }); - assert_eq!( - serde_json::from_value::(user_config_json).unwrap(), - UserCacheConfig::Enabled { - cache: Some(MustBe!(true)), - enabled_cache_config: EnabledCacheConfig { - env: Some(std::iter::once("NODE_ENV".into()).collect()), - untracked_env: Some(std::iter::once("FOO".into()).collect()), - input: None, - output: None, - } - }, - ); - } - - #[test] - fn test_input_empty_array() { - let user_config_json = json!({ - "input": [] - }); - let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!(config.input, Some(vec![])); - } - - #[test] - fn test_input_auto_true() { - let user_config_json = json!({ - "input": [{ "auto": true }] - }); - let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!(config.input, Some(vec![UserInputEntry::Auto(AutoTracking { auto: true })])); - } - - #[test] - fn test_input_auto_false() { - let user_config_json = json!({ - "input": [{ "auto": false }] - }); - let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!(config.input, Some(vec![UserInputEntry::Auto(AutoTracking { auto: false })])); - } - - #[test] - fn test_input_globs() { - let user_config_json = json!({ - "input": ["src/**/*.ts", "package.json"] - }); - let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - config.input, - Some(vec![ - UserInputEntry::Glob("src/**/*.ts".into()), - UserInputEntry::Glob("package.json".into()), - ]) - ); - } - - #[test] - fn test_input_negative_globs() { - let user_config_json = json!({ - "input": ["src/**", "!src/**/*.test.ts"] - }); - let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - config.input, - Some(vec![ - UserInputEntry::Glob("src/**".into()), - UserInputEntry::Glob("!src/**/*.test.ts".into()), - ]) - ); - } - - #[test] - fn test_input_mixed() { - let user_config_json = json!({ - "input": ["package.json", { "auto": true }, "!node_modules/**"] - }); - let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - config.input, - Some(vec![ - UserInputEntry::Glob("package.json".into()), - UserInputEntry::Auto(AutoTracking { auto: true }), - UserInputEntry::Glob("!node_modules/**".into()), - ]) - ); - } - - #[test] - fn test_input_glob_with_base_workspace() { - let user_config_json = json!({ - "input": [{ "pattern": "configs/tsconfig.json", "base": "workspace" }] - }); - let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - config.input, - Some(vec![UserInputEntry::GlobWithBase(GlobWithBase { - pattern: "configs/tsconfig.json".into(), - base: InputBase::Workspace, - })]) - ); - } - - #[test] - fn test_input_glob_with_base_package() { - let user_config_json = json!({ - "input": [{ "pattern": "src/**", "base": "package" }] - }); - let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - config.input, - Some(vec![UserInputEntry::GlobWithBase(GlobWithBase { - pattern: "src/**".into(), - base: InputBase::Package, - })]) - ); - } - - #[test] - fn test_input_negative_glob_with_base() { - let user_config_json = json!({ - "input": [{ "pattern": "!dist/**", "base": "workspace" }] - }); - let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - config.input, - Some(vec![UserInputEntry::GlobWithBase(GlobWithBase { - pattern: "!dist/**".into(), - base: InputBase::Workspace, - })]) - ); - } - - #[test] - fn test_input_glob_with_base_missing_base_error() { - // { "pattern": "src/**" } without "base" should fail (base is required) - let user_config_json = json!({ - "input": [{ "pattern": "src/**" }] - }); - let result = serde_json::from_value::(user_config_json); - assert!(result.is_err(), "missing 'base' field should produce an error"); - } - - #[test] - fn test_input_glob_with_base_invalid_base_error() { - let user_config_json = json!({ - "input": [{ "pattern": "src/**", "base": "invalid" }] - }); - let result = serde_json::from_value::(user_config_json); - assert!(result.is_err(), "invalid 'base' value should produce an error"); - } - - #[test] - fn test_input_mixed_auto_and_glob_with_base_error() { - // An object with both "auto" and "pattern"/"base" should fail due to deny_unknown_fields - let user_config_json = json!({ - "input": [{ "auto": true, "pattern": "src/**", "base": "workspace" }] - }); - let result = serde_json::from_value::(user_config_json); - assert!(result.is_err(), "mixing auto and pattern/base fields should produce an error"); - } - - #[test] - fn test_input_mixed_with_glob_base() { - let user_config_json = json!({ - "input": [ - "package.json", - { "pattern": "configs/**", "base": "workspace" }, - { "auto": true }, - "!node_modules/**" - ] - }); - let config: EnabledCacheConfig = serde_json::from_value(user_config_json).unwrap(); - assert_eq!( - config.input, - Some(vec![ - UserInputEntry::Glob("package.json".into()), - UserInputEntry::GlobWithBase(GlobWithBase { - pattern: "configs/**".into(), - base: InputBase::Workspace, - }), - UserInputEntry::Auto(AutoTracking { auto: true }), - UserInputEntry::Glob("!node_modules/**".into()), - ]) - ); - } - - #[test] - fn test_input_with_cache_false_error() { - // input with cache: false should produce a serde error due to deny_unknown_fields - let user_config_json = json!({ - "cache": false, - "input": ["src/**"] - }); - assert!(serde_json::from_value::(user_config_json).is_err()); - } - - #[test] - fn test_cache_disabled_but_with_fields() { - let user_config_json = json!({ - "cache": false, - "env": ["NODE_ENV"], - }); - assert!(serde_json::from_value::(user_config_json).is_err()); - } - - #[test] - fn test_deny_unknown_field() { - let user_config_json = json!({ - "foo": 42, - }); - assert!(serde_json::from_value::(user_config_json).is_err()); - } - - #[test] - fn test_global_cache_bool_true() { - let config: UserGlobalCacheConfig = serde_json::from_value(json!(true)).unwrap(); - assert_eq!(config, UserGlobalCacheConfig::Bool(true)); - let resolved = ResolvedGlobalCacheConfig::resolve_from(Some(&config)); - assert!(resolved.scripts); - assert!(resolved.tasks); - } - - #[test] - fn test_global_cache_bool_false() { - let config: UserGlobalCacheConfig = serde_json::from_value(json!(false)).unwrap(); - assert_eq!(config, UserGlobalCacheConfig::Bool(false)); - let resolved = ResolvedGlobalCacheConfig::resolve_from(Some(&config)); - assert!(!resolved.scripts); - assert!(!resolved.tasks); - } - - #[test] - fn test_global_cache_detailed_scripts_only() { - let config: UserGlobalCacheConfig = - serde_json::from_value(json!({ "scripts": true })).unwrap(); - let resolved = ResolvedGlobalCacheConfig::resolve_from(Some(&config)); - assert!(resolved.scripts); - assert!(resolved.tasks); // defaults to true - } - - #[test] - fn test_global_cache_detailed_tasks_false() { - let config: UserGlobalCacheConfig = - serde_json::from_value(json!({ "tasks": false })).unwrap(); - let resolved = ResolvedGlobalCacheConfig::resolve_from(Some(&config)); - assert!(!resolved.scripts); // defaults to false - assert!(!resolved.tasks); - } - - #[test] - fn test_global_cache_detailed_both() { - let config: UserGlobalCacheConfig = - serde_json::from_value(json!({ "scripts": true, "tasks": false })).unwrap(); - let resolved = ResolvedGlobalCacheConfig::resolve_from(Some(&config)); - assert!(resolved.scripts); - assert!(!resolved.tasks); - } - - #[test] - fn test_global_cache_none_defaults() { - let resolved = ResolvedGlobalCacheConfig::resolve_from(None); - assert!(!resolved.scripts); // defaults to false - assert!(resolved.tasks); // defaults to true - } - - #[test] - fn test_global_cache_detailed_unknown_field() { - assert!( - serde_json::from_value::(json!({ "unknown": true })).is_err() - ); - } - - #[test] - fn test_run_config_unknown_top_level_field() { - assert!(serde_json::from_value::(json!({ "unknown": true })).is_err()); - } - - #[test] - fn test_task_config_unknown_field() { - assert!( - serde_json::from_value::(json!({ "command": "echo", "unknown": true })) - .is_err() - ); - } -} diff --git a/crates/vite_task_graph/src/display.rs b/crates/vite_task_graph/src/display.rs deleted file mode 100644 index 2648aae95..000000000 --- a/crates/vite_task_graph/src/display.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! Structs for printing packages and tasks in a human-readable way. It's used in error messages and CLI outputs. - -use std::{fmt::Display, sync::Arc}; - -use serde::Serialize; -use vite_path::AbsolutePath; -use vite_str::Str; - -use crate::{IndexedTaskGraph, TaskNodeIndex}; - -/// struct for printing a task in a human-readable way. -#[derive(Debug, Clone, Serialize)] -pub struct TaskDisplay { - pub package_name: Str, - pub task_name: Str, - pub package_path: Arc, -} - -impl Display for TaskDisplay { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // Only include package name and # separator if package name is not empty - if self.package_name.is_empty() { - write!(f, "{}", self.task_name) - } else { - write!(f, "{}#{}", self.package_name, self.task_name) - } - } -} - -/// A task with its display info and command, for listing purposes. -#[derive(Debug)] -pub struct TaskListEntry { - pub task_display: TaskDisplay, - pub command: Str, -} - -impl IndexedTaskGraph { - /// Get human-readable display for a task node. - #[must_use] - pub fn display_task(&self, task_index: TaskNodeIndex) -> TaskDisplay { - self.task_graph()[task_index].task_display.clone() - } - - /// Returns all tasks as a flat list. - #[must_use] - pub fn list_tasks(&self) -> Vec { - self.task_graph() - .node_indices() - .map(|idx| { - let node = &self.task_graph()[idx]; - TaskListEntry { - task_display: node.task_display.clone(), - command: format_command_for_task_list(&node.resolved_config.commands), - } - }) - .collect() - } -} - -// Display-only formatting for task list/selector descriptions. Execution planning keeps -// command arrays structured and must not depend on this joined string. -fn format_command_for_task_list(commands: &Arc<[Str]>) -> Str { - if commands.len() == 1 { - commands[0].clone() - } else { - let mut display = Str::default(); - for (index, command) in commands.iter().enumerate() { - if index > 0 { - display.push_str(" && "); - } - display.push_str(command.as_str()); - } - display - } -} diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs deleted file mode 100644 index 72dc950ac..000000000 --- a/crates/vite_task_graph/src/lib.rs +++ /dev/null @@ -1,612 +0,0 @@ -pub mod config; -pub mod display; -pub mod loader; -pub mod query; -mod specifier; - -use std::{convert::Infallible, sync::Arc}; - -use config::{ - ResolvedGlobalCacheConfig, ResolvedTaskConfig, UserRunConfig, UserTaskConfig, - UserTaskDefinition, -}; -use petgraph::{ - graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}, - visit::EdgeRef as _, -}; -use rustc_hash::{FxBuildHasher, FxHashMap}; -use serde::Serialize; -pub use specifier::TaskSpecifier; -use vite_path::AbsolutePath; -use vite_str::Str; -use vite_workspace::{ - DependencyType, PackageNodeIndex, WorkspaceRoot, package_graph::IndexedPackageGraph, -}; - -use crate::{ - config::user::{ - UserDependencyType, UserDependsOnEntry, UserPackageDependency, UserTaskOptions, - }, - display::TaskDisplay, -}; - -/// The type of a task dependency edge in the task graph. -/// -/// All edges are produced from `dependsOn` in task config, including string-form -/// task specifiers and object-form package dependency selections. Topological -/// ordering is handled at query time via the package subgraph rather than by -/// pre-computing package edges in the task graph. -#[derive(Debug, Clone, Copy, Serialize)] -pub struct TaskDependencyType; - -impl TaskDependencyType { - /// Returns `true` — all task graph edges are explicit `dependsOn` dependencies. - /// - /// Kept as an associated function for use as a filter predicate in - /// `add_dependencies`. Always returns `true` since `TaskDependencyType` - /// only represents explicit edges now. - #[must_use] - pub const fn is_explicit() -> bool { - true - } -} - -/// Uniquely identifies a task, by its name and the package where it's defined. -#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)] -pub(crate) struct TaskId { - /// The index of the package where the task is defined. - pub package_index: PackageNodeIndex, - - /// The name of the script or the entry in `vite.config.*`. - pub task_name: Str, -} - -/// Whether a task originates from the `tasks` map or from a package.json script. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] -pub enum TaskSource { - /// Defined in the `tasks` map in the workspace config. - TaskConfig, - /// Pure package.json script (not in the tasks map). - PackageJsonScript, -} - -/// A node in the task graph, representing a task with its resolved configuration. -#[derive(Debug, Serialize)] -pub struct TaskNode { - /// Printing the task in a human-readable way. - pub task_display: TaskDisplay, - - /// The resolved configuration of this task. - /// - /// This contains information affecting how the task is spawn, - /// whereas `task_id` is for looking up the task. - /// - /// However, it does not contain external factors like additional args from cli and env vars. - pub resolved_config: ResolvedTaskConfig, - - /// Whether this task comes from the tasks map or a package.json script. - pub source: TaskSource, -} - -#[derive(Debug, thiserror::Error)] -pub enum TaskGraphLoadError { - #[error("Failed to load package graph")] - PackageGraphLoadError(#[from] vite_workspace::Error), - - #[error("Failed to load task config file for package at {package_path:?}")] - ConfigLoadError { - package_path: Arc, - #[source] - error: anyhow::Error, - }, - - #[error( - "Task {task_display} conflicts with a package.json script of the same name. \ - Remove the script from package.json or rename the task" - )] - ScriptConflict { task_display: TaskDisplay }, - - #[error("Failed to resolve task config for task {task_display}")] - ResolveConfigError { - task_display: TaskDisplay, - #[source] - error: crate::config::ResolveTaskConfigError, - }, - - #[error("Failed to lookup dependency '{specifier}' for task {task_display}")] - DependencySpecifierLookupError { - specifier: Str, - task_display: TaskDisplay, - #[source] - error: SpecifierLookupError, - }, - - #[error("`cache` can only be set in the workspace root config, but found in {package_path}")] - CacheInNonRootPackage { package_path: Arc }, - - #[error( - "`enablePrePostScripts` can only be set in the workspace root config, but found in {package_path}" - )] - PrePostScriptsInNonRootPackage { package_path: Arc }, -} - -/// Error when looking up a task by its specifier. -/// -/// It's generic over `UnknownPackageError`, which is the error type when looking up a task without a package name and without a package origin. -/// -/// - When the specifier is from `dependOn` of a known task, `UnknownPackageError` is `Infallible` because the origin package is always known. -/// - When the specifier is from a CLI command, `UnknownPackageError` can be a real error type in case cwd is not in any package. -#[derive(Debug, thiserror::Error, Serialize)] -pub enum SpecifierLookupError { - #[error("Package '{package_name}' is ambiguous among multiple packages: {package_paths:?}")] - AmbiguousPackageName { package_name: Str, package_paths: Box<[Arc]> }, - - #[error("Package '{package_name}' not found")] - PackageNameNotFound { package_name: Str }, - - #[error("Task '{task_name}' not found in package {package_name}")] - TaskNameNotFound { - package_name: Str, - task_name: Str, - #[serde(skip)] - package_index: PackageNodeIndex, - }, - - #[error( - "Nowhere to look for task '{task_name}' because the package is unknown: {unspecifier_package_error}" - )] - PackageUnknown { unspecifier_package_error: PackageUnknownError, task_name: Str }, -} - -/// newtype of `DefaultIx` for indices in task graphs -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] -pub struct TaskIx(DefaultIx); -// SAFETY: TaskIx is a newtype over DefaultIx which already implements IndexType correctly -unsafe impl IndexType for TaskIx { - fn new(x: usize) -> Self { - Self(DefaultIx::new(x)) - } - - fn index(&self) -> usize { - self.0.index() - } - - fn max() -> Self { - Self(::max()) - } -} - -pub type TaskNodeIndex = NodeIndex; -pub type TaskEdgeIndex = EdgeIndex; - -/// A `dependsOn` entry that selects direct package dependencies from a source task. -struct PackageDependencyEntry { - task_name: Str, - dependency_types: Box<[DependencyType]>, -} - -impl PackageDependencyEntry { - fn from_user_config(entry: &UserPackageDependency) -> Self { - Self { - task_name: entry.task.clone(), - dependency_types: entry - .from - .as_slice() - .iter() - .copied() - .map(DependencyType::from) - .collect(), - } - } -} - -impl From for DependencyType { - fn from(value: UserDependencyType) -> Self { - match value { - UserDependencyType::Dependencies => Self::Normal, - UserDependencyType::DevDependencies => Self::Dev, - UserDependencyType::PeerDependencies => Self::Peer, - } - } -} - -/// Full task graph of a workspace, with necessary hash maps for quick task lookup -/// -/// It's immutable after created. The task nodes contain resolved task configurations and their dependencies. -/// External factors (e.g. additional args from cli, current working directory, environmental variables) are not stored here. -#[derive(Debug)] -pub struct IndexedTaskGraph { - task_graph: DiGraph, - - /// Preserve the package graph for two purposes: - /// - `self.task_graph` refers packages via `PackageNodeIndex`. To display package names and paths, we need to lookup them in `package_graph`. - /// - To find nearest topological tasks when the starting package itself doesn't contain the task with the given name. - indexed_package_graph: IndexedPackageGraph, - - /// task indices by task id for quick lookup - pub(crate) node_indices_by_task_id: FxHashMap, - - /// Reverse map: task node index → task id (for hook lookup) - task_ids_by_node_index: FxHashMap, - - /// Global cache configuration resolved from the workspace root config. - resolved_global_cache: ResolvedGlobalCacheConfig, - - /// Whether pre/post script hooks are enabled (from `enablePrePostScripts` in workspace root config). - pre_post_scripts_enabled: bool, -} - -pub type TaskGraph = DiGraph; - -impl IndexedTaskGraph { - /// Load the task graph from a discovered workspace using the provided config loader. - /// - /// # Errors - /// - /// Returns [`TaskGraphLoadError`] if the package graph fails to load, a config file - /// cannot be read, a task config cannot be resolved, a dependency specifier is invalid, - /// or `cache` is set in a non-root package. - #[tracing::instrument(level = "debug", skip_all)] - #[expect( - clippy::too_many_lines, - reason = "graph loading is inherently sequential and multi-step" - )] - pub async fn load( - workspace_root: &WorkspaceRoot, - config_loader: &dyn loader::UserConfigLoader, - ) -> Result { - let mut task_graph = DiGraph::::default(); - - let package_graph = vite_workspace::load_package_graph(workspace_root)?; - - // Record dependency declarations for each task node to add explicit dependencies later. - let mut task_ids_with_depends_on_entries: Vec<(TaskId, Option>)> = - Vec::new(); - - // index tasks by ids - let mut node_indices_by_task_id: FxHashMap = - FxHashMap::with_capacity_and_hasher(task_graph.node_count(), FxBuildHasher); - let mut task_ids_by_node_index: FxHashMap = - FxHashMap::with_capacity_and_hasher(task_graph.node_count(), FxBuildHasher); - - // First pass: load all configs, extract root cache config, validate - let mut root_cache = None; - let mut root_pre_post_scripts_enabled = None; - let mut package_configs: Vec<(PackageNodeIndex, Arc, UserRunConfig)> = - Vec::with_capacity(package_graph.node_count()); - - for package_index in package_graph.node_indices() { - let package = &package_graph[package_index]; - let package_dir: Arc = workspace_root.path.join(&package.path).into(); - let is_workspace_root = package.path.as_str().is_empty(); - - let user_config = config_loader - .load_user_config_file(&package_dir) - .await - .map_err(|error| TaskGraphLoadError::ConfigLoadError { - error, - package_path: package_dir.clone(), - })? - .unwrap_or_default(); - - if let Some(cache) = user_config.cache { - if is_workspace_root { - root_cache = Some(cache); - } else { - return Err(TaskGraphLoadError::CacheInNonRootPackage { - package_path: package_dir.clone(), - }); - } - } - - if let Some(val) = user_config.enable_pre_post_scripts { - if is_workspace_root { - root_pre_post_scripts_enabled = Some(val); - } else { - return Err(TaskGraphLoadError::PrePostScriptsInNonRootPackage { - package_path: package_dir.clone(), - }); - } - } - - package_configs.push((package_index, package_dir, user_config)); - } - - let resolved_global_cache = ResolvedGlobalCacheConfig::resolve_from(root_cache.as_ref()); - - // Second pass: create task nodes (cache is NOT applied here; it's applied at plan time) - for (package_index, package_dir, user_config) in package_configs { - let package = &package_graph[package_index]; - - // Collect package.json scripts into a mutable map for draining lookup. - let mut package_json_scripts: FxHashMap<&str, &str> = package - .package_json - .scripts - .iter() - .map(|(name, value)| (name.as_str(), value.as_str())) - .collect(); - - for (task_name, task_user_config) in user_config.tasks.unwrap_or_default() { - // Error if a package.json script with the same name exists - if package_json_scripts.remove(task_name.as_str()).is_some() { - return Err(TaskGraphLoadError::ScriptConflict { - task_display: TaskDisplay { - package_name: package.package_json.name.clone(), - task_name: task_name.clone(), - package_path: Arc::clone(&package_dir), - }, - }); - } - - let task_id = TaskId { task_name: task_name.clone(), package_index }; - - let task_user_config = match task_user_config { - UserTaskDefinition::Object(config) => config, - UserTaskDefinition::CommandShorthand(command) => { - UserTaskConfig { command, options: UserTaskOptions::default() } - } - }; - let depends_on_entries = task_user_config.options.depends_on.clone(); - - // Resolve the task configuration from the user config - let resolved_config = ResolvedTaskConfig::resolve( - task_user_config, - &package_dir, - &workspace_root.path, - ) - .map_err(|err| TaskGraphLoadError::ResolveConfigError { - error: err, - task_display: TaskDisplay { - package_name: package.package_json.name.clone(), - task_name: task_name.clone(), - package_path: Arc::clone(&package_dir), - }, - })?; - - let task_node = TaskNode { - task_display: TaskDisplay { - package_name: package.package_json.name.clone(), - task_name: task_name.clone(), - package_path: Arc::clone(&package_dir), - }, - resolved_config, - source: TaskSource::TaskConfig, - }; - - let node_index = task_graph.add_node(task_node); - task_ids_with_depends_on_entries.push((task_id.clone(), depends_on_entries)); - task_ids_by_node_index.insert(node_index, task_id.clone()); - node_indices_by_task_id.insert(task_id, node_index); - } - - // For remaining package.json scripts not in the tasks map, create tasks with default config - for (script_name, package_json_script) in package_json_scripts { - let task_id = TaskId { task_name: Str::from(script_name), package_index }; - let resolved_config = ResolvedTaskConfig::resolve_package_json_script( - &package_dir, - package_json_script, - &workspace_root.path, - ) - .map_err(|err| TaskGraphLoadError::ResolveConfigError { - error: err, - task_display: TaskDisplay { - package_name: package.package_json.name.clone(), - task_name: script_name.into(), - package_path: Arc::clone(&package_dir), - }, - })?; - let node_index = task_graph.add_node(TaskNode { - task_display: TaskDisplay { - package_name: package.package_json.name.clone(), - task_name: script_name.into(), - package_path: Arc::clone(&package_dir), - }, - resolved_config, - source: TaskSource::PackageJsonScript, - }); - task_ids_by_node_index.insert(node_index, task_id.clone()); - node_indices_by_task_id.insert(task_id, node_index); - } - } - - // Construct `Self` with task_graph with all task nodes ready and indexed, but no edges. - let mut me = Self { - task_graph, - indexed_package_graph: IndexedPackageGraph::index(package_graph), - node_indices_by_task_id, - task_ids_by_node_index, - resolved_global_cache, - pre_post_scripts_enabled: root_pre_post_scripts_enabled.unwrap_or(true), - }; - - // Add explicit dependencies. String-form entries resolve to fixed task - // specifier edges. Object-form entries select direct package dependency - // tasks and materialize those selections as global task graph edges. - for (from_task_id, depends_on_entries) in task_ids_with_depends_on_entries { - let from_node_index = me.node_indices_by_task_id[&from_task_id]; - for entry in depends_on_entries.iter().flat_map(|entries| entries.iter()) { - match entry { - UserDependsOnEntry::Task(specifier) => { - let to_node_index = me - .get_task_index_by_specifier::( - TaskSpecifier::parse_raw(specifier), - || Ok(from_task_id.package_index), - ) - .map_err(|error| { - TaskGraphLoadError::DependencySpecifierLookupError { - error, - specifier: specifier.clone(), - task_display: me.display_task(from_node_index), - } - })?; - me.task_graph.update_edge( - from_node_index, - to_node_index, - TaskDependencyType, - ); - } - UserDependsOnEntry::Package(entry) => { - let entry = PackageDependencyEntry::from_user_config(entry); - me.add_package_dependency_edges(from_node_index, &from_task_id, &entry); - } - } - } - } - - // Topological dependency edges are no longer pre-computed here. - // Ordering is now handled at query time via the package subgraph induced by - // `IndexedPackageGraph::resolve_query` in `query/mod.rs`. - Ok(me) - } - - /// Lookup the node index of a task by a specifier. - /// - /// The specifier can be either 'packageName#taskName' or just 'taskName' (in which case the task in the origin package is looked up). - fn get_task_index_by_specifier( - &self, - specifier: TaskSpecifier, - get_package_origin: impl FnOnce() -> Result, - ) -> Result> { - let package_index = if let Some(package_name) = specifier.package_name { - // Lookup package path by the package name from '#' - let Some(package_indices) = - self.indexed_package_graph.get_package_indices_by_name(&package_name) - else { - return Err(SpecifierLookupError::PackageNameNotFound { package_name }); - }; - if package_indices.len() > 1 { - return Err(SpecifierLookupError::AmbiguousPackageName { - package_name, - package_paths: package_indices - .iter() - .map(|package_index| { - Arc::clone( - &self.indexed_package_graph.package_graph()[*package_index] - .absolute_path, - ) - }) - .collect(), - }); - } - *package_indices.first() - } else { - // No '#', so the specifier only contains task name, look up in the origin path package - get_package_origin().map_err(|err| SpecifierLookupError::PackageUnknown { - unspecifier_package_error: err, - task_name: specifier.task_name.clone(), - })? - }; - let task_id_to_lookup = TaskId { task_name: specifier.task_name, package_index }; - let Some(node_index) = self.node_indices_by_task_id.get(&task_id_to_lookup) else { - return Err(SpecifierLookupError::TaskNameNotFound { - package_name: self.indexed_package_graph.package_graph()[package_index] - .package_json - .name - .clone(), - task_name: task_id_to_lookup.task_name, - package_index, - }); - }; - Ok(*node_index) - } - - /// Materialize one object-form `dependsOn` entry as ordinary task graph edges. - /// - /// Object entries such as `{ "task": "build", "from": "dependencies" }` - /// are anchored to the package that declares the source task. During graph - /// loading we resolve the selected direct package dependencies and add - /// `source_task -> dependency_package#task` edges, so later query planning - /// can treat them exactly like string-form `dependsOn` edges. - fn add_package_dependency_edges( - &mut self, - from_node_index: TaskNodeIndex, - from_task_id: &TaskId, - entry: &PackageDependencyEntry, - ) { - let package_graph = self.indexed_package_graph.package_graph(); - let dependency_tasks = package_graph - .edges(from_task_id.package_index) - // Only use package.json dependency fields requested by `from`. - .filter(|edge| entry.dependency_types.contains(edge.weight())) - // Missing tasks are intentionally ignored: selecting - // `{ task: "build", from: "dependencies" }` means "run build in - // direct dependency packages that define build", not "require every - // selected package to define build". - .filter_map(|edge| { - self.node_indices_by_task_id - .get(&TaskId { - package_index: edge.target(), - task_name: entry.task_name.clone(), - }) - .copied() - }) - .collect::>(); - - // Keep discovery separate from insertion so the package-graph walk and - // task-id lookups finish before we take a mutable borrow of task_graph. - for to_node_index in dependency_tasks { - self.task_graph.update_edge(from_node_index, to_node_index, TaskDependencyType); - } - } - - #[must_use] - pub const fn task_graph(&self) -> &TaskGraph { - &self.task_graph - } - - #[must_use] - pub fn get_package_name(&self, package_index: PackageNodeIndex) -> &str { - self.indexed_package_graph.package_graph()[package_index].package_json.name.as_str() - } - - #[must_use] - pub fn get_package_path(&self, package_index: PackageNodeIndex) -> &Arc { - &self.indexed_package_graph.package_graph()[package_index].absolute_path - } - - #[must_use] - pub fn get_package_path_for_task(&self, task_index: TaskNodeIndex) -> &Arc { - &self.task_graph[task_index].task_display.package_path - } - - /// Get the package path for a given current working directory by traversing up the directory - /// tree to find the nearest package. - #[must_use] - pub fn get_package_path_from_cwd(&self, cwd: &AbsolutePath) -> Option<&Arc> { - let index = self.indexed_package_graph.get_package_index_from_cwd(cwd)?; - Some(self.get_package_path(index)) - } - - #[must_use] - pub const fn global_cache_config(&self) -> &ResolvedGlobalCacheConfig { - &self.resolved_global_cache - } - - /// Whether pre/post script hooks are enabled workspace-wide. - #[must_use] - pub const fn pre_post_scripts_enabled(&self) -> bool { - self.pre_post_scripts_enabled - } - - /// Returns the `TaskNodeIndex` of the pre/post hook for a `PackageJsonScript` task. - /// - /// Given a task named `X` and `prefix = "pre"`, looks up `preX` in the same package. - /// Given a task named `X` and `prefix = "post"`, looks up `postX` in the same package. - /// - /// Returns `None` if: - /// - The task is not a `PackageJsonScript` - /// - No `{prefix}{name}` script exists in the same package - /// - The hook is not itself a `PackageJsonScript` - #[must_use] - pub fn get_script_hook(&self, task_idx: TaskNodeIndex, prefix: &str) -> Option { - let task_node = &self.task_graph[task_idx]; - if task_node.source != TaskSource::PackageJsonScript { - return None; - } - let task_id = self.task_ids_by_node_index.get(&task_idx)?; - let hook_name = vite_str::format!("{prefix}{}", task_node.task_display.task_name); - let hook_id = TaskId { package_index: task_id.package_index, task_name: hook_name }; - let &hook_idx = self.node_indices_by_task_id.get(&hook_id)?; - (self.task_graph[hook_idx].source == TaskSource::PackageJsonScript).then_some(hook_idx) - } -} diff --git a/crates/vite_task_graph/src/loader.rs b/crates/vite_task_graph/src/loader.rs deleted file mode 100644 index d00f2dfe6..000000000 --- a/crates/vite_task_graph/src/loader.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::fmt::Debug; - -use vite_path::AbsolutePath; - -use crate::config::UserRunConfig; - -/// Loader trait for loading user configuration files (vite-task.json). -#[async_trait::async_trait(?Send)] -pub trait UserConfigLoader: Debug + Send + Sync { - async fn load_user_config_file( - &self, - package_path: &AbsolutePath, - ) -> anyhow::Result>; -} diff --git a/crates/vite_task_graph/src/query/mod.rs b/crates/vite_task_graph/src/query/mod.rs deleted file mode 100644 index 9478a4b18..000000000 --- a/crates/vite_task_graph/src/query/mod.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! Task query: map a `PackageQuery` to a `TaskExecutionGraph`. -//! -//! # Two-stage model -//! -//! Stage 1 — package selection — is handled by `IndexedPackageGraph::resolve_query` -//! and produces a `DiGraphMap` (the *package subgraph*). -//! -//! Stage 2 — task mapping — is handled by `map_subgraph_to_tasks`: -//! - Packages that **have** the requested task are mapped to their `TaskNodeIndex`. -//! - Packages that **lack** the task are *reconnected*: each predecessor is wired -//! directly to each successor, then the task-lacking node is removed. This preserves -//! transitive ordering even when intermediate packages miss the task. -//! -//! After all task-lacking nodes have been removed, the remaining package subgraph -//! contains only task-having packages; edges map directly to task dependency edges. -//! -//! Explicit `dependsOn` dependencies are then added on top by `add_dependencies`. - -use petgraph::{Direction, prelude::DiGraphMap, visit::EdgeRef}; -use rustc_hash::{FxHashMap, FxHashSet}; -use vite_str::Str; -use vite_workspace::PackageNodeIndex; -pub use vite_workspace::package_graph::{PackageQuery, PackageQueryResolveError}; - -use crate::{IndexedTaskGraph, TaskDependencyType, TaskId, TaskNodeIndex}; - -/// A task execution graph queried from a `TaskQuery`. -/// -/// Nodes in `graph` are `TaskNodeIndex` values into the full `TaskGraph`. -/// Edges represent the final dependency relationships between tasks (no weights). -/// -/// `requested` is the subset of nodes the user typed on the CLI — i.e. the -/// nodes added by `map_subgraph_to_tasks` (stage 2), not the ones reached -/// only via `dependsOn` expansion in `IndexedTaskGraph::add_dependencies` (stage 3). -/// -/// For example, given `test` with `dependsOn: ["build"]` and the command -/// `vp run test some-filter`: -/// -/// - `graph` contains both `test` and `build` with an edge between them. -/// - `requested` contains only `test`. -/// -/// The planner uses this distinction to forward `some-filter` to `test` -/// while running `build` with no extra args. -#[derive(Debug, Default, Clone)] -pub struct TaskExecutionGraph { - pub graph: DiGraphMap, - pub requested: FxHashSet, -} - -/// A query for which tasks to run. -/// -/// A `TaskQuery` must be **self-contained**: it fully describes which tasks -/// will be selected, without relying on ambient state such as cwd or -/// environment variables. For example, the implicit cwd is captured as a -/// `ContainingPackage(path)` selector inside [`PackageQuery`], so two -/// queries from different directories compare as unequal even though the -/// user typed the same CLI arguments. -/// -/// This property is essential for the **skip rule** in task planning, which -/// compares the nested query against the parent query with `==`. If any -/// external context leaked into the comparison (or was excluded from it), -/// the skip rule would either miss legitimate recursion or incorrectly -/// suppress distinct expansions. -#[derive(Debug, PartialEq)] -pub struct TaskQuery { - /// Which packages to select. - pub package_query: PackageQuery, - - /// The task name to run within each selected package. - pub task_name: Str, - - /// Whether to include explicit `dependsOn` dependencies from the task config. - pub include_explicit_deps: bool, -} - -/// The result of [`IndexedTaskGraph::query_tasks`]. -#[derive(Debug)] -pub struct TaskQueryResult { - /// The final execution graph for the selected tasks. - /// - /// May be empty if no selected packages have the requested task, or if no - /// packages matched the filters. The caller distinguishes the two cases - /// with [`Self::selected_package_count`]. - pub execution_graph: TaskExecutionGraph, - - /// Original `--filter` strings for inclusion filters that contributed no - /// packages to the final selected set — either the core selector matched - /// nothing, or the traversal (e.g. `^...`) collapsed an otherwise-matching - /// seed down to zero. - /// - /// Omits synthetic filters (implicit cwd, `-w`) since the user didn't type them. - /// Always empty when `PackageQuery::All` was used. - pub unmatched_selectors: Vec, - - /// Number of packages in the resolved package subgraph (Stage 1 result), - /// before any task mapping. - /// - /// `0` means the filter expression(s) selected no packages at all — this - /// is what tells the caller "no packages matched the filter" rather than - /// "packages were selected but none have the requested task". - pub selected_package_count: usize, -} - -impl IndexedTaskGraph { - /// Query the task graph based on the given [`TaskQuery`]. - /// - /// Returns a [`TaskQueryResult`] containing the execution graph and any - /// unmatched selectors. The execution graph may be empty — the caller decides - /// what to do in that case (show task selector, emit warnings, etc.). - /// - /// # Errors - /// - /// Returns [`PackageQueryResolveError::AmbiguousPackageName`] when an exact - /// package name (from a `pkg#task` specifier) matches multiple packages. - /// - /// # Order of operations - /// - /// 1. Resolve `PackageQuery` → package subgraph (Stage 1). - /// 2. Map package subgraph → task execution graph, reconnecting task-lacking - /// packages (Stage 2). - /// 3. Expand explicit `dependsOn` edges (if `include_explicit_deps`). - pub fn query_tasks( - &self, - query: &TaskQuery, - ) -> Result { - let mut execution_graph = TaskExecutionGraph::default(); - - // Stage 1: resolve package selection. - let resolution = self.indexed_package_graph.resolve_query(&query.package_query)?; - let selected_package_count = resolution.package_subgraph.node_count(); - - // Stage 2: map each selected package to its task node (with reconnection). - self.map_subgraph_to_tasks( - &resolution.package_subgraph, - &query.task_name, - &mut execution_graph, - ); - - // Expand explicit dependsOn edges (may add new task nodes from outside the subgraph). - if query.include_explicit_deps { - self.add_dependencies(&mut execution_graph, |_| TaskDependencyType::is_explicit()); - } - - Ok(TaskQueryResult { - execution_graph, - unmatched_selectors: resolution.unmatched_selectors, - selected_package_count, - }) - } - - /// Map a package subgraph to a task execution graph. - /// - /// For packages **with** the task: add the corresponding `TaskNodeIndex`. - /// - /// For packages **without** the task: wire each predecessor directly to each - /// successor (skip-intermediate reconnection), then remove the node. Working on - /// a *mutable clone* of the subgraph ensures that reconnected edges are visible - /// when processing subsequent task-lacking nodes in the same pass — transitive - /// task-lacking chains are resolved correctly regardless of iteration order. - /// - /// After all task-lacking nodes are removed, every remaining node in `subgraph` - /// is guaranteed to be in `pkg_to_task`. The index operator panics on a missing - /// key — a panic here indicates a bug in the reconnection loop above. - fn map_subgraph_to_tasks( - &self, - package_subgraph: &DiGraphMap, - task_name: &Str, - execution_graph: &mut TaskExecutionGraph, - ) { - // Build the task-lookup map for all packages that have the requested task. - let pkg_to_task: FxHashMap = package_subgraph - .nodes() - .filter_map(|pkg| { - self.node_indices_by_task_id - .get(&TaskId { package_index: pkg, task_name: task_name.clone() }) - .map(|&task_idx| (pkg, task_idx)) - }) - .collect(); - - // Clone the subgraph so that reconnection edits are visible in subsequent iterations. - let mut subgraph = package_subgraph.clone(); - - // Reconnect and remove each task-lacking node. - for pkg in package_subgraph.nodes() { - if pkg_to_task.contains_key(&pkg) { - continue; // this package has the task — leave it in - } - // Read pred/succ from the live (possibly already-modified) subgraph. - let preds: Vec<_> = subgraph.neighbors_directed(pkg, Direction::Incoming).collect(); - let succs: Vec<_> = subgraph.neighbors_directed(pkg, Direction::Outgoing).collect(); - // Bridge: every predecessor connects directly to every successor. - for &pred in &preds { - for &succ in &succs { - subgraph.add_edge(pred, succ, ()); - } - } - subgraph.remove_node(pkg); - } - - // Map remaining nodes and their edges to task nodes. - // Every node still in `subgraph` is in `pkg_to_task`; the index operator - // panics on a missing key — that would be a bug in the loop above. - // - // All nodes added here are explicitly-requested tasks, so they are - // inserted into both the inner graph and the `requested` set. - for &task_idx in pkg_to_task.values() { - execution_graph.graph.add_node(task_idx); - execution_graph.requested.insert(task_idx); - } - for (src, dst, ()) in subgraph.all_edges() { - let st = pkg_to_task[&src]; - let dt = pkg_to_task[&dst]; - execution_graph.graph.add_edge(st, dt, ()); - } - } - - /// Recursively add dependencies to the execution graph based on filtered edges. - /// - /// Starts from the current nodes in `execution_graph` and follows outgoing edges - /// that match `filter_edge`, adding new nodes to the frontier until no new nodes - /// are discovered. - fn add_dependencies( - &self, - execution_graph: &mut TaskExecutionGraph, - mut filter_edge: impl FnMut(TaskDependencyType) -> bool, - ) { - let mut frontier: FxHashSet = execution_graph.graph.nodes().collect(); - - // Continue until no new nodes are added to the frontier. - // - // Nodes added here are dependency-only tasks and must NOT be marked as - // `requested` — the planner uses that distinction to decide whether to - // forward CLI extra args to a task. - while !frontier.is_empty() { - let mut next_frontier = FxHashSet::::default(); - - for from_node in frontier { - for edge_ref in self.task_graph.edges(from_node) { - let to_node = edge_ref.target(); - let dep_type = *edge_ref.weight(); - if filter_edge(dep_type) { - let is_new = !execution_graph.graph.contains_node(to_node); - execution_graph.graph.add_edge(from_node, to_node, ()); - if is_new { - next_frontier.insert(to_node); - } - } - } - } - - frontier = next_frontier; - } - } -} diff --git a/crates/vite_task_graph/src/specifier.rs b/crates/vite_task_graph/src/specifier.rs deleted file mode 100644 index c495dc9cb..000000000 --- a/crates/vite_task_graph/src/specifier.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::{convert::Infallible, fmt::Display, str::FromStr}; - -use serde::Serialize; -use vite_str::Str; - -/// Parsed task specifier (`"packageName#taskName"` or `"taskName"`) -/// -/// For `taskName`, `package_name` will be `None`. -/// For `#taskName`, `package_name` will be `Some("")`. It's valid to have an empty package name. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)] -pub struct TaskSpecifier { - pub package_name: Option, - pub task_name: Str, -} - -impl Display for TaskSpecifier { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(package_name) = &self.package_name { - write!(f, "{package_name}#")?; - } - write!(f, "{}", self.task_name) - } -} - -impl TaskSpecifier { - #[must_use] - pub fn parse_raw(raw_specifier: &str) -> Self { - if let Some((package_name, task_name)) = raw_specifier.rsplit_once('#') { - Self { package_name: Some(Str::from(package_name)), task_name: Str::from(task_name) } - } else { - Self { package_name: None, task_name: Str::from(raw_specifier) } - } - } -} - -impl FromStr for TaskSpecifier { - type Err = Infallible; - - fn from_str(s: &str) -> Result { - Ok(Self::parse_raw(s)) - } -} diff --git a/crates/vite_task_ipc_shared/.clippy.toml b/crates/vite_task_ipc_shared/.clippy.toml deleted file mode 120000 index c7929b369..000000000 --- a/crates/vite_task_ipc_shared/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -../../.non-vite.clippy.toml \ No newline at end of file diff --git a/crates/vite_task_ipc_shared/Cargo.toml b/crates/vite_task_ipc_shared/Cargo.toml deleted file mode 100644 index bcc90d441..000000000 --- a/crates/vite_task_ipc_shared/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "vite_task_ipc_shared" -version = "0.0.0" -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -native_str = { workspace = true } -rustc-hash = { workspace = true } -wincode = { workspace = true, features = ["derive"] } - -[lints] -workspace = true - -[lib] -doctest = false -test = false diff --git a/crates/vite_task_ipc_shared/README.md b/crates/vite_task_ipc_shared/README.md deleted file mode 100644 index 26116e79a..000000000 --- a/crates/vite_task_ipc_shared/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# vite_task_ipc_shared - -Shared IPC message types for communication between the task runner and tools. diff --git a/crates/vite_task_ipc_shared/src/lib.rs b/crates/vite_task_ipc_shared/src/lib.rs deleted file mode 100644 index 8c3f7fb15..000000000 --- a/crates/vite_task_ipc_shared/src/lib.rs +++ /dev/null @@ -1,54 +0,0 @@ -use native_str::NativeStr; -use rustc_hash::FxHashMap; -use wincode::{SchemaRead, SchemaWrite}; - -pub const IPC_ENV_NAME: &str = "VP_RUN_IPC_NAME"; - -/// Path to the Node client module that JS/TS tools `require()` to talk to -/// the runner. -/// -/// Implementation-detail leakage (`napi`, `.node`, `addon`) is intentionally -/// kept out of the name: from the consumer's point of view this is just a -/// path they can `require()`. The `NODE_` scope reserves room for a future -/// C-ABI client library advertised via its own env var for non-Node -/// consumers. -pub const NODE_CLIENT_PATH_ENV_NAME: &str = "VP_RUN_NODE_CLIENT_PATH"; - -/// IPC request frame sent by tools to the runner. -/// -/// `IgnoreInput`, `IgnoreOutput`, and `DisableCache` are fire-and-forget: -/// the runner processes them when they arrive and never writes a response. -/// `GetEnv` and `GetEnvs` are round-trips and pair with the matching response -/// types below. -/// -/// Fire-and-forget is safe because nothing in the runner observes individual -/// IPC events live — the recorded set is only consumed *after* the per-task -/// IPC driver has drained the connection, which happens after the child -/// process exits. So a tool can `flush + exit` and the server's drain phase -/// will still consume every buffered frame. -#[derive(Debug, SchemaWrite, SchemaRead)] -pub enum Request<'a> { - IgnoreInput(&'a NativeStr), - IgnoreOutput(&'a NativeStr), - GetEnv { name: &'a NativeStr, tracked: bool }, - GetEnvs { query: EnvQuery<'a>, tracked: bool }, - DisableCache, -} - -#[derive(Debug, Clone, Copy, SchemaWrite, SchemaRead)] -pub enum EnvQuery<'a> { - Glob(&'a str), - Prefix(&'a str), -} - -#[derive(Debug, SchemaWrite, SchemaRead)] -pub struct GetEnvResponse { - pub env_value: Option>, -} - -#[derive(Debug, SchemaWrite, SchemaRead)] -pub struct GetEnvsResponse { - /// Match snapshot for the glob pattern. Keys/values are byte-faithful - /// (`NativeStr`) so non-UTF-8 env values are preserved over the wire. - pub entries: FxHashMap, Box>, -} diff --git a/crates/vite_task_plan/Cargo.toml b/crates/vite_task_plan/Cargo.toml deleted file mode 100644 index 31290667d..000000000 --- a/crates/vite_task_plan/Cargo.toml +++ /dev/null @@ -1,54 +0,0 @@ -[package] -name = "vite_task_plan" -version = "0.1.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[lints] -workspace = true - -[dependencies] -anyhow = { workspace = true } -async-trait = { workspace = true } -wincode = { workspace = true, features = ["derive"] } -cow-utils = { workspace = true } -futures-util = { workspace = true } -pathdiff = { workspace = true } -petgraph = { workspace = true } -rustc-hash = { workspace = true } -serde = { workspace = true, features = ["derive"] } -sha2 = { workspace = true } -shell-escape = { workspace = true } -thiserror = { workspace = true } -tracing = { workspace = true } -vite_glob = { workspace = true } -vite_graph_ser = { workspace = true } -vite_path = { workspace = true } -vite_powershell = { workspace = true } -vite_shell = { workspace = true } -vite_str = { workspace = true } -vite_task_graph = { workspace = true } -which = { workspace = true } - -[dev-dependencies] -clap = { workspace = true, features = ["derive"] } -copy_dir = { workspace = true } -libtest-mimic = { workspace = true } -snapshot_test = { workspace = true } -serde_json = { workspace = true } -tempfile = { workspace = true } -tokio = { workspace = true, features = ["rt", "macros"] } -toml = { workspace = true } -vite_task = { workspace = true } -vite_task_bin = { workspace = true } -vite_workspace = { workspace = true } - -[[test]] -name = "plan_snapshots" -harness = false - -[lib] -doctest = false diff --git a/crates/vite_task_plan/README.md b/crates/vite_task_plan/README.md deleted file mode 100644 index 64ce06450..000000000 --- a/crates/vite_task_plan/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# vite_task_plan - -Execution planning layer for the vite-task monorepo task runner. This crate converts abstract task definitions from the task graph into concrete execution plans ready for execution. - -## Overview - -`vite_task_plan` sits between [`vite_task_graph`](../vite_task_graph) (which defines what tasks exist and their dependencies) and the actual task executor. It resolves all the runtime details needed to execute tasks: - -- Environment variables (fingerprinted and pass-through) -- Working directories -- Command parsing and expansion -- Process spawn configuration -- Caching metadata - -## Key Concepts - -### Execution Plan - -The main output of this crate is an [`ExecutionPlan`](src/lib.rs), which contains a **tree** of task executions with all runtime details resolved. - -```rust -let plan = ExecutionPlan::plan(plan_request, cwd, envs, callbacks).await?; -plan.root_node() // Root execution node -``` - -### Plan Requests - -There are two types of execution requests: - -1. **Query Request** - Execute tasks from the task graph (e.g., `vp run -r build`) - - Queries the task graph based on task patterns - - Builds execution graph with dependency ordering - -2. **Synthetic Request** - Execute on-the-fly tasks not in the graph (e.g., `vp lint` in a task script) - - Generated dynamically by the TaskSynthesizer - - Used for synthesized commands within task scripts - -### Execution Items - -Each task's command is parsed and split into execution items: - -- **Spawn Execution** - Spawns a child process - - Contains: resolved env vars, cwd, program/args or shell script - - Environment resolution for cache fingerprinting - -- **In-Process Execution** - Runs built-in commands in-process - - Optimizes simple commands like `echo` - - No process spawn overhead - -- **Expanded Execution** - Nested execution graph - - Commands like `vp run ...` expand into sub-graphs - - Enables composition of vp commands - -### Command Parsing - -Commands are intelligently parsed: - -```bash -# Single command -> Single spawn execution -"tsc --noEmit" - -# Multiple commands -> Multiple execution items -"tsc --noEmit && vp run test && echo Done" -# ↓ ↓ ↓ -# SpawnExecution Expanded InProcess -``` - -### Error Handling - -- **Recursion Detection** - Prevents infinite task dependency loops -- **Call Stack Tracking** - Maintains task call stack for error reporting diff --git a/crates/vite_task_plan/src/cache_metadata.rs b/crates/vite_task_plan/src/cache_metadata.rs deleted file mode 100644 index 9c2c5489f..000000000 --- a/crates/vite_task_plan/src/cache_metadata.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::{ffi::OsStr, sync::Arc}; - -use rustc_hash::FxHashMap; -use serde::Serialize; -use vite_path::RelativePathBuf; -use vite_str::{self, Str}; -use vite_task_graph::config::ResolvedGlobConfig; -use wincode::{SchemaRead, SchemaWrite}; - -use crate::envs::EnvFingerprints; -pub use crate::envs::EnvValueHash; - -/// Key to identify an execution across sessions. -#[derive(Debug, SchemaWrite, SchemaRead, Serialize)] -pub enum ExecutionCacheKey { - /// This execution is from a script of a user-defined task. - UserTask { - /// The name of the user-defined task. - task_name: Str, - /// The index of the command item in the task's command array. - command_item_index: usize, - /// The index of the execution item within the command item split by `&&`. - /// This is to distinguish multiple execution items from the same task. - and_item_index: usize, - /// Extra args provided when invoking the user-defined task (`vp [task_name] [extra_args...]`). - /// These args are appended to the last `and_item`. Non-last `and_items` don't get extra args. - extra_args: Arc<[Str]>, - /// The package path where the user-defined task is defined, relative to the workspace root. - package_path: RelativePathBuf, - }, - /// This execution is from a synthetic task directly invoked from `Session::plan_exec` API. - /// - /// The cache key is an opaque value provided by the caller. - ExecAPI(Arc<[Str]>), -} - -/// Cache information for a spawn execution. -/// -/// It only contains information needed for hitting existing cache entries pre-execution. -/// It doesn't contain any post-execution information like file fingerprints -/// (which needs actual execution and is out of scope for planning). -#[derive(Debug, Serialize)] -pub struct CacheMetadata { - /// Fingerprint for spawn execution that affects caching. - pub spawn_fingerprint: SpawnFingerprint, - - /// Key to identify an execution across sessions. - pub execution_cache_key: ExecutionCacheKey, - - /// Resolved input configuration for cache fingerprinting. - /// Used at execution time to determine what files to track. - pub input_config: ResolvedGlobConfig, - - /// Resolved output configuration for cache restoration. - /// Used at execution time to determine what output files to archive. - pub output_config: ResolvedGlobConfig, - - /// The unfiltered env context for runner-aware APIs. This is the planning - /// context's envs before spawn-env filtering, including command prefix envs - /// from this command and enclosing nested `vp run` expansions. - /// - /// It is not passed to the spawned process; that remains - /// `SpawnCommand::spawn_envs`. It is skipped in serialized plans because it - /// mirrors the ambient environment and would make snapshots noisy. - #[serde(skip)] - pub unfiltered_envs: Arc, Arc>>, -} - -/// Fingerprint for spawn execution that affects caching. -/// -/// # Environment Variable Impact on Cache -/// -/// The `envs_without_untracked` field is crucial for cache correctness: -/// - Only includes env vars explicitly declared in the task's `env` array -/// - Does NOT include untracked envs (PATH, CI, etc.) -/// - These env vars become part of the cache key -/// -/// When a task runs: -/// 1. All envs (including untracked) are available to the process -/// 2. Only declared envs affect the cache key -/// 3. If a declared env changes value, cache will miss -/// 4. If an untracked env changes, cache will still hit -/// -/// For built-in tasks (lint, build, etc): -/// - The resolver provides envs which become part of the fingerprint -/// - If resolver provides different envs between runs, cache breaks -/// - Each built-in task type must have unique task name to avoid cache collision -#[derive(SchemaWrite, SchemaRead, Debug, Serialize, PartialEq, Eq, Clone)] -pub struct SpawnFingerprint { - pub(crate) cwd: RelativePathBuf, - pub(crate) program_fingerprint: ProgramFingerprint, - pub(crate) args: Arc<[Str]>, - pub(crate) env_fingerprints: EnvFingerprints, -} - -impl SpawnFingerprint { - /// Get the environment fingerprints. - #[must_use] - pub const fn env_fingerprints(&self) -> &EnvFingerprints { - &self.env_fingerprints - } - - /// Get the program fingerprint as a debug string. - #[must_use] - pub fn program_fingerprint_debug(&self) -> Str { - vite_str::format!("{:?}", self.program_fingerprint) - } - - /// Get the command args. - #[must_use] - pub const fn args(&self) -> &Arc<[Str]> { - &self.args - } - - /// Get the working directory. - #[must_use] - pub const fn cwd(&self) -> &RelativePathBuf { - &self.cwd - } -} - -/// The program fingerprint used in `SpawnFingerprint` -#[derive(SchemaWrite, SchemaRead, Debug, Serialize, PartialEq, Eq, Clone)] -pub(crate) enum ProgramFingerprint { - /// If the program is outside the workspace, fingerprint by its name only (like `node`, `npm`, etc) - OutsideWorkspace { program_name: Str }, - - /// If the program is inside the workspace, fingerprint by its path relative to the workspace root - InsideWorkspace { relative_program_path: RelativePathBuf }, -} diff --git a/crates/vite_task_plan/src/context.rs b/crates/vite_task_plan/src/context.rs deleted file mode 100644 index 8422128f8..000000000 --- a/crates/vite_task_plan/src/context.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::{env::JoinPathsError, ffi::OsStr, ops::Range, sync::Arc}; - -use rustc_hash::FxHashMap; -use vite_path::AbsolutePath; -use vite_str::Str; -use vite_task_graph::{ - IndexedTaskGraph, TaskNodeIndex, config::ResolvedGlobalCacheConfig, query::TaskQuery, -}; - -use crate::{PlanRequestParser, path_env::prepend_path_env}; - -#[derive(Debug, thiserror::Error)] -#[error( - "Detected a recursion in task call stack: the last frame calls the {0}th frame", recursion_point + 1 -)] -pub struct TaskRecursionError { - /// The index in `task_call_stack` where the last frame recurses to. - recursion_point: usize, -} - -/// The context for planning an execution from a task. -#[derive(Debug)] -pub struct PlanContext<'a> { - /// The root path of the workspace. - workspace_path: &'a Arc, - - /// The current working directory. - cwd: Arc, - - /// The environment variables for the current execution context. - /// - /// `Arc`-shared with copy-on-write semantics: [`duplicate`](Self::duplicate) - /// and downstream consumers (`ScriptCommand::envs`) share the map; - /// mutations ([`add_envs`](Self::add_envs), - /// [`prepend_path`](Self::prepend_path)) clone only when the map is - /// currently shared. - envs: Arc, Arc>>, - - /// The callbacks for loading task graphs and parsing commands. - callbacks: &'a mut (dyn PlanRequestParser + 'a), - - /// The current call stack of task index nodes being planned. - task_call_stack: Vec<(TaskNodeIndex, Range)>, - - /// The extra args (`vp run task [extra_arg...]`). - /// It may come from real cli args, or commands in task scripts. - extra_args: Arc<[Str]>, - - indexed_task_graph: &'a IndexedTaskGraph, - - /// Final resolved global cache config, combining the graph's config with any CLI override. - resolved_global_cache: ResolvedGlobalCacheConfig, - - /// The query that caused the current expansion. - /// Used by the skip rule to detect and skip duplicate nested expansions. - parent_query: Arc, -} - -impl<'a> PlanContext<'a> { - pub fn new( - workspace_path: &'a Arc, - cwd: Arc, - envs: Arc, Arc>>, - callbacks: &'a mut (dyn PlanRequestParser + 'a), - indexed_task_graph: &'a IndexedTaskGraph, - resolved_global_cache: ResolvedGlobalCacheConfig, - parent_query: Arc, - ) -> Self { - Self { - workspace_path, - cwd, - envs, - callbacks, - task_call_stack: Vec::new(), - indexed_task_graph, - extra_args: Arc::default(), - resolved_global_cache, - parent_query, - } - } - - pub const fn envs(&self) -> &Arc, Arc>> { - &self.envs - } - - /// Check if adding the given task node index would create a recursion in the call stack. - pub fn check_recursion( - &self, - task_node_index: TaskNodeIndex, - ) -> Result<(), TaskRecursionError> { - if let Some(recursion_start) = - self.task_call_stack.iter().position(|(idx, _)| *idx == task_node_index) - { - return Err(TaskRecursionError { recursion_point: recursion_start }); - } - Ok(()) - } - - pub const fn indexed_task_graph(&self) -> &'a IndexedTaskGraph { - self.indexed_task_graph - } - - pub const fn workspace_path(&self) -> &Arc { - self.workspace_path - } - - /// Push a new frame onto the task call stack. - pub fn push_stack_frame(&mut self, task_node_index: TaskNodeIndex, command_span: Range) { - self.task_call_stack.push((task_node_index, command_span)); - } - - pub fn callbacks(&mut self) -> &mut (dyn PlanRequestParser + '_) { - self.callbacks - } - - pub fn prepend_path(&mut self, path_to_prepend: &AbsolutePath) -> Result<(), JoinPathsError> { - prepend_path_env(Arc::make_mut(&mut self.envs), path_to_prepend) - } - - pub fn add_envs( - &mut self, - new_envs: impl Iterator, impl AsRef)>, - ) { - // Don't touch the Arc for an empty iterator — `make_mut` would clone - // the shared map for nothing (most commands have no prefix envs). - let mut new_envs = new_envs.peekable(); - if new_envs.peek().is_none() { - return; - } - let envs = Arc::make_mut(&mut self.envs); - for (key, value) in new_envs { - envs.insert(Arc::from(key.as_ref()), Arc::from(value.as_ref())); - } - } - - pub const fn extra_args(&self) -> &Arc<[Str]> { - &self.extra_args - } - - pub fn set_extra_args(&mut self, extra_args: Arc<[Str]>) { - self.extra_args = extra_args; - } - - pub const fn resolved_global_cache(&self) -> &ResolvedGlobalCacheConfig { - &self.resolved_global_cache - } - - pub const fn set_resolved_global_cache(&mut self, config: ResolvedGlobalCacheConfig) { - self.resolved_global_cache = config; - } - - pub fn parent_query(&self) -> &TaskQuery { - &self.parent_query - } - - pub fn set_parent_query(&mut self, query: Arc) { - self.parent_query = query; - } - - /// Returns the task currently being expanded (whose command triggered a nested query). - /// This is the last task on the call stack, or `None` at the top level. - pub fn expanding_task(&self) -> Option { - self.task_call_stack.last().map(|(idx, _)| *idx) - } - - pub fn duplicate(&mut self) -> PlanContext<'_> { - PlanContext { - workspace_path: self.workspace_path, - cwd: Arc::clone(&self.cwd), - envs: Arc::clone(&self.envs), - callbacks: self.callbacks, - task_call_stack: self.task_call_stack.clone(), - indexed_task_graph: self.indexed_task_graph, - extra_args: Arc::clone(&self.extra_args), - resolved_global_cache: self.resolved_global_cache, - parent_query: Arc::clone(&self.parent_query), - } - } -} diff --git a/crates/vite_task_plan/src/envs.rs b/crates/vite_task_plan/src/envs.rs deleted file mode 100644 index 0dcfd0a99..000000000 --- a/crates/vite_task_plan/src/envs.rs +++ /dev/null @@ -1,555 +0,0 @@ -use std::{collections::BTreeMap, ffi::OsStr, fmt, sync::Arc}; - -use rustc_hash::FxHashMap; -use serde::{Serialize, Serializer}; -use sha2::{Digest as _, Sha256}; -use vite_glob::env::EnvGlobSet; -use vite_str::Str; -use vite_task_graph::config::EnvConfig; -use wincode::{SchemaRead, SchemaWrite}; - -const SHA256_DIGEST_LEN: usize = 32; -const SHA256_PREFIX: &str = "sha256:"; - -/// SHA-256 digest of a fingerprinted environment variable value. -#[derive(SchemaWrite, SchemaRead, PartialEq, Eq, Clone, Copy)] -pub struct EnvValueHash([u8; SHA256_DIGEST_LEN]); - -impl EnvValueHash { - #[must_use] - pub fn new(value: &str) -> Self { - let digest = Sha256::digest(value.as_bytes()); - let mut bytes = [0; SHA256_DIGEST_LEN]; - bytes.copy_from_slice(&digest); - Self(bytes) - } -} - -impl fmt::LowerHex for EnvValueHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for byte in self.0 { - write!(f, "{byte:02x}")?; - } - Ok(()) - } -} - -impl fmt::Display for EnvValueHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{SHA256_PREFIX}{self:x}") - } -} - -impl fmt::Debug for EnvValueHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -// Serialization is only for plan snapshot testing; cache storage uses wincode. -impl Serialize for EnvValueHash { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.collect_str(&format_args!("{SHA256_PREFIX}{self:x}")) - } -} - -/// Environment variable fingerprints for a task execution. -/// -/// Contents of this struct are only for fingerprinting and cache key computation. -/// The actual environment variables to be passed to the execution are in -/// `SpawnCommand::spawn_envs`. -#[derive(Debug, SchemaWrite, SchemaRead, Serialize, PartialEq, Eq, Clone)] -pub struct EnvFingerprints { - /// Environment variables that should be fingerprinted for this execution. - /// - /// Use `BTreeMap` to ensure stable order. - pub fingerprinted_envs: BTreeMap, - - /// Environment variable names that should be passed through without values being fingerprinted. - /// - /// Names are still included in the fingerprint so that changes to these names can invalidate the cache. - pub untracked_env_config: Arc<[Str]>, -} - -#[derive(Debug, thiserror::Error)] -pub enum ResolveEnvError { - #[error("Failed to resolve envs with invalid glob patterns")] - GlobError { - #[source] - #[from] - glob_error: vite_glob::env::EnvGlobError, - }, - - #[error("Env value is not valid unicode: {key} = {value:?}")] - EnvValueIsNotValidUnicode { key: Str, value: Arc }, -} -impl EnvFingerprints { - /// Resolves from all available envs and env config. - /// - /// Before the call, `envs` is expected to contain all available envs. - /// After the call, it will be modified to contain only envs to be passed to the execution (fingerprinted + untracked). - /// - /// After pattern filtering, `FORCE_COLOR=1` is inserted as a fallback if - /// nothing else set it, so cached output is always colored by default. - /// Tasks that need a different value (e.g. `FORCE_COLOR=0` to suppress - /// ANSI for a misbehaving tool) can opt in to passthrough by listing - /// `FORCE_COLOR` in `env` or `untrackedEnv`. - pub fn resolve( - envs: &mut FxHashMap, Arc>, - env_config: &EnvConfig, - ) -> Result { - // Collect all envs matching fingerprinted or untracked envs in env_config - *envs = resolve_envs_with_patterns( - envs.iter(), - &env_config - .untracked_env - .iter() - .map(std::convert::AsRef::as_ref) - .chain(env_config.fingerprinted_envs.iter().map(std::convert::AsRef::as_ref)) - .collect::>(), - )?; - - // Ensure cached output is colored by default. Skipped if the user - // opted into passing `FORCE_COLOR` through (via `env` / `untrackedEnv`) - // and the parent supplied a value — in that case the user's choice - // wins, even `FORCE_COLOR=0`. - envs.entry(Arc::::from(OsStr::new("FORCE_COLOR"))) - .or_insert_with(|| Arc::::from(OsStr::new("1"))); - - // Resolve fingerprinted envs - let mut fingerprinted_envs = BTreeMap::::new(); - if !env_config.fingerprinted_envs.is_empty() { - let fingerprinted_env_patterns = EnvGlobSet::new(env_config.fingerprinted_envs.iter())?; - for (name, value) in envs.iter() { - let Some(name) = name.to_str() else { - continue; - }; - if !fingerprinted_env_patterns.is_match(name) { - continue; - } - let Some(value) = value.to_str() else { - return Err(ResolveEnvError::EnvValueIsNotValidUnicode { - key: name.into(), - value: Arc::clone(value), - }); - }; - fingerprinted_envs.insert(name.into(), EnvValueHash::new(value)); - } - } - - Ok(Self { - fingerprinted_envs, - // Save untracked_env names sorted for deterministic cache fingerprinting - untracked_env_config: { - let mut sorted: Vec = env_config.untracked_env.iter().cloned().collect(); - sorted.sort(); - sorted.into() - }, - }) - } -} - -fn resolve_envs_with_patterns<'a>( - env_vars: impl Iterator, &'a Arc)>, - patterns: &[&str], -) -> Result, Arc>, vite_glob::env::EnvGlobError> { - let patterns = EnvGlobSet::new(patterns.iter())?; - let envs: FxHashMap, Arc> = env_vars - .filter_map(|(name, value)| { - let name_str = name.as_ref().to_str()?; - if patterns.is_match(name_str) { - Some((Arc::clone(name), Arc::clone(value))) - } else { - None - } - }) - .collect(); - Ok(envs) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn create_test_envs(pairs: Vec<(&str, &str)>) -> FxHashMap, Arc> { - pairs - .into_iter() - .map(|(k, v)| (Arc::from(OsStr::new(k)), Arc::from(OsStr::new(v)))) - .collect() - } - - fn create_env_config(fingerprinted: &[&str], untracked: &[&str]) -> EnvConfig { - EnvConfig { - fingerprinted_envs: fingerprinted.iter().map(|s| Str::from(*s)).collect(), - untracked_env: untracked.iter().map(|s| Str::from(*s)).collect(), - } - } - - fn hash(value: &str) -> EnvValueHash { - EnvValueHash::new(value) - } - - #[test] - fn test_force_color_defaults_to_one_when_user_does_not_opt_in() { - // The user did not list `FORCE_COLOR` in `env` or `untrackedEnv`, so - // the parent's value is filtered out by the pattern step. The - // post-resolution fallback then inserts `FORCE_COLOR=1` so cached - // output is colored. - let mut envs = create_test_envs(vec![("PATH", "/usr/bin"), ("FORCE_COLOR", "2")]); - let env_config = create_env_config(&[], &["PATH"]); - - let _result = EnvFingerprints::resolve(&mut envs, &env_config).unwrap(); - - let force_color_value = envs - .get(OsStr::new("FORCE_COLOR")) - .expect("FORCE_COLOR should be present after resolution"); - assert_eq!(force_color_value.to_str().unwrap(), "1"); - } - - #[test] - fn test_force_color_defaults_to_one_when_absent_from_parent() { - // Parent env has no `FORCE_COLOR` at all. The fallback still inserts - // `FORCE_COLOR=1` so the child emits colored output. - let mut envs = create_test_envs(vec![("PATH", "/usr/bin")]); - let env_config = create_env_config(&[], &["PATH"]); - - let _result = EnvFingerprints::resolve(&mut envs, &env_config).unwrap(); - - assert_eq!(envs.get(OsStr::new("FORCE_COLOR")).unwrap().to_str().unwrap(), "1"); - } - - #[test] - fn test_force_color_passthrough_when_user_opts_in_via_untracked() { - // If the user lists `FORCE_COLOR` in `untrackedEnv`, the parent's - // value passes through verbatim and the fallback is skipped. - let mut envs = create_test_envs(vec![("FORCE_COLOR", "0")]); - let env_config = create_env_config(&[], &["FORCE_COLOR"]); - - let result = EnvFingerprints::resolve(&mut envs, &env_config).unwrap(); - - assert_eq!(envs.get(OsStr::new("FORCE_COLOR")).unwrap().to_str().unwrap(), "0"); - assert!(!result.fingerprinted_envs.contains_key("FORCE_COLOR")); - } - - #[test] - fn test_force_color_passthrough_when_user_opts_in_via_fingerprinted() { - // If the user lists `FORCE_COLOR` in `env` (fingerprinted), the - // parent's value passes through and is recorded in the cache key. - let mut envs = create_test_envs(vec![("FORCE_COLOR", "3")]); - let env_config = create_env_config(&["FORCE_COLOR"], &[]); - - let result = EnvFingerprints::resolve(&mut envs, &env_config).unwrap(); - - assert_eq!(envs.get(OsStr::new("FORCE_COLOR")).unwrap().to_str().unwrap(), "3"); - assert_eq!(result.fingerprinted_envs.get("FORCE_COLOR").copied(), Some(hash("3"))); - } - - #[test] - fn test_force_color_fallback_fingerprinted_when_opted_in_but_parent_absent() { - // User opts in to `FORCE_COLOR` as fingerprinted, but parent has no - // value. The fallback supplies `1`, and because the fingerprint scan - // runs after the fallback, `1` is recorded in the cache key — keeping - // the fingerprint consistent with what the child actually sees. - let mut envs = create_test_envs(vec![]); - let env_config = create_env_config(&["FORCE_COLOR"], &[]); - - let result = EnvFingerprints::resolve(&mut envs, &env_config).unwrap(); - - assert_eq!(envs.get(OsStr::new("FORCE_COLOR")).unwrap().to_str().unwrap(), "1"); - assert_eq!(result.fingerprinted_envs.get("FORCE_COLOR").copied(), Some(hash("1"))); - } - - #[test] - #[cfg(unix)] - fn test_task_envs_stable_ordering() { - // Create env config with multiple envs - let env_config = create_env_config( - &["ZEBRA_VAR", "ALPHA_VAR", "MIDDLE_VAR", "BETA_VAR", "NOT_EXISTS_VAR", "APP?_*"], - &["PATH", "HOME", "VSCODE_VAR", "OXLINT_*"], - ); - - // Create mock environment variables - let mock_envs = vec![ - ("ZEBRA_VAR", "zebra_value"), - ("ALPHA_VAR", "alpha_value"), - ("MIDDLE_VAR", "middle_value"), - ("BETA_VAR", "beta_value"), - ("VSCODE_VAR", "vscode_value"), - ("APP1_TOKEN", "app1_token"), - ("APP2_TOKEN", "app2_token"), - ("APP1_NAME", "app1_value"), - ("APP2_NAME", "app2_value"), - ("APP1_PASSWORD", "app1_password"), - ("OXLINT_TSGOLINT_PATH", "/path/to/oxlint_tsgolint"), - ("PATH", "/usr/bin"), - ("HOME", "/home/user"), - ]; - - // Resolve envs multiple times - let mut envs1 = create_test_envs(mock_envs.clone()); - let mut envs2 = create_test_envs(mock_envs.clone()); - let mut envs3 = create_test_envs(mock_envs.clone()); - - let result1 = EnvFingerprints::resolve(&mut envs1, &env_config).unwrap(); - let result2 = EnvFingerprints::resolve(&mut envs2, &env_config).unwrap(); - let result3 = EnvFingerprints::resolve(&mut envs3, &env_config).unwrap(); - - // Convert to vecs for comparison (BTreeMap already maintains stable ordering) - let fingerprinted_envs1: Vec<_> = result1.fingerprinted_envs.iter().collect(); - let fingerprinted_envs2: Vec<_> = result2.fingerprinted_envs.iter().collect(); - let fingerprinted_envs3: Vec<_> = result3.fingerprinted_envs.iter().collect(); - - // Verify all resolutions produce the same result - assert_eq!(fingerprinted_envs1, fingerprinted_envs2); - assert_eq!(fingerprinted_envs2, fingerprinted_envs3); - - // Verify all expected variables are present - assert_eq!(fingerprinted_envs1.len(), 9); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "ALPHA_VAR")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "BETA_VAR")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "MIDDLE_VAR")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "ZEBRA_VAR")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "APP1_NAME")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "APP2_NAME")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "APP1_PASSWORD")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "APP1_TOKEN")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "APP2_TOKEN")); - - // All fingerprinted env values should be hashed. - let password = result1.fingerprinted_envs.get("APP1_PASSWORD").unwrap(); - assert_eq!(*password, hash("app1_password")); - let app_name = result1.fingerprinted_envs.get("APP1_NAME").unwrap(); - assert_eq!(*app_name, hash("app1_value")); - - // Verify untracked envs are present in envs - assert!(envs1.contains_key(OsStr::new("VSCODE_VAR"))); - assert!(envs1.contains_key(OsStr::new("PATH"))); - assert!(envs1.contains_key(OsStr::new("HOME"))); - assert!(envs1.contains_key(OsStr::new("OXLINT_TSGOLINT_PATH"))); - } - - #[test] - #[cfg(unix)] - fn test_unix_env_case_sensitive() { - // Test that Unix environment variable matching is case-sensitive - // Create env config with envs in different cases - let env_config = create_env_config(&["TEST_VAR", "test_var", "Test_Var"], &[]); - - // Create mock environment variables with different cases - let mut envs = create_test_envs(vec![ - ("TEST_VAR", "uppercase"), - ("test_var", "lowercase"), - ("Test_Var", "mixed"), - ]); - - let result = EnvFingerprints::resolve(&mut envs, &env_config).unwrap(); - let fingerprinted_envs = &result.fingerprinted_envs; - - // On Unix, all three should be treated as separate variables - assert_eq!( - fingerprinted_envs.len(), - 3, - "Unix should treat different cases as different variables" - ); - - assert_eq!(fingerprinted_envs.get("TEST_VAR").copied(), Some(hash("uppercase"))); - assert_eq!(fingerprinted_envs.get("test_var").copied(), Some(hash("lowercase"))); - assert_eq!(fingerprinted_envs.get("Test_Var").copied(), Some(hash("mixed"))); - } - - #[test] - #[cfg(windows)] - fn test_windows_env_case_insensitive() { - let env_config = create_env_config( - &["ZEBRA_VAR", "ALPHA_VAR", "MIDDLE_VAR", "BETA_VAR", "NOT_EXISTS_VAR", "APP?_*"], - &["Path", "VSCODE_VAR"], - ); - - let mock_envs = vec![ - ("ZEBRA_VAR", "zebra_value"), - ("ALPHA_VAR", "alpha_value"), - ("MIDDLE_VAR", "middle_value"), - ("BETA_VAR", "beta_value"), - ("VSCODE_VAR", "vscode_value"), - ("app1_name", "app1_value"), - ("app2_name", "app2_value"), - ("Path", "C:\\Windows\\System32"), - ]; - - let mut envs1 = create_test_envs(mock_envs.clone()); - let mut envs2 = create_test_envs(mock_envs.clone()); - let mut envs3 = create_test_envs(mock_envs.clone()); - - let result1 = EnvFingerprints::resolve(&mut envs1, &env_config).unwrap(); - let result2 = EnvFingerprints::resolve(&mut envs2, &env_config).unwrap(); - let result3 = EnvFingerprints::resolve(&mut envs3, &env_config).unwrap(); - - let fingerprinted_envs1: Vec<_> = result1.fingerprinted_envs.iter().collect(); - let fingerprinted_envs2: Vec<_> = result2.fingerprinted_envs.iter().collect(); - let fingerprinted_envs3: Vec<_> = result3.fingerprinted_envs.iter().collect(); - - assert_eq!(fingerprinted_envs1, fingerprinted_envs2); - assert_eq!(fingerprinted_envs2, fingerprinted_envs3); - - assert_eq!(fingerprinted_envs1.len(), 6); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "ALPHA_VAR")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "BETA_VAR")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "MIDDLE_VAR")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "ZEBRA_VAR")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "app1_name")); - assert!(fingerprinted_envs1.iter().any(|(k, _)| k.as_str() == "app2_name")); - - // Verify untracked envs are present - assert!(envs1.contains_key(OsStr::new("VSCODE_VAR"))); - assert!(envs1.contains_key(OsStr::new("Path")) || envs1.contains_key(OsStr::new("PATH"))); - } - - // ============================================ - // New tests for changed/new logic - // ============================================ - - #[test] - fn test_btreemap_stable_fingerprint() { - // Verify BTreeMap produces identical ordering regardless of insertion order - let env_config = create_env_config(&["AAA", "ZZZ", "MMM", "BBB"], &[]); - - // Create envs in different orders - let mut envs1 = - create_test_envs(vec![("AAA", "a"), ("ZZZ", "z"), ("MMM", "m"), ("BBB", "b")]); - let mut envs2 = - create_test_envs(vec![("ZZZ", "z"), ("BBB", "b"), ("AAA", "a"), ("MMM", "m")]); - - let result1 = EnvFingerprints::resolve(&mut envs1, &env_config).unwrap(); - let result2 = EnvFingerprints::resolve(&mut envs2, &env_config).unwrap(); - - // Both should produce identical iteration order due to BTreeMap - let keys1: Vec<_> = result1.fingerprinted_envs.keys().collect(); - let keys2: Vec<_> = result2.fingerprinted_envs.keys().collect(); - - assert_eq!(keys1, keys2); - // BTreeMap should be sorted alphabetically - assert_eq!(keys1, vec!["AAA", "BBB", "MMM", "ZZZ"]); - } - - #[test] - fn test_untracked_env_names_stored() { - let env_config = create_env_config(&["BUILD_MODE"], &["PATH", "HOME", "CI"]); - - let mut envs = create_test_envs(vec![ - ("BUILD_MODE", "production"), - ("PATH", "/usr/bin"), - ("HOME", "/home/user"), - ("CI", "true"), - ]); - - let result = EnvFingerprints::resolve(&mut envs, &env_config).unwrap(); - - // Verify untracked_env names are stored - assert_eq!(result.untracked_env_config.len(), 3); - assert!(result.untracked_env_config.iter().any(|s| s.as_str() == "PATH")); - assert!(result.untracked_env_config.iter().any(|s| s.as_str() == "HOME")); - assert!(result.untracked_env_config.iter().any(|s| s.as_str() == "CI")); - } - - #[test] - fn test_envs_mutated_after_resolve() { - // Include some envs that should be filtered out - let env_config = create_env_config(&["KEEP_THIS"], &["PASS_THROUGH"]); - - let mut envs = create_test_envs(vec![ - ("KEEP_THIS", "kept"), - ("PASS_THROUGH", "passed"), - ("FILTER_OUT", "filtered"), - ("ANOTHER_FILTERED", "also filtered"), - ]); - - let _result = EnvFingerprints::resolve(&mut envs, &env_config).unwrap(); - - // envs should only contain fingerprinted + untracked envs (plus auto-added ones) - assert!(envs.contains_key(OsStr::new("KEEP_THIS"))); - assert!(envs.contains_key(OsStr::new("PASS_THROUGH"))); - assert!(!envs.contains_key(OsStr::new("FILTER_OUT"))); - assert!(!envs.contains_key(OsStr::new("ANOTHER_FILTERED"))); - } - - #[test] - #[cfg(unix)] - fn test_error_env_value_not_valid_unicode() { - use std::os::unix::ffi::OsStrExt; - - let env_config = create_env_config(&["INVALID_UTF8"], &[]); - - // Create invalid UTF-8 sequence - let invalid_utf8 = OsStr::from_bytes(&[0xff, 0xfe]); - let mut envs: FxHashMap, Arc> = - std::iter::once((Arc::from(OsStr::new("INVALID_UTF8")), Arc::from(invalid_utf8))) - .collect(); - - let result = EnvFingerprints::resolve(&mut envs, &env_config); - - assert!(result.is_err()); - match result.unwrap_err() { - ResolveEnvError::EnvValueIsNotValidUnicode { key, .. } => { - assert_eq!(key.as_str(), "INVALID_UTF8"); - } - other @ ResolveEnvError::GlobError { .. } => { - panic!("Expected EnvValueIsNotValidUnicode, got {other:?}") - } - } - } - - #[test] - fn test_all_fingerprinted_envs_are_hashed() { - let env_config = create_env_config( - &["API_KEY", "MY_SECRET", "AUTH_TOKEN", "DB_PASSWORD", "NORMAL_VAR"], - &[], - ); - - let mut envs = create_test_envs(vec![ - ("API_KEY", "secret_key_123"), - ("MY_SECRET", "secret_value"), - ("AUTH_TOKEN", "token_abc"), - ("DB_PASSWORD", "password123"), - ("NORMAL_VAR", "normal_value"), - ]); - - let result = EnvFingerprints::resolve(&mut envs, &env_config).unwrap(); - - assert_eq!(result.fingerprinted_envs.get("API_KEY").copied(), Some(hash("secret_key_123"))); - assert_eq!(result.fingerprinted_envs.get("MY_SECRET").copied(), Some(hash("secret_value"))); - assert_eq!(result.fingerprinted_envs.get("AUTH_TOKEN").copied(), Some(hash("token_abc"))); - assert_eq!( - result.fingerprinted_envs.get("DB_PASSWORD").copied(), - Some(hash("password123")) - ); - assert_eq!( - result.fingerprinted_envs.get("NORMAL_VAR").copied(), - Some(hash("normal_value")) - ); - } - - #[test] - fn test_playwright_env_untracked() { - // Verify PLAYWRIGHT_* pattern matches Playwright environment variables - let env_config = create_env_config(&[], &["PLAYWRIGHT_*"]); - - let mut envs = create_test_envs(vec![ - ("PLAYWRIGHT_BROWSERS_PATH", "/custom/browsers"), - ("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "1"), - ("PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH", "/path/to/chromium"), - ("OTHER_VAR", "should_be_filtered"), - ]); - - let _result = EnvFingerprints::resolve(&mut envs, &env_config).unwrap(); - - // PLAYWRIGHT_* envs should be passed through - assert!(envs.contains_key(OsStr::new("PLAYWRIGHT_BROWSERS_PATH"))); - assert!(envs.contains_key(OsStr::new("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD"))); - assert!(envs.contains_key(OsStr::new("PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH"))); - // Non-matching env should be filtered out - assert!(!envs.contains_key(OsStr::new("OTHER_VAR"))); - } -} diff --git a/crates/vite_task_plan/src/error.rs b/crates/vite_task_plan/src/error.rs deleted file mode 100644 index 674f6dd32..000000000 --- a/crates/vite_task_plan/src/error.rs +++ /dev/null @@ -1,178 +0,0 @@ -#[expect( - clippy::disallowed_types, - reason = "Arc is used for non-UTF-8 path data in error types" -)] -use std::path::Path; -use std::{env::JoinPathsError, ffi::OsStr, sync::Arc}; - -use vite_path::{AbsolutePath, relative::InvalidPathDataError}; -use vite_str::Str; -use vite_task_graph::display::TaskDisplay; - -use crate::{context::TaskRecursionError, envs::ResolveEnvError}; - -#[derive(Debug, thiserror::Error)] -pub enum CdCommandError { - #[error("No home directory found for 'cd' command with no arguments")] - NoHomeDirectory, - - #[error("Too many args for 'cd' command")] - TooManyArgs, -} - -#[derive(Debug, thiserror::Error)] -pub struct WhichError { - pub program: Arc, - pub path_env: Option>, - pub cwd: Arc, - #[source] - pub error: which::Error, -} -impl std::fmt::Display for WhichError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Failed to find executable {} under cwd {} with ", - self.program.display(), - self.cwd.as_path().display() - )?; - if let Some(path_env) = &self.path_env { - write!(f, "PATH: {}", path_env.display())?; - } else { - write!(f, "No PATH")?; - } - Ok(()) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum PathFingerprintErrorKind { - #[error("Path {path:?} is outside of the workspace {workspace_path:?}")] - PathOutsideWorkspace { path: Arc, workspace_path: Arc }, - #[error("Path {path:?} contains characters that make it non-portable")] - NonPortableRelativePath { - #[expect(clippy::disallowed_types, reason = "path may contain non-UTF-8 data")] - path: Arc, - #[source] - error: InvalidPathDataError, - }, -} - -#[derive(Debug)] -pub enum PathType { - Cwd, - Program, - PackagePath, -} -impl std::fmt::Display for PathType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Cwd => write!(f, "current working directory"), - Self::Program => write!(f, "program path"), - Self::PackagePath => write!(f, "package path"), - } - } -} - -#[derive(Debug, thiserror::Error)] -#[error("Failed to fingerprint {path_type}")] -pub struct PathFingerprintError { - pub path_type: PathType, - #[source] - pub kind: PathFingerprintErrorKind, -} - -/// Errors that can occur when planning a specific execution from a task. -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("Failed to plan tasks from `{command}` in task {task_display}")] - NestPlan { - task_display: TaskDisplay, - command: Str, - #[source] - error: Box, - }, - - #[error(transparent)] - PackageQueryResolve(#[from] vite_task_graph::query::PackageQueryResolveError), - - #[error("Failed to load task graph")] - TaskGraphLoad( - #[source] - #[from] - vite_task_graph::TaskGraphLoadError, - ), - - #[error("Failed to execute 'cd' command")] - CdCommand( - #[source] - #[from] - CdCommandError, - ), - - #[error(transparent)] - ProgramNotFound(#[from] WhichError), - - #[error(transparent)] - PathFingerprint(#[from] PathFingerprintError), - - #[error(transparent)] - TaskRecursionDetected(#[from] TaskRecursionError), - - #[error("Invalid vite task command: {program} with args {args:?} under cwd {cwd:?}")] - ParsePlanRequest { - program: Str, - args: Arc<[Str]>, - cwd: Arc, - #[source] - error: anyhow::Error, - }, - - #[error("Failed to add node_modules/.bin to PATH environment variable")] - AddNodeModulesBinPath { - #[source] - join_paths_error: JoinPathsError, - }, - - #[error("Failed to resolve environment variables")] - ResolveEnv(#[source] ResolveEnvError), - - #[error("Failed to resolve task configuration")] - ResolveTaskConfig( - #[source] - #[from] - vite_task_graph::config::ResolveTaskConfigError, - ), - - #[error("No task specifier provided for 'run' command")] - MissingTaskSpecifier, - - /// No tasks matched the query in a nested `vp run` call inside a task script. - /// - /// At the top level, an empty execution graph triggers the task selector UI. - /// In a nested context there is no UI, so this is returned as an error instead. - #[error("Task \"{0}\" not found")] - NoTasksMatched(Str), - - /// One or more `--filter` expressions matched no packages and the user - /// opted into strict mode with `--fail-if-no-match`. The message echoes - /// the warning phrasing ("No packages matched the filter: ...") and joins - /// multiple unmatched sources with `, ` so the chain renderer keeps it on - /// a single line. - #[error( - "No packages matched the filter: {}", - sources.iter().map(vite_str::Str::as_str).collect::>().join(", ") - )] - NoPackagesMatched { sources: Vec }, - - #[error("Invalid value for VP_RUN_CONCURRENCY_LIMIT: {0:?}")] - InvalidConcurrencyLimitEnv(Arc), - - /// A cycle was detected in the task dependency graph during planning. - /// - /// This is caught by `AcyclicGraph::try_from_graph`, which validates that the - /// graph is a DAG. The contained path lists all tasks forming the cycle - /// as a closed loop (the first and last elements are the same). - #[error("Cycle dependency detected: {}", _0.iter().map(std::string::ToString::to_string).collect::>().join(" -> "))] - CycleDependencyDetected(Vec), -} diff --git a/crates/vite_task_plan/src/execution_graph.rs b/crates/vite_task_plan/src/execution_graph.rs deleted file mode 100644 index 4e8b94d90..000000000 --- a/crates/vite_task_plan/src/execution_graph.rs +++ /dev/null @@ -1,532 +0,0 @@ -use std::ops::{Deref, Index}; - -use petgraph::{ - graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}, - visit::{DfsEvent, depth_first_search}, -}; -use rustc_hash::FxHashMap; -use serde::{Serialize, Serializer}; - -use crate::TaskExecution; - -/// newtype of `DefaultIx` for indices in execution graphs -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ExecutionIx(DefaultIx); -// SAFETY: ExecutionIx is a newtype over DefaultIx which already implements IndexType correctly -unsafe impl IndexType for ExecutionIx { - fn new(x: usize) -> Self { - Self(DefaultIx::new(x)) - } - - fn index(&self) -> usize { - self.0.index() - } - - fn max() -> Self { - Self(::max()) - } -} - -pub type ExecutionNodeIndex = NodeIndex; -pub type ExecutionEdgeIndex = EdgeIndex; - -/// The inner directed graph type used for construction and storage. -pub(crate) type InnerExecutionGraph = DiGraph; - -/// Error returned by [`AcyclicGraph::try_from_graph`] when a cycle is detected. -/// -/// Contains the full cycle path as node indices. The path is a closed loop: -/// `[n0, n1, ..., nk, n0]` representing the cycle `n0 → n1 → ... → nk → n0`. -#[derive(Debug)] -pub struct CycleError { - cycle_path: Vec>, -} - -impl CycleError { - /// Return the full cycle path as node indices. - /// - /// The path is a closed loop: `[n0, n1, ..., nk, n0]` where each consecutive - /// pair `(path[i], path[i+1])` is an edge in the graph. - #[must_use] - pub fn cycle_path(&self) -> &[NodeIndex] { - &self.cycle_path - } -} - -/// A directed acyclic graph with a compile-time guarantee (via construction-time -/// validation) that it contains no cycles. -/// -/// Unlike `petgraph::acyclic::Acyclic`, this type is `Sync` (no internal `RefCell`), -/// so it can be safely shared via `Arc` across threads. -/// -/// The type implements `Deref>`, so all read operations on the -/// inner graph (indexing, iteration, node/edge counts) work transparently. -#[derive(Debug)] -pub struct AcyclicGraph { - /// The underlying directed graph, guaranteed to be acyclic. - graph: DiGraph, -} - -impl AcyclicGraph { - /// Validate that `graph` is acyclic and wrap it in an `AcyclicGraph`. - /// - /// Uses a DFS to detect cycles. If a cycle is found, returns a [`CycleError`] - /// containing the full cycle path. The cycle detection correctly handles graphs - /// with multiple disconnected components. - /// - /// # Errors - /// - /// Returns [`CycleError`] if the graph contains a cycle. - pub fn try_from_graph(graph: DiGraph) -> Result> { - if let Some(cycle_path) = find_cycle_path(&graph) { - return Err(CycleError { cycle_path }); - } - Ok(Self { graph }) - } - - /// Return a reference to the underlying `DiGraph`. - /// - /// Useful when an API requires `&DiGraph` explicitly (e.g. `vite_graph_ser::serialize_by_key`). - #[must_use] - pub const fn inner(&self) -> &DiGraph { - &self.graph - } - - /// Build an `AcyclicGraph` from an iterator of nodes with no edges. - /// - /// Each node becomes an independent node in the graph. Since there are - /// no edges, the graph is trivially acyclic. - pub fn from_node_list(nodes: impl IntoIterator) -> Self { - let mut graph = DiGraph::default(); - for node in nodes { - graph.add_node(node); - } - Self { graph } - } - - /// Compute the topological order of node indices. - /// - /// For every edge A→B in the graph, A appears before B in the returned vector. - /// Since our edges mean "A depends on B", dependents come before their - /// dependencies. Callers that need execution order (dependencies first) should - /// iterate in reverse. - /// - /// This computes the order on each call rather than caching it. - /// - /// # Panics - /// - /// Panics if the graph contains a cycle, which should be impossible since - /// acyclicity is validated at construction time. - #[must_use] - pub fn compute_topological_order(&self) -> Vec> { - petgraph::algo::toposort(&self.graph, None) - .expect("AcyclicGraph: acyclicity validated at construction time") - } -} - -impl Default for AcyclicGraph { - /// Create an empty `AcyclicGraph` (no nodes, no edges). - /// - /// An empty graph is trivially acyclic. - fn default() -> Self { - Self { graph: DiGraph::default() } - } -} - -/// Deref to the inner `DiGraph` so that read-only graph operations -/// (`node_count()`, `node_weights()`, `node_indices()`, etc.) -/// work transparently on `AcyclicGraph`. -impl Deref for AcyclicGraph { - type Target = DiGraph; - - fn deref(&self) -> &Self::Target { - &self.graph - } -} - -/// Explicit `NodeIndex` indexing so that `graph[node_ix]` continues to work -/// even when downstream crates add their own `Index` impls on `AcyclicGraph` -/// (a direct `Index` impl shadows any `Index` that would be found through `Deref`). -impl Index> for AcyclicGraph { - type Output = N; - - fn index(&self, index: NodeIndex) -> &Self::Output { - &self.graph[index] - } -} - -impl Serialize for AcyclicGraph { - fn serialize(&self, serializer: S) -> Result { - vite_graph_ser::serialize_by_key(&self.graph, serializer) - } -} - -/// The default concurrency limit for task execution within a single graph level. -pub const DEFAULT_CONCURRENCY_LIMIT: usize = 4; - -/// An execution graph with a per-level concurrency limit. -/// -/// Wraps an [`AcyclicGraph`] of task executions together with the maximum number -/// of tasks that may run concurrently within this graph level. Nested `Expanded` -/// graphs carry their own concurrency limit, enabling per-level control. -#[derive(Debug, Serialize)] -pub struct ExecutionGraph { - /// The underlying acyclic task execution graph. - pub graph: AcyclicGraph, - - /// Maximum number of tasks that can execute concurrently within this graph level. - pub concurrency_limit: usize, -} - -impl ExecutionGraph { - /// Validate that `graph` is acyclic and wrap it in an `ExecutionGraph` with - /// the given concurrency limit. - /// - /// # Errors - /// - /// Returns [`CycleError`] if the graph contains a cycle. - pub fn try_from_graph( - graph: InnerExecutionGraph, - concurrency_limit: usize, - ) -> Result> { - Ok(Self { graph: AcyclicGraph::try_from_graph(graph)?, concurrency_limit }) - } -} - -/// Find a cycle in the directed graph, returning the cycle path if one exists. -/// -/// Uses a DFS with predecessor tracking. When a back edge `u → v` is detected -/// (where `v` is an ancestor of `u` in the DFS tree), the predecessor chain from -/// `u` back to `v` is walked to reconstruct the full cycle path. -/// -/// The returned path is a closed loop: `[v, ..., u, v]` representing the cycle -/// `v → ... → u → v`. -/// -/// Handles graphs with multiple disconnected components by starting DFS from -/// all nodes via `graph.node_indices()`. -fn find_cycle_path(graph: &DiGraph) -> Option>> { - let mut predecessor = FxHashMap::, NodeIndex>::default(); - - let result: Result<(), Vec>> = - depth_first_search(graph, graph.node_indices(), |event| match event { - DfsEvent::TreeEdge(u, v) => { - predecessor.insert(v, u); - Ok(()) - } - DfsEvent::BackEdge(u, v) => { - // v is an ancestor of u in the DFS tree. - // Walk the predecessor chain from u back to v to reconstruct the cycle. - let mut path = vec![u]; - let mut current = u; - while current != v { - current = predecessor[¤t]; - path.push(current); - } - path.reverse(); // now [v, ..., u] - path.push(v); // close the cycle: [v, ..., u, v] - Err(path) - } - _ => Ok(()), - }); - - result.err() -} - -#[cfg(test)] -#[expect(clippy::many_single_char_names, reason = "short names are clear for graph test nodes")] -mod tests { - use petgraph::graph::{DefaultIx, DiGraph, NodeIndex}; - - use super::*; - - /// Assert that `cycle_path` is a valid cycle in `graph`: - /// - first == last (closed loop) - /// - length >= 2 - /// - each consecutive pair `(path[i], path[i+1])` is an edge in the graph - fn assert_valid_cycle( - graph: &DiGraph, - cycle_path: &[NodeIndex], - ) { - assert!( - cycle_path.len() >= 2, - "cycle path must have at least 2 elements, got {cycle_path:?}" - ); - assert_eq!( - cycle_path.first(), - cycle_path.last(), - "cycle must be closed (first == last), got {cycle_path:?}" - ); - for window in cycle_path.windows(2) { - assert!( - graph.contains_edge(window[0], window[1]), - "missing edge {:?} -> {:?} in graph; cycle_path = {cycle_path:?}", - window[0], - window[1], - ); - } - } - - #[test] - fn empty_graph() { - let graph = AcyclicGraph::::default(); - assert_eq!(graph.node_count(), 0); - assert!(graph.compute_topological_order().is_empty()); - } - - #[test] - fn single_node() { - let mut g = DiGraph::<&str, ()>::new(); - let a = g.add_node("a"); - let graph = AcyclicGraph::try_from_graph(g).unwrap(); - assert_eq!(graph.node_count(), 1); - assert_eq!(graph.compute_topological_order(), vec![a]); - } - - #[test] - fn linear_chain() { - // A → B → C - let mut g = DiGraph::<&str, ()>::new(); - let a = g.add_node("a"); - let b = g.add_node("b"); - let c = g.add_node("c"); - g.add_edge(a, b, ()); - g.add_edge(b, c, ()); - - let graph = AcyclicGraph::try_from_graph(g).unwrap(); - let order = graph.compute_topological_order(); - let pos_a = order.iter().position(|&n| n == a).unwrap(); - let pos_b = order.iter().position(|&n| n == b).unwrap(); - let pos_c = order.iter().position(|&n| n == c).unwrap(); - assert!(pos_a < pos_b, "a must come before b"); - assert!(pos_b < pos_c, "b must come before c"); - } - - #[test] - fn diamond_dag() { - // A - // / \ - // B C - // \ / - // D - let mut g = DiGraph::<&str, ()>::new(); - let a = g.add_node("a"); - let b = g.add_node("b"); - let c = g.add_node("c"); - let d = g.add_node("d"); - g.add_edge(a, b, ()); - g.add_edge(a, c, ()); - g.add_edge(b, d, ()); - g.add_edge(c, d, ()); - - let graph = AcyclicGraph::try_from_graph(g).unwrap(); - let order = graph.compute_topological_order(); - let pos = |n: NodeIndex| order.iter().position(|&x| x == n).unwrap(); - assert!(pos(a) < pos(b)); - assert!(pos(a) < pos(c)); - assert!(pos(b) < pos(d)); - assert!(pos(c) < pos(d)); - } - - #[test] - fn simple_cycle() { - // A → B → A - let mut g = DiGraph::<&str, ()>::new(); - let a = g.add_node("a"); - let b = g.add_node("b"); - g.add_edge(a, b, ()); - g.add_edge(b, a, ()); - - let err = AcyclicGraph::try_from_graph(g.clone()).unwrap_err(); - let path = err.cycle_path(); - assert_valid_cycle(&g, path); - // Both nodes must appear in the cycle - let unique: rustc_hash::FxHashSet<_> = path.iter().copied().collect(); - assert!(unique.contains(&a)); - assert!(unique.contains(&b)); - } - - #[test] - fn self_loop() { - // A → A - let mut g = DiGraph::<&str, ()>::new(); - let a = g.add_node("a"); - g.add_edge(a, a, ()); - - let err = AcyclicGraph::try_from_graph(g.clone()).unwrap_err(); - let path = err.cycle_path(); - assert_eq!(path, &[a, a]); - assert_valid_cycle(&g, path); - } - - #[test] - fn three_node_cycle() { - // A → B → C → A - let mut g = DiGraph::<&str, ()>::new(); - let a = g.add_node("a"); - let b = g.add_node("b"); - let c = g.add_node("c"); - g.add_edge(a, b, ()); - g.add_edge(b, c, ()); - g.add_edge(c, a, ()); - - let err = AcyclicGraph::try_from_graph(g.clone()).unwrap_err(); - let path = err.cycle_path(); - assert_valid_cycle(&g, path); - let unique: rustc_hash::FxHashSet<_> = path.iter().copied().collect(); - assert!(unique.contains(&a)); - assert!(unique.contains(&b)); - assert!(unique.contains(&c)); - } - - #[test] - fn from_node_list_is_acyclic() { - let graph = AcyclicGraph::<&str>::from_node_list(["a", "b", "c"]); - assert_eq!(graph.node_count(), 3); - assert_eq!(graph.compute_topological_order().len(), 3); - } - - #[test] - fn compute_topological_order_valid() { - // Verify every edge A→B has A before B in the topological order - let mut g = DiGraph::::new(); - let n0 = g.add_node(0); - let n1 = g.add_node(1); - let n2 = g.add_node(2); - let n3 = g.add_node(3); - let n4 = g.add_node(4); - g.add_edge(n0, n1, ()); - g.add_edge(n0, n2, ()); - g.add_edge(n1, n3, ()); - g.add_edge(n2, n3, ()); - g.add_edge(n3, n4, ()); - - let graph = AcyclicGraph::try_from_graph(g).unwrap(); - let order = graph.compute_topological_order(); - let pos = |n: NodeIndex| order.iter().position(|&x| x == n).unwrap(); - - // Check every edge - assert!(pos(n0) < pos(n1)); - assert!(pos(n0) < pos(n2)); - assert!(pos(n1) < pos(n3)); - assert!(pos(n2) < pos(n3)); - assert!(pos(n3) < pos(n4)); - } - - #[test] - fn deref_to_inner() { - let mut g = DiGraph::<&str, ()>::new(); - let a = g.add_node("hello"); - g.add_node("world"); - - let graph = AcyclicGraph::try_from_graph(g).unwrap(); - // Deref allows calling DiGraph methods directly - assert_eq!(graph.node_count(), 2); - assert_eq!(graph.edge_count(), 0); - assert_eq!(graph[a], "hello"); - assert_eq!(graph.node_weights().count(), 2); - } - - #[test] - fn disconnected_acyclic_components() { - // Component 1: A → B - // Component 2: C → D - // No edges between components — acyclic - let mut g = DiGraph::<&str, ()>::new(); - let a = g.add_node("a"); - let b = g.add_node("b"); - let c = g.add_node("c"); - let d = g.add_node("d"); - g.add_edge(a, b, ()); - g.add_edge(c, d, ()); - - let graph = AcyclicGraph::try_from_graph(g).unwrap(); - let order = graph.compute_topological_order(); - assert_eq!(order.len(), 4); - let pos = |n: NodeIndex| order.iter().position(|&x| x == n).unwrap(); - assert!(pos(a) < pos(b)); - assert!(pos(c) < pos(d)); - } - - #[test] - fn disconnected_with_cycle_in_one_component() { - // Component 1: A → B (acyclic) - // Component 2: C → D → C (cycle) - let mut g = DiGraph::<&str, ()>::new(); - let a = g.add_node("a"); - let b = g.add_node("b"); - let c = g.add_node("c"); - let d = g.add_node("d"); - g.add_edge(a, b, ()); - g.add_edge(c, d, ()); - g.add_edge(d, c, ()); - - let err = AcyclicGraph::try_from_graph(g.clone()).unwrap_err(); - let path = err.cycle_path(); - assert_valid_cycle(&g, path); - - // The cycle path must only contain nodes from the cyclic component (c, d) - let unique: rustc_hash::FxHashSet<_> = path.iter().copied().collect(); - assert!(unique.contains(&c), "cycle path must contain c"); - assert!(unique.contains(&d), "cycle path must contain d"); - assert!(!unique.contains(&a), "cycle path must not contain a"); - assert!(!unique.contains(&b), "cycle path must not contain b"); - } - - #[test] - fn disconnected_with_cycles_in_multiple_components() { - // Component 1: A → B → A (cycle) - // Component 2: C → D → C (cycle) - let mut g = DiGraph::<&str, ()>::new(); - let a = g.add_node("a"); - let b = g.add_node("b"); - let c = g.add_node("c"); - let d = g.add_node("d"); - g.add_edge(a, b, ()); - g.add_edge(b, a, ()); - g.add_edge(c, d, ()); - g.add_edge(d, c, ()); - - let err = AcyclicGraph::try_from_graph(g.clone()).unwrap_err(); - let path = err.cycle_path(); - assert_valid_cycle(&g, path); - - // DFS detects the first cycle encountered — nodes must be from one component - let unique: rustc_hash::FxHashSet<_> = path.iter().copied().collect(); - let is_component_1 = unique.contains(&a) && unique.contains(&b); - let is_component_2 = unique.contains(&c) && unique.contains(&d); - assert!(is_component_1 || is_component_2, "cycle path must belong to one component"); - assert!(!(is_component_1 && is_component_2), "cycle path must not span components"); - } - - #[test] - fn large_graph_cycle_in_later_component() { - // Component 1: chain of 10 nodes (n0 → n1 → ... → n9), acyclic - // Component 2: X → Y → X (cycle) - let mut g = DiGraph::::new(); - - let mut chain_nodes = Vec::new(); - for i in 0..10 { - chain_nodes.push(g.add_node(i)); - } - for i in 0..9 { - g.add_edge(chain_nodes[i], chain_nodes[i + 1], ()); - } - - let x = g.add_node(100); - let y = g.add_node(101); - g.add_edge(x, y, ()); - g.add_edge(y, x, ()); - - let err = AcyclicGraph::try_from_graph(g.clone()).unwrap_err(); - let path = err.cycle_path(); - assert_valid_cycle(&g, path); - - // The cycle must be in the second component - let unique: rustc_hash::FxHashSet<_> = path.iter().copied().collect(); - assert!(unique.contains(&x), "cycle path must contain x"); - assert!(unique.contains(&y), "cycle path must contain y"); - for &chain_node in &chain_nodes { - assert!(!unique.contains(&chain_node), "cycle path must not contain chain nodes"); - } - } -} diff --git a/crates/vite_task_plan/src/in_process.rs b/crates/vite_task_plan/src/in_process.rs deleted file mode 100644 index d5ba3314c..000000000 --- a/crates/vite_task_plan/src/in_process.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::sync::Arc; - -use serde::Serialize; -use vite_path::AbsolutePath; -use vite_str::Str; - -/// The output of an in-process execution. -#[derive(Debug)] -pub struct InProcessExecutionOutput { - /// The standard output of the execution. - pub stdout: Vec, - // stderr, exit code, etc can be added later -} - -/// An in-process execution item -#[derive(Debug, Serialize)] -pub struct InProcessExecution { - kind: InProcessExecutionKind, -} - -impl InProcessExecution { - /// Execute the in-process execution and return the output. - #[must_use] - pub fn execute(&self) -> InProcessExecutionOutput { - match &self.kind { - InProcessExecutionKind::Echo { strings, trailing_newline } => { - let mut stdout = Vec::new(); - for s in strings { - stdout.extend_from_slice(s.as_bytes()); - stdout.push(b' '); - } - stdout.pop(); // remove last space - if *trailing_newline { - stdout.push(b'\n'); - } - InProcessExecutionOutput { stdout } - } - } - } -} - -/// The kind of an in-process execution. -#[derive(Debug, Serialize)] -enum InProcessExecutionKind { - /// echo command - Echo { - /// strings to print, spaced by ' ' - strings: Vec, - /// whether to print a trailing newline - trailing_newline: bool, - }, -} - -impl InProcessExecution { - pub fn get_builtin_execution( - name: &str, - mut args: impl Iterator>, - _cwd: &Arc, - ) -> Option { - match name { - "echo" => { - let mut strings = Vec::new(); - #[expect( - clippy::option_if_let_else, - reason = "side effect (push to strings) makes map_or unsuitable" - )] - let trailing_newline = if let Some(first_arg) = args.next() { - let first_arg = first_arg.as_ref(); - if first_arg == "-n" { - false - } else { - strings.push(first_arg.into()); - true - } - } else { - true - }; - strings.extend(args.map(|s| s.as_ref().into())); - Some(Self { kind: InProcessExecutionKind::Echo { strings, trailing_newline } }) - } - _ => None, - } - } -} diff --git a/crates/vite_task_plan/src/lib.rs b/crates/vite_task_plan/src/lib.rs deleted file mode 100644 index 19d343d3b..000000000 --- a/crates/vite_task_plan/src/lib.rs +++ /dev/null @@ -1,270 +0,0 @@ -pub mod cache_metadata; -mod context; -mod envs; -mod error; -pub mod execution_graph; -mod in_process; -mod path_env; -mod plan; -pub mod plan_request; -mod ps1_shim; - -use std::{collections::BTreeMap, ffi::OsStr, fmt::Debug, sync::Arc}; - -use context::PlanContext; -pub use error::Error; -pub use execution_graph::{DEFAULT_CONCURRENCY_LIMIT, ExecutionGraph}; -pub use in_process::InProcessExecution; -pub use path_env::{get_path_env, prepend_path_env}; -use plan::{ParentCacheConfig, plan_query_request, plan_synthetic_request}; -use plan_request::{CacheOverride, PlanRequest, QueryPlanRequest, SyntheticPlanRequest}; -use rustc_hash::FxHashMap; -use serde::{Serialize, ser::SerializeMap as _}; -use vite_path::AbsolutePath; -use vite_str::Str; -use vite_task_graph::{TaskGraphLoadError, display::TaskDisplay}; - -/// A resolved spawn execution. -/// -/// Unlike tasks in `vite_task_graph`, this struct contains all information needed for execution, -/// like resolved environment variables, current working directory, and additional args from cli. -#[derive(Debug, Serialize)] -pub struct SpawnExecution { - /// Cache metadata for this execution. `None` means caching is disabled. - pub cache_metadata: Option, - - /// All information about a command to be spawned - pub spawn_command: SpawnCommand, -} - -/// All information about a command to be spawned. -#[derive(Debug, Serialize)] -pub struct SpawnCommand { - /// A program with args to be executed directly - pub program_path: Arc, - - /// args to be passed to the program - pub args: Arc<[Str]>, - - /// Environment variables to set for the spawned process, including both - /// fingerprinted and untracked envs. - #[serde(serialize_with = "serialize_envs")] - pub spawn_envs: Arc, Arc>>, - - /// Current working directory - pub cwd: Arc, -} - -/// Serialize environment variables as a map from string to string for better readability. -fn serialize_envs( - envs: &BTreeMap, Arc>, - serializer: S, -) -> Result -where - S: serde::Serializer, -{ - let mut map_ser = serializer.serialize_map(Some(envs.len()))?; - for (key, value) in envs { - map_ser.serialize_entry(&key.display().to_string(), &value.display().to_string())?; - } - map_ser.end() -} - -/// Represents how a task should be executed. It's the node type for the execution graph. Each node corresponds to a task. -#[derive(Debug, Serialize)] -pub struct TaskExecution { - /// The task this execution corresponds to - pub task_display: TaskDisplay, - - /// A task's command is split by `&&` and expanded into multiple execution items. - /// - /// It contains a single item if the command has no `&&` - pub items: Vec, -} - -impl vite_graph_ser::GetKey for TaskExecution { - type Key<'a> = (&'a AbsolutePath, &'a str); - - #[expect( - clippy::disallowed_types, - reason = "vite_graph_ser::GetKey uses String in its trait definition" - )] - fn key(&self) -> Result, String> { - Ok((&self.task_display.package_path, &self.task_display.task_name)) - } -} - -#[derive(Debug, Clone, Serialize)] -pub struct ExecutionItemDisplay { - /// Human-readable display for the task this execution item corresponds to. - pub task_display: TaskDisplay, - - /// The command to be executed, including the extra args. - /// For displaying purpose only. - /// `SpawnExecution` contains the actual args for execution. - pub command: Str, - - /// The cwd when this execution item is planned. - /// This is for displaying purpose only. - /// - /// `SpawnExecution.cwd` contains the actual cwd for execution. - /// These two may differ if the task synthesizer returns a task with a different cwd. - /// - /// Hypothetically , if `vp lint-src` under cwd `packages/lib` synthesizes a task spawning `oxlint` under `packages/lib/src`. - /// The spawned process' cwd will be `packages/lib/src`, while this field will be `packages/lib`, - /// which will be displayed like `packages/lib$ vp lint-src` - pub cwd: Arc, -} - -/// An execution item, either expanded from a known vp subcommand, or a spawn execution. -#[derive(Debug, Serialize)] -pub struct ExecutionItem { - /// Human-readable display for this execution item. - pub execution_item_display: ExecutionItemDisplay, - - /// The kind of this execution item - pub kind: ExecutionItemKind, -} - -/// The kind of a leaf execution item, which cannot be expanded further. -#[derive(Debug, Serialize)] -#[expect(clippy::large_enum_variant, reason = "SpawnExecution is large but not worth boxing")] -pub enum LeafExecutionKind { - /// The execution is a spawn of a child process - Spawn(SpawnExecution), - /// The execution is done in-process by `InProcessExecution::execute()` - InProcess(InProcessExecution), -} - -/// An execution item, from a split subcommand in a task's command (`item1 && item2 && ...`). -#[derive(Debug, Serialize)] -#[expect( - clippy::large_enum_variant, - reason = "SpawnExecution in Leaf is large but not worth boxing" -)] -pub enum ExecutionItemKind { - /// Expanded from a known vp subcommand, like `vp run ...` or a synthesized task. - Expanded(ExecutionGraph), - /// A normal execution that spawns a child process, like `tsc --noEmit`. - Leaf(LeafExecutionKind), -} - -/// The callback trait for parsing plan requests from script commands. -/// See the method for details. -#[async_trait::async_trait(?Send)] -pub trait PlanRequestParser: Debug { - /// This is called for every parsable command in the task graph in order to determine how to execute it. - /// - /// `vite_task_plan` doesn't have the knowledge of how cli args should be parsed. It relies on this callback. - /// - /// The implementation can either mutate `command` or return a `PlanRequest`: - /// - If it returns `Err`, the planning will abort with the returned error. - /// - If it returns `Ok(None)`, the (potentially mutated) `command` will be spawned as a normal process. - /// - If it returns `Ok(Some(PlanRequest::Query))`, the command will be expanded as a `ExpandedExecution` with a task graph queried from the returned `TaskQuery`. - /// - If it returns `Ok(Some(PlanRequest::Synthetic))`, the command will become a `SpawnExecution` with the synthetic task. - /// - /// When a `PlanRequest` is returned, any mutations to `command` are discarded. - async fn get_plan_request( - &mut self, - command: &mut plan_request::ScriptCommand, - ) -> anyhow::Result>; -} - -#[async_trait::async_trait(?Send)] -pub trait TaskGraphLoader { - async fn load_task_graph( - &mut self, - ) -> Result<&vite_task_graph::IndexedTaskGraph, TaskGraphLoadError>; -} - -/// Output of [`plan_query`] (and the internal `plan_query_request`). -/// -/// Wraps the [`ExecutionGraph`] together with a diagnostic flag that lets the -/// CLI distinguish a Stage-1 zero-package result (a `--filter` that selected -/// nothing — succeed silently) from a Stage-2 zero-task result (packages were -/// selected but none have the requested task — surface `NoTasksMatched`). -#[derive(Debug)] -pub struct PlanResult { - pub graph: ExecutionGraph, - /// `true` when the package filter selected zero packages (Stage 1 was - /// empty). The warnings printed inside the planner already explain why. - pub no_packages_matched: bool, -} - -/// Plan a query execution: load the task graph, query it, and build the execution graph. -/// -/// # Errors -/// Returns an error if task graph loading, query, or execution planning fails. -#[tracing::instrument(level = "debug", skip_all)] -#[expect(clippy::implicit_hasher, reason = "FxHashMap is the only hasher used in this codebase")] -pub async fn plan_query( - query_plan_request: QueryPlanRequest, - workspace_path: &Arc, - cwd: &Arc, - envs: &Arc, Arc>>, - plan_request_parser: &mut (dyn PlanRequestParser + '_), - task_graph_loader: &mut (dyn TaskGraphLoader + '_), -) -> Result { - let indexed_task_graph = task_graph_loader.load_task_graph().await?; - - let resolved_global_cache = resolve_cache_with_override( - *indexed_task_graph.global_cache_config(), - query_plan_request.plan_options.cache_override, - ); - - let QueryPlanRequest { query, plan_options } = query_plan_request; - let query = Arc::new(query); - let context = PlanContext::new( - workspace_path, - Arc::clone(cwd), - Arc::clone(envs), - plan_request_parser, - indexed_task_graph, - resolved_global_cache, - Arc::clone(&query), - ); - plan_query_request(query, plan_options, context).await -} - -const fn resolve_cache_with_override( - graph_cache: vite_task_graph::config::ResolvedGlobalCacheConfig, - cache_override: CacheOverride, -) -> vite_task_graph::config::ResolvedGlobalCacheConfig { - match cache_override { - CacheOverride::ForceEnabled => { - vite_task_graph::config::ResolvedGlobalCacheConfig { scripts: true, tasks: true } - } - CacheOverride::ForceDisabled => { - vite_task_graph::config::ResolvedGlobalCacheConfig { scripts: false, tasks: false } - } - CacheOverride::None => graph_cache, - } -} - -/// Plan a synthetic task execution, returning the resolved [`SpawnExecution`] directly. -/// -/// Unlike [`plan_query`] which returns a full execution graph, synthetic executions -/// are always a single spawned process. The caller can execute it directly using -/// `execute_spawn`. -/// -/// # Errors -/// Returns an error if the program is not found or path fingerprinting fails. -#[tracing::instrument(level = "debug", skip_all)] -#[expect(clippy::result_large_err, reason = "Error is large for diagnostics")] -pub fn plan_synthetic( - workspace_path: &Arc, - cwd: &Arc, - synthetic_plan_request: SyntheticPlanRequest, - cache_key: Arc<[Str]>, -) -> Result { - let execution_cache_key = cache_metadata::ExecutionCacheKey::ExecAPI(cache_key); - plan_synthetic_request( - workspace_path, - &BTreeMap::default(), - synthetic_plan_request, - Some(execution_cache_key), - cwd, - cwd, - ParentCacheConfig::None, - ) -} diff --git a/crates/vite_task_plan/src/path_env.rs b/crates/vite_task_plan/src/path_env.rs deleted file mode 100644 index fca82d59e..000000000 --- a/crates/vite_task_plan/src/path_env.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::{ - env::{JoinPathsError, join_paths, split_paths}, - ffi::OsStr, - iter, - sync::Arc, -}; - -use rustc_hash::FxHashMap; -use vite_path::AbsolutePath; - -/// Get the PATH environment variable from the given envs map. -/// On Windows, this function performs a case-insensitive search for "PATH". -/// On Unix, it performs a case-sensitive search. -#[must_use] -#[expect(clippy::implicit_hasher, reason = "function is specific to FxHashMap")] -pub fn get_path_env(envs: &FxHashMap, Arc>) -> Option<&Arc> { - if cfg!(windows) { - // On Windows, environment variable names are case-insensitive (e.g., "PATH", "Path", "path" are all the same) - // However, Rust's HashMap keys are case-sensitive, so we need to find the existing PATH variable - // regardless of its casing. - envs.iter().find_map( - |(name, value)| { - if name.eq_ignore_ascii_case("path") { Some(value) } else { None } - }, - ) - } else { - // On Unix, environment variable names are case-sensitive - envs.get(OsStr::new("PATH")) - } -} - -/// Prepend a path to the PATH environment variable. -/// -/// # Errors -/// Returns an error if the paths cannot be joined. -#[expect(clippy::implicit_hasher, reason = "function is specific to FxHashMap")] -pub fn prepend_path_env( - envs: &mut FxHashMap, Arc>, - path_to_prepend: &AbsolutePath, -) -> Result<(), JoinPathsError> { - // Add node_modules/.bin to PATH - // On Windows, environment variable names are case-insensitive (e.g., "PATH", "Path", "path" are all the same) - // However, Rust's HashMap keys are case-sensitive, so we need to find the existing PATH variable - // regardless of its casing to avoid creating duplicate PATH entries with different casings. - // For example, if the system has "Path", we should use that instead of creating a new "PATH" entry. - let env_path = { - if cfg!(windows) - && let Some(existing_path) = envs.iter_mut().find_map(|(name, value)| { - if name.eq_ignore_ascii_case("path") { Some(value) } else { None } - }) - { - // Found existing PATH variable (with any casing), use it - existing_path - } else { - // On Unix or no existing PATH on Windows, create/get "PATH" entry - envs.entry(Arc::from(OsStr::new("PATH"))) - .or_insert_with(|| Arc::::from(OsStr::new(""))) - } - }; - - let existing_paths = split_paths(env_path); - let paths = iter::once(path_to_prepend.as_path().to_path_buf()).chain(existing_paths.filter( - // remove duplicates - |path| path != path_to_prepend.as_path(), - )); - - let new_path_value = join_paths(paths)?; - *env_path = new_path_value.into(); - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn create_test_envs(pairs: Vec<(&str, &str)>) -> FxHashMap, Arc> { - pairs - .into_iter() - .map(|(k, v)| (Arc::from(OsStr::new(k)), Arc::from(OsStr::new(v)))) - .collect() - } - - #[test] - #[cfg(windows)] - fn test_windows_path_case_insensitive_mixed_case() { - let mut envs = create_test_envs(vec![("Path", "C:\\existing\\path")]); - let path_to_prepend = - AbsolutePath::new("C:\\workspace\\packages\\app\\node_modules\\.bin").unwrap(); - - prepend_path_env(&mut envs, path_to_prepend).unwrap(); - - // Verify that the original "Path" casing is preserved, not "PATH" - assert!(envs.contains_key(OsStr::new("Path"))); - assert!(!envs.contains_key(OsStr::new("PATH"))); - - // Verify the PATH value has node_modules/.bin prepended - let path_value = envs.get(OsStr::new("Path")).unwrap(); - assert!(path_value.to_str().unwrap().contains("node_modules\\.bin")); - assert!(path_value.to_str().unwrap().contains("C:\\existing\\path")); - - // Verify no duplicate PATH entry was created - assert_eq!( - envs.keys() - .filter(|k| k.to_str().is_some_and(|s| s.eq_ignore_ascii_case("path"))) - .count(), - 1 - ); - } - - #[test] - #[cfg(windows)] - fn test_windows_path_case_insensitive_uppercase() { - let mut envs = create_test_envs(vec![("PATH", "C:\\existing\\path")]); - let path_to_prepend = - AbsolutePath::new("C:\\workspace\\packages\\app\\node_modules\\.bin").unwrap(); - - prepend_path_env(&mut envs, path_to_prepend).unwrap(); - - // Verify the PATH value has node_modules/.bin prepended - let path_value = envs.get(OsStr::new("PATH")).unwrap(); - assert!(path_value.to_str().unwrap().contains("node_modules\\.bin")); - assert!(path_value.to_str().unwrap().contains("C:\\existing\\path")); - } - - #[test] - #[cfg(windows)] - fn test_windows_path_created_when_missing() { - let mut envs = create_test_envs(vec![]); - let path_to_prepend = - AbsolutePath::new("C:\\workspace\\packages\\app\\node_modules\\.bin").unwrap(); - - prepend_path_env(&mut envs, path_to_prepend).unwrap(); - - // Verify PATH was created with only node_modules/.bin - let path_value = envs.get(OsStr::new("PATH")).unwrap(); - assert!(path_value.to_str().unwrap().contains("node_modules\\.bin")); - } - - #[test] - #[cfg(unix)] - fn test_unix_path_case_sensitive() { - let mut envs = create_test_envs(vec![("PATH", "/existing/path")]); - let path_to_prepend = - AbsolutePath::new("/workspace/packages/app/node_modules/.bin").unwrap(); - - prepend_path_env(&mut envs, path_to_prepend).unwrap(); - - // Verify "PATH" exists and the complete value has node_modules/.bin prepended - let path_value = envs.get(OsStr::new("PATH")).unwrap(); - let path_str = path_value.to_str().unwrap(); - assert!(path_str.contains("node_modules/.bin")); - assert!(path_str.contains("/existing/path")); - - // Verify that on Unix, the code uses exact "PATH" match (case-sensitive) - assert!(!envs.contains_key(OsStr::new("Path"))); - assert!(!envs.contains_key(OsStr::new("path"))); - } - - #[test] - #[cfg(unix)] - fn test_prepend_paths_removes_duplicates() { - let mut envs = create_test_envs(vec![("PATH", "/workspace/node_modules/.bin:/other/path")]); - let path_to_prepend = AbsolutePath::new("/workspace/node_modules/.bin").unwrap(); - - prepend_path_env(&mut envs, path_to_prepend).unwrap(); - - let path_value = envs.get(OsStr::new("PATH")).unwrap(); - let path_str = path_value.to_str().unwrap(); - - // Should only have one occurrence of node_modules/.bin (duplicates removed) - let node_modules_count = path_str.matches("/workspace/node_modules/.bin").count(); - assert_eq!(node_modules_count, 1, "Duplicate paths should be removed"); - } -} diff --git a/crates/vite_task_plan/src/plan.rs b/crates/vite_task_plan/src/plan.rs deleted file mode 100644 index b9455434d..000000000 --- a/crates/vite_task_plan/src/plan.rs +++ /dev/null @@ -1,1137 +0,0 @@ -#[expect( - clippy::disallowed_types, - reason = "Path is needed for cd command argument and error reporting" -)] -use std::path::Path; -use std::{ - borrow::Cow, - collections::BTreeMap, - env::home_dir, - ffi::OsStr, - sync::{Arc, LazyLock}, -}; - -use futures_util::FutureExt; -use petgraph::Direction; -use rustc_hash::FxHashMap; -use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf, relative::InvalidPathDataError}; -use vite_shell::try_parse_as_and_list; -use vite_str::Str; -use vite_task_graph::{ - TaskNodeIndex, TaskSource, - config::{ - CacheConfig, EnabledCacheConfig, ResolvedGlobConfig, ResolvedGlobalCacheConfig, - ResolvedTaskOptions, - user::{UserCacheConfig, UserTaskOptions}, - }, - query::TaskQuery, -}; - -use crate::{ - ExecutionItem, ExecutionItemDisplay, ExecutionItemKind, LeafExecutionKind, PlanContext, - SpawnCommand, SpawnExecution, TaskExecution, - cache_metadata::{CacheMetadata, ExecutionCacheKey, ProgramFingerprint, SpawnFingerprint}, - envs::{EnvFingerprints, EnvValueHash}, - error::{CdCommandError, Error, PathFingerprintError, PathFingerprintErrorKind, PathType}, - execution_graph::{ExecutionGraph, ExecutionNodeIndex, InnerExecutionGraph}, - in_process::InProcessExecution, - path_env::get_path_env, - plan_request::{ - CacheOverride, PlanOptions, PlanRequest, QueryPlanRequest, ScriptCommand, - SyntheticPlanRequest, - }, - resolve_cache_with_override, -}; - -/// Locate the executable path for a given program name in the provided envs and cwd. -fn which( - program: &Arc, - envs: &FxHashMap, Arc>, - cwd: &Arc, -) -> Result, crate::error::WhichError> { - let path_env = get_path_env(envs); - let executable_path = which::which_in(program, path_env, cwd.as_path()).map_err(|err| { - crate::error::WhichError { - program: Arc::clone(program), - path_env: path_env.map(Arc::clone), - cwd: Arc::clone(cwd), - error: err, - } - })?; - Ok(AbsolutePathBuf::new(executable_path) - .expect("path returned by which::which_in should always be absolute") - .into()) -} - -/// Compute the effective cache config for a task, applying the global cache config. -/// -/// The task graph stores per-task cache config without applying the global kill switch. -/// This function applies the global config at plan time, checking `cache.scripts` for -/// package.json scripts and `cache.tasks` for task-map entries. -fn effective_cache_config( - task_cache_config: Option<&CacheConfig>, - source: TaskSource, - resolved_global_cache: ResolvedGlobalCacheConfig, -) -> Option { - let enabled = match source { - TaskSource::PackageJsonScript => resolved_global_cache.scripts, - TaskSource::TaskConfig => resolved_global_cache.tasks, - }; - if enabled { task_cache_config.cloned() } else { None } -} - -/// - `with_hooks`: whether to look up `preX`/`postX` lifecycle hooks for this task. -/// `false` when the task itself is being executed as a hook, so that hooks are -/// never expanded more than one level deep (matching npm behavior). -#[expect(clippy::too_many_lines, reason = "sequential planning steps are clearer in one function")] -async fn plan_task_as_execution_node( - task_node_index: TaskNodeIndex, - mut context: PlanContext<'_>, - with_hooks: bool, -) -> Result { - // Check for recursions in the task call stack. - context.check_recursion(task_node_index)?; - - let task_node = &context.indexed_task_graph().task_graph()[task_node_index]; - - let package_path = context.indexed_task_graph().get_package_path_for_task(task_node_index); - // Prepend {package_path}/node_modules/.bin to PATH - let package_node_modules_bin_path = package_path.join("node_modules").join(".bin"); - if let Err(join_paths_error) = context.prepend_path(&package_node_modules_bin_path) { - return Err(Error::AddNodeModulesBinPath { join_paths_error }); - } - - let mut items = Vec::::new(); - - // Expand pre/post hooks (`preX`/`postX`) for package.json scripts. - // Hooks are never expanded more than one level deep (matching npm behavior): when planning a - // hook script, `with_hooks` is false so it won't look for its own pre/post hooks. - // Resolve the flag once before any mutable borrow of `context` (duplicate() needs &mut). - let pre_post_scripts_enabled = - with_hooks && context.indexed_task_graph().pre_post_scripts_enabled(); - let pre_hook_idx = if pre_post_scripts_enabled { - context.indexed_task_graph().get_script_hook(task_node_index, "pre") - } else { - None - }; - if let Some(pre_hook_idx) = pre_hook_idx { - let mut pre_context = context.duplicate(); - // Extra args (e.g. `vt run test --coverage`) must not be forwarded to hooks. - pre_context.set_extra_args(Arc::new([])); - let pre_execution = - Box::pin(plan_task_as_execution_node(pre_hook_idx, pre_context, false)).await?; - items.extend(pre_execution.items); - } - - // Use task's resolved cwd for display (from task config's cwd option) - let mut cwd = Arc::clone(&task_node.resolved_config.resolved_options.cwd); - - // TODO: variable expansion (https://crates.io/crates/shellexpand) BEFORE parsing - let commands = &task_node.resolved_config.commands; - for (command_item_index, command) in commands.iter().enumerate() { - let command_str = command.as_str(); - let is_last_command = command_item_index + 1 == commands.len(); - // Try to parse the command string as a list of subcommands separated by `&&` - if let Some(parsed_subcommands) = try_parse_as_and_list(command_str) { - let and_item_count = parsed_subcommands.len(); - for (and_item_index, (and_item, add_item_span)) in - parsed_subcommands.into_iter().enumerate() - { - // Duplicate the context before modifying it for each and_item - let mut context = context.duplicate(); - context.push_stack_frame(task_node_index, add_item_span.clone()); - - // This command's env context: extend the planning context with - // the command's prefix envs (`FOO=1 tool ...`). Everything that - // interprets the command reads this one context: program - // lookup, plan-request callbacks, nested-run planning, and - // cached execution metadata. The per-and-item duplicate - // above scopes the extension to this command, matching shell - // semantics (`FOO=1 a && b` does not set `FOO` for `b`). - context.add_envs(and_item.envs.iter()); - - let mut args = and_item.args; - let extra_args = if is_last_command && and_item_index == and_item_count - 1 { - // For the last and_item of the last command, append extra args from the plan context - Arc::clone(context.extra_args()) - } else { - Arc::new([]) - }; - args.extend(extra_args.iter().cloned()); - - // Handle `cd` builtin command - if and_item.program == "cd" { - #[expect( - clippy::disallowed_types, - reason = "Path is needed for std::env::home_dir return type and AbsolutePath::join" - )] - let cd_target: Cow<'_, Path> = match args.as_slice() { - // No args, go to home directory - [] => home_dir() - .ok_or(Error::CdCommand(CdCommandError::NoHomeDirectory))? - .into(), - [dir] => Path::new(dir.as_str()).into(), - _ => { - return Err(Error::CdCommand(CdCommandError::TooManyArgs)); - } - }; - cwd = cwd.join(cd_target.as_ref()).into(); - continue; - } - - // Build execution display - let execution_item_display = ExecutionItemDisplay { - command: { - let mut command = Str::from(&command_str[add_item_span.clone()]); - for arg in extra_args.iter() { - command.push(' '); - command.push_str(shell_escape::escape(arg.as_str().into()).as_ref()); - } - command - }, - cwd: Arc::clone(&cwd), - task_display: task_node.task_display.clone(), - }; - - // Check for builtin commands like `echo ...` - if let Some(builtin_execution) = - InProcessExecution::get_builtin_execution(&and_item.program, args.iter(), &cwd) - { - items.push(ExecutionItem { - execution_item_display, - kind: ExecutionItemKind::Leaf(LeafExecutionKind::InProcess( - builtin_execution, - )), - }); - continue; - } - - // Create execution cache key for this and_item - let task_execution_cache_key = ExecutionCacheKey::UserTask { - task_name: task_node.task_display.task_name.clone(), - command_item_index, - and_item_index, - extra_args: Arc::clone(&extra_args), - package_path: strip_prefix_for_cache(package_path, context.workspace_path()) - .map_err(|kind| PathFingerprintError { - kind, - path_type: PathType::PackagePath, - })?, - }; - - // Try to parse the args of an and_item to a plan request like `run -r build` - let envs = Arc::clone(context.envs()); - let mut script_command = ScriptCommand { - program: and_item.program.clone(), - args: args.into(), - envs, - cwd: Arc::clone(&cwd), - }; - let plan_request = - context.callbacks().get_plan_request(&mut script_command).await.map_err( - |error| Error::ParsePlanRequest { - program: script_command.program.clone(), - args: Arc::clone(&script_command.args), - cwd: Arc::clone(&script_command.cwd), - error, - }, - )?; - - let execution_item_kind: ExecutionItemKind = match plan_request { - // Expand task query like `vp run -r build` - Some(PlanRequest::Query(query_plan_request)) => { - // Skip rule: skip if this nested query is the same as the parent expansion. - // This handles workspace root tasks like `"build": "vp run -r build"` — - // re-entering the same query would just re-expand the same tasks. - // - // The comparison is on TaskQuery only (package_query + task_name + - // include_explicit_deps). Extra args live in PlanOptions, so - // `vp run -r build extra_arg` still matches `vp run -r build`. - // Conversely, `cd packages/a && vp run build` does NOT match a - // parent `vp run build` from root because `cd` changes the cwd, - // producing a different ContainingPackage in the PackageQuery. - if query_plan_request.query == *context.parent_query() { - continue; - } - - // Save task name before consuming the request - let task_name = query_plan_request.query.task_name.clone(); - let QueryPlanRequest { query, plan_options } = query_plan_request; - let query = Arc::new(query); - let nested_plan = - plan_query_request(Arc::clone(&query), plan_options, context) - .await - .map_err(|error| Error::NestPlan { - task_display: task_node.task_display.clone(), - command: Str::from(&command_str[add_item_span.clone()]), - error: Box::new(error), - })?; - // Two empty-graph cases: - // 1. The nested filter selected no packages (`no_packages_matched`): - // treat as a no-op so a script's `vp run --filter X build` does - // not break when X happens to match nothing. The planner has - // already printed any per-filter warnings. - // 2. Packages were selected but none have the task: there is no - // task selector in a nested context, so surface NoTasksMatched. - if nested_plan.graph.graph.node_count() == 0 - && !nested_plan.no_packages_matched - { - return Err(Error::NestPlan { - task_display: task_node.task_display.clone(), - command: Str::from(&command_str[add_item_span]), - error: Box::new(Error::NoTasksMatched(task_name)), - }); - } - ExecutionItemKind::Expanded(nested_plan.graph) - } - // Synthetic task (from CommandHandler) - Some(PlanRequest::Synthetic(synthetic_plan_request)) => { - let task_effective_cache = effective_cache_config( - task_node.resolved_config.resolved_options.cache_config.as_ref(), - task_node.source, - *context.resolved_global_cache(), - ); - let parent_cache_config = task_effective_cache - .as_ref() - .map_or(ParentCacheConfig::Disabled, |config| { - ParentCacheConfig::Inherited(config.clone()) - }); - let spawn_execution = plan_synthetic_request( - context.workspace_path(), - &and_item.envs, - synthetic_plan_request, - Some(task_execution_cache_key), - &cwd, - package_path, - parent_cache_config, - )?; - ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(spawn_execution)) - } - // Normal 3rd party tool command (like `tsc --noEmit`), using potentially mutated script_command - None => { - let program_path = which( - &OsStr::new(&script_command.program).into(), - &script_command.envs, - &script_command.cwd, - )?; - let (program_path, spawn_args) = - crate::ps1_shim::rewrite_cmd_shim_with_args( - program_path, - script_command.args, - &script_command.cwd, - context.workspace_path(), - ); - let resolved_options = ResolvedTaskOptions { - cwd: Arc::clone(&script_command.cwd), - cache_config: effective_cache_config( - task_node.resolved_config.resolved_options.cache_config.as_ref(), - task_node.source, - *context.resolved_global_cache(), - ), - }; - let spawn_execution = plan_spawn_execution( - context.workspace_path(), - Some(task_execution_cache_key), - &and_item.envs, - &resolved_options, - &script_command.envs, - program_path, - spawn_args, - )?; - ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(spawn_execution)) - } - }; - items.push(ExecutionItem { execution_item_display, kind: execution_item_kind }); - } - } else { - #[expect(clippy::disallowed_types, reason = "PathBuf needed for which fallback path")] - static SHELL_PROGRAM_PATH: LazyLock> = LazyLock::new(|| { - if cfg!(target_os = "windows") { - AbsolutePathBuf::new(which::which("cmd.exe").unwrap_or_else(|_| { - std::path::PathBuf::from("C:\\Windows\\System32\\cmd.exe") - })) - .unwrap() - .into() - } else { - AbsolutePath::new("/bin/sh").unwrap().into() - } - }); - - static SHELL_ARGS: &[&str] = - if cfg!(target_os = "windows") { &["/d", "/s", "/c"] } else { &["-c"] }; - - let mut context = context.duplicate(); - context.push_stack_frame(task_node_index, 0..command_str.len()); - - let extra_args = - if is_last_command { Arc::clone(context.extra_args()) } else { Arc::new([]) }; - - let execution_item_display = ExecutionItemDisplay { - command: command_str.into(), - cwd: Arc::clone(&cwd), - task_display: task_node.task_display.clone(), - }; - - let mut script = Str::from(command_str); - for arg in extra_args.iter() { - script.push(' '); - script.push_str(shell_escape::escape(arg.as_str().into()).as_ref()); - } - - let resolved_options = ResolvedTaskOptions { - cwd: Arc::clone(&cwd), - cache_config: effective_cache_config( - task_node.resolved_config.resolved_options.cache_config.as_ref(), - task_node.source, - *context.resolved_global_cache(), - ), - }; - let spawn_execution = plan_spawn_execution( - context.workspace_path(), - Some(ExecutionCacheKey::UserTask { - task_name: task_node.task_display.task_name.clone(), - command_item_index, - and_item_index: 0, - extra_args: Arc::clone(&extra_args), - package_path: strip_prefix_for_cache(package_path, context.workspace_path()) - .map_err(|kind| PathFingerprintError { - kind, - path_type: PathType::PackagePath, - })?, - }), - &BTreeMap::new(), - &resolved_options, - context.envs(), - Arc::clone(&*SHELL_PROGRAM_PATH), - SHELL_ARGS.iter().map(|s| Str::from(*s)).chain(std::iter::once(script)).collect(), - )?; - items.push(ExecutionItem { - execution_item_display, - kind: ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(spawn_execution)), - }); - } - } - - // Expand post-hook (`postX`) for package.json scripts. - let post_hook_idx = if pre_post_scripts_enabled { - context.indexed_task_graph().get_script_hook(task_node_index, "post") - } else { - None - }; - if let Some(post_hook_idx) = post_hook_idx { - let mut post_context = context.duplicate(); - // Extra args must not be forwarded to hooks. - post_context.set_extra_args(Arc::new([])); - let post_execution = - Box::pin(plan_task_as_execution_node(post_hook_idx, post_context, false)).await?; - items.extend(post_execution.items); - } - - Ok(TaskExecution { task_display: task_node.task_display.clone(), items }) -} - -/// Cache configuration inherited from the parent task that contains a synthetic command. -/// -/// When a synthetic task (e.g., `vp lint` expanding to `oxlint`) appears inside a -/// user-defined task's script, the parent task's cache configuration should constrain -/// the synthetic task's caching behavior. -pub enum ParentCacheConfig { - /// No parent task (top-level synthetic command like `vp lint` run directly). - /// The synthetic task uses its own default cache configuration. - None, - - /// Parent task has caching disabled (`cache: false` or `cache.scripts` not enabled). - /// The synthetic task should also have caching disabled. - Disabled, - - /// Parent task has caching enabled with this configuration. - /// The synthetic task inherits this config, merged with its own additions. - Inherited(CacheConfig), -} - -/// Resolves the effective cache configuration for a synthetic task by combining -/// the parent task's cache config with the synthetic command's own additions. -/// -/// Synthetic tasks (e.g., `vp lint` → `oxlint`) may declare their own cache-related -/// env requirements (e.g., `untracked_env` for env-test). When a parent task -/// exists, its cache config takes precedence: -/// - If the parent disables caching, the synthetic task is also uncached. -/// - If the parent enables caching but the synthetic disables it, caching is disabled. -/// - If both parent and synthetic enable caching, the synthetic inherits the parent's -/// env config and merges in any additional envs the synthetic command needs. -/// - If there is no parent (top-level invocation), the synthetic task's own -/// [`UserCacheConfig`] is resolved with defaults. -#[expect(clippy::result_large_err, reason = "Error is large for diagnostics")] -fn resolve_synthetic_cache_config( - parent: ParentCacheConfig, - synthetic_cache_config: UserCacheConfig, - package_dir: &AbsolutePath, - workspace_path: &AbsolutePath, -) -> Result, Error> { - match parent { - ParentCacheConfig::None => { - // Top-level: resolve from synthetic's own config - Ok(ResolvedTaskOptions::resolve( - UserTaskOptions { - cache_config: synthetic_cache_config, - cwd_relative_to_package: None, - depends_on: None, - }, - &package_dir.into(), - workspace_path, - ) - .map_err(Error::ResolveTaskConfig)? - .cache_config) - } - ParentCacheConfig::Disabled => Ok(Option::None), - ParentCacheConfig::Inherited(mut parent_config) => { - // Cache is enabled only if both parent and synthetic want it. - // Merge synthetic's additions into parent's config. - Ok(match synthetic_cache_config { - UserCacheConfig::Disabled { .. } => Option::None, - UserCacheConfig::Enabled { enabled_cache_config, .. } => { - let EnabledCacheConfig { env, untracked_env, input, output } = - enabled_cache_config; - parent_config.env_config.fingerprinted_envs.extend(env.unwrap_or_default()); - parent_config - .env_config - .untracked_env - .extend(untracked_env.unwrap_or_default()); - - if let Some(input) = input { - let synthetic_input = ResolvedGlobConfig::from_user_config( - Some(&input), - package_dir, - workspace_path, - ) - .map_err(Error::ResolveTaskConfig)?; - // Merge globs but preserve the parent's includes_auto — - // the user's explicit choice takes precedence - // over the synthetic handler's preference. - parent_config - .input_config - .positive_globs - .extend(synthetic_input.positive_globs); - parent_config - .input_config - .negative_globs - .extend(synthetic_input.negative_globs); - } - - if let Some(output) = output { - let synthetic_output = ResolvedGlobConfig::from_user_output_config( - Some(&output), - package_dir, - workspace_path, - ) - .map_err(Error::ResolveTaskConfig)?; - parent_config - .output_config - .positive_globs - .extend(synthetic_output.positive_globs); - parent_config - .output_config - .negative_globs - .extend(synthetic_output.negative_globs); - } - - Some(parent_config) - } - }) - } - } -} - -#[expect(clippy::result_large_err, reason = "Error is large for diagnostics")] -pub fn plan_synthetic_request( - workspace_path: &Arc, - prefix_envs: &BTreeMap, - synthetic_plan_request: SyntheticPlanRequest, - execution_cache_key: Option, - cwd: &Arc, - package_dir: &AbsolutePath, - parent_cache_config: ParentCacheConfig, -) -> Result { - let SyntheticPlanRequest { program, args, cache_config, envs } = synthetic_plan_request; - - let program_path = which(&program, &envs, cwd)?; - let (program_path, args) = - crate::ps1_shim::rewrite_cmd_shim_with_args(program_path, args, cwd, workspace_path); - let resolved_cache_config = resolve_synthetic_cache_config( - parent_cache_config, - cache_config, - package_dir, - workspace_path, - )?; - let resolved_options = - ResolvedTaskOptions { cwd: Arc::clone(cwd), cache_config: resolved_cache_config }; - - plan_spawn_execution( - workspace_path, - execution_cache_key, - prefix_envs, - &resolved_options, - &envs, - program_path, - args, - ) -} - -fn strip_prefix_for_cache( - path: &Arc, - workspace_path: &Arc, -) -> Result { - match path.strip_prefix(workspace_path) { - Ok(Some(rel_path)) => Ok(rel_path), - Ok(None) => Err(PathFingerprintErrorKind::PathOutsideWorkspace { - path: Arc::clone(path), - workspace_path: Arc::clone(workspace_path), - }), - Err(err) => Err(PathFingerprintErrorKind::NonPortableRelativePath { - path: err.stripped_path.into(), - error: err.invalid_path_data_error, - }), - } -} - -#[expect(clippy::result_large_err, reason = "Error is large for diagnostics")] -#[expect( - clippy::needless_pass_by_value, - reason = "program_path ownership is needed for Arc construction" -)] -fn plan_spawn_execution( - workspace_path: &Arc, - execution_cache_key: Option, - prefix_envs: &BTreeMap, - resolved_task_options: &ResolvedTaskOptions, - envs: &Arc, Arc>>, - program_path: Arc, - args: Arc<[Str]>, -) -> Result { - // The child env starts from the full context and is filtered in place by - // `EnvFingerprints::resolve` below — this clone is the one place the map's - // contents are actually copied per spawn. - let mut spawn_envs = (**envs).clone(); - let cwd = Arc::clone(&resolved_task_options.cwd); - - let mut resolved_cache_metadata = None; - if let Some(cache_config) = &resolved_task_options.cache_config { - // Resolve envs according cache configs - let mut env_fingerprints = - EnvFingerprints::resolve(&mut spawn_envs, &cache_config.env_config) - .map_err(Error::ResolveEnv)?; - - // Add prefix envs to fingerprinted envs - env_fingerprints.fingerprinted_envs.extend( - prefix_envs - .iter() - .map(|(name, value)| (name.clone(), EnvValueHash::new(value.as_str()))), - ); - - let program_fingerprint = match strip_prefix_for_cache(&program_path, workspace_path) { - Ok(relative_program_path) => { - ProgramFingerprint::InsideWorkspace { relative_program_path } - } - Err(PathFingerprintErrorKind::PathOutsideWorkspace { path, .. }) => { - let program_name_os_str = path.as_path().file_name().unwrap_or_default(); - #[expect( - clippy::manual_let_else, - reason = "? operator doesn't apply since early return has a different error type" - )] - let program_name_str = match program_name_os_str.to_str() { - Some(s) => s, - None => { - #[expect( - clippy::disallowed_types, - reason = "Arc for non-UTF-8 path data in error" - )] - return Err(PathFingerprintError { - kind: PathFingerprintErrorKind::NonPortableRelativePath { - path: Path::new(program_name_os_str).into(), - error: InvalidPathDataError::NonUtf8, - }, - path_type: PathType::Program, - } - .into()); - } - }; - ProgramFingerprint::OutsideWorkspace { program_name: program_name_str.into() } - } - Err(err) => { - return Err(PathFingerprintError { kind: err, path_type: PathType::Program }.into()); - } - }; - - let spawn_fingerprint: SpawnFingerprint = SpawnFingerprint { - cwd: strip_prefix_for_cache(&cwd, workspace_path) - .map_err(|kind| PathFingerprintError { kind, path_type: PathType::Cwd })?, - program_fingerprint, - args: Arc::clone(&args), - env_fingerprints, - }; - if let Some(execution_cache_key) = execution_cache_key { - resolved_cache_metadata = Some(CacheMetadata { - spawn_fingerprint, - execution_cache_key, - input_config: cache_config.input_config.clone(), - output_config: cache_config.output_config.clone(), - unfiltered_envs: Arc::clone(envs), - }); - } - } - - // Add prefix envs to spawn envs. - spawn_envs.extend(prefix_envs.iter().map(|(name, value)| { - (OsStr::new(name.as_str()).into(), OsStr::new(value.as_str()).into()) - })); - - Ok(SpawnExecution { - spawn_command: SpawnCommand { - program_path, - args: Arc::clone(&args), - cwd, - spawn_envs: Arc::new(spawn_envs.into_iter().collect()), - }, - cache_metadata: resolved_cache_metadata, - }) -} - -/// Expand the parsed task request (like `run -r build`/`lint`) into an execution graph. -/// -/// Builds a `DiGraph` of task executions, then validates it is acyclic via -/// `ExecutionGraph::try_from_graph`. Returns `CycleDependencyDetected` if a cycle is found. -/// -/// **Prune rule:** If the expanding task (the task whose command triggered -/// this nested query) appears in the expansion result, it is pruned from the graph -/// and its predecessors are wired directly to its successors. This prevents -/// workspace root tasks like `"build": "vp run -r build"` from infinitely -/// re-expanding themselves when a different query reaches them (e.g., -/// `vp run build` produces a different query than the script's `vp run -r build`, -/// so the skip rule doesn't fire, but the prune rule catches root in the result). -/// Like the skip rule, extra args don't affect this — only the `TaskQuery` matters. -#[expect(clippy::too_many_lines, reason = "sequential planning steps are clearer in one function")] -pub async fn plan_query_request( - query: Arc, - plan_options: PlanOptions, - mut context: PlanContext<'_>, -) -> Result { - // Apply cache override from `--cache` / `--no-cache` flags on this request. - // - // When `None`, we skip the update so the context keeps whatever the parent - // resolved — this is how `vp run --cache outer` propagates to a nested - // `vp run inner` that has no flags of its own. - let cache_override = plan_options.cache_override; - if cache_override != CacheOverride::None { - // Override is relative to the *workspace* config, not the parent's - // resolved config. This means `vp run --no-cache outer` where outer - // runs `vp run --cache inner` re-enables caching from the workspace - // defaults, rather than from the parent's disabled state. - let final_cache = resolve_cache_with_override( - *context.indexed_task_graph().global_cache_config(), - cache_override, - ); - context.set_resolved_global_cache(final_cache); - } - // Resolve effective concurrency for this level. - // - // Priority (highest to lowest): - // 1. `--concurrency-limit N` CLI flag - // 2. `--parallel` (without the above) → unlimited - // 3. `VP_RUN_CONCURRENCY_LIMIT` env var - // 4. `DEFAULT_CONCURRENCY_LIMIT` (4) - let effective_concurrency = match plan_options.concurrency_limit { - Some(n) => n, - None => { - if plan_options.parallel { - usize::MAX - } else { - concurrency_limit_from_env(context.envs())? - .unwrap_or(crate::DEFAULT_CONCURRENCY_LIMIT) - } - } - }; - - let parallel = plan_options.parallel; - - // Extra args are applied per-task below, not globally on the context. - // Tasks explicitly requested by the query receive `extra_args`; tasks - // only reached via `dependsOn` expansion receive an empty slice so that - // caller-specific CLI args don't pollute dependency tasks. - // See https://github.com/voidzero-dev/vite-task/issues/324. - let extra_args = plan_options.extra_args; - // Allocated once and shared across every dep-only task's context below, - // instead of calling `Arc::new([])` inside the per-task loop. - let empty_extra_args: Arc<[Str]> = Arc::from([]); - context.set_parent_query(Arc::clone(&query)); - - // Query matching tasks from the task graph. - // An empty graph means either no packages matched the filter (caller - // should succeed silently) or no selected package has the task (caller - // should error or show the task selector). `selected_package_count` - // tells the caller which case applies. - let task_query_result = context.indexed_task_graph().query_tasks(&query)?; - - // Strict mode (`--fail-if-no-match`): any user-provided `--filter` that - // contributed zero packages is an error. Skip the per-filter warnings - // below since the error already names every unmatched source. - if plan_options.fail_if_no_match && !task_query_result.unmatched_selectors.is_empty() { - return Err(Error::NoPackagesMatched { sources: task_query_result.unmatched_selectors }); - } - - #[expect(clippy::print_stderr, reason = "user-facing warning for typos in --filter")] - for selector in &task_query_result.unmatched_selectors { - eprintln!("No packages matched the filter: {selector}"); - } - - let no_packages_matched = task_query_result.selected_package_count == 0; - let task_node_index_graph = task_query_result.execution_graph; - - // Prune rule: if the expanding task appears in the expansion, prune it. - // This handles cases like root `"build": "vp run build"` — the root's build - // task is in the result but expanding it would recurse, so we remove it and - // reconnect its predecessors directly to its successors. - let pruned_task = - context.expanding_task().filter(|t| task_node_index_graph.graph.contains_node(*t)); - - let mut execution_node_indices_by_task_index = - FxHashMap::::with_capacity_and_hasher( - task_node_index_graph.graph.node_count(), - rustc_hash::FxBuildHasher, - ); - - // Build the inner DiGraph first, then validate acyclicity at the end. - let mut inner_graph = InnerExecutionGraph::with_capacity( - task_node_index_graph.graph.node_count(), - task_node_index_graph.graph.edge_count(), - ); - - // Plan each task node as execution nodes, skipping the pruned task - for task_index in task_node_index_graph.graph.nodes() { - if Some(task_index) == pruned_task { - continue; - } - let mut task_context = context.duplicate(); - // Only the explicitly requested tasks receive CLI extra args. - // Dep-only tasks (pulled in via `dependsOn`) run with empty extras. - if task_node_index_graph.requested.contains(&task_index) { - task_context.set_extra_args(Arc::clone(&extra_args)); - } else { - task_context.set_extra_args(Arc::clone(&empty_extra_args)); - } - let task_execution = - plan_task_as_execution_node(task_index, task_context, true).boxed_local().await?; - execution_node_indices_by_task_index - .insert(task_index, inner_graph.add_node(task_execution)); - } - - // Add edges between execution nodes according to task dependencies, - // skipping edges involving the pruned task. - for (from_task_index, to_task_index, ()) in task_node_index_graph.graph.all_edges() { - if Some(from_task_index) == pruned_task || Some(to_task_index) == pruned_task { - continue; - } - inner_graph.add_edge( - execution_node_indices_by_task_index[&from_task_index], - execution_node_indices_by_task_index[&to_task_index], - (), - ); - } - - // Reconnect through the pruned node: wire each predecessor directly to each successor. - if let Some(pruned) = pruned_task { - let preds: Vec<_> = - task_node_index_graph.graph.neighbors_directed(pruned, Direction::Incoming).collect(); - let succs: Vec<_> = - task_node_index_graph.graph.neighbors_directed(pruned, Direction::Outgoing).collect(); - for &pred in &preds { - for &succ in &succs { - if let (Some(&pe), Some(&se)) = ( - execution_node_indices_by_task_index.get(&pred), - execution_node_indices_by_task_index.get(&succ), - ) { - inner_graph.add_edge(pe, se, ()); - } - } - } - } - - // If --parallel, discard all edges so tasks run independently. - if parallel { - inner_graph.clear_edges(); - } - - // Validate the graph is acyclic. - // `try_from_graph` performs a DFS; if a cycle is found, it returns - // `CycleError` containing the full cycle path as node indices. - let graph = - ExecutionGraph::try_from_graph(inner_graph, effective_concurrency).map_err(|cycle| { - // Map each execution node index in the cycle path to its human-readable TaskDisplay. - // Every node in the cycle was added via `inner_graph.add_node()` above, - // with a corresponding entry in `execution_node_indices_by_task_index`. - let displays = cycle - .cycle_path() - .iter() - .map(|&exec_idx| { - execution_node_indices_by_task_index - .iter() - .find_map(|(task_idx, &mapped_exec_idx)| { - if mapped_exec_idx == exec_idx { - Some(context.indexed_task_graph().display_task(*task_idx)) - } else { - None - } - }) - .expect("cycle node must exist in execution_node_indices_by_task_index") - }) - .collect(); - Error::CycleDependencyDetected(displays) - })?; - - Ok(crate::PlanResult { graph, no_packages_matched }) -} - -/// Parse `VP_RUN_CONCURRENCY_LIMIT` from the environment variables. -/// -/// Returns `Ok(None)` if the variable is not set. -/// Returns `Err` if the variable is set but cannot be parsed as a positive integer. -#[expect(clippy::result_large_err, reason = "Error type is shared across all plan functions")] -fn concurrency_limit_from_env( - envs: &FxHashMap, Arc>, -) -> Result, Error> { - let Some(value) = envs.get(OsStr::new("VP_RUN_CONCURRENCY_LIMIT")) else { - return Ok(None); - }; - let s = value.to_str().ok_or_else(|| Error::InvalidConcurrencyLimitEnv(Arc::clone(value)))?; - let n: usize = s.parse().map_err(|_| Error::InvalidConcurrencyLimitEnv(Arc::clone(value)))?; - Ok(Some(n.max(1))) -} - -#[cfg(test)] -mod tests { - use std::collections::BTreeSet; - - use rustc_hash::FxHashSet; - use vite_path::AbsolutePathBuf; - use vite_str::Str; - use vite_task_graph::config::{ - CacheConfig, EnabledCacheConfig, EnvConfig, ResolvedGlobConfig, - user::{UserCacheConfig, UserInputEntry}, - }; - - use super::{ParentCacheConfig, resolve_synthetic_cache_config}; - - fn test_paths() -> (AbsolutePathBuf, AbsolutePathBuf) { - if cfg!(windows) { - ( - AbsolutePathBuf::new("C:\\workspace\\packages\\my-pkg".into()).unwrap(), - AbsolutePathBuf::new("C:\\workspace".into()).unwrap(), - ) - } else { - ( - AbsolutePathBuf::new("/workspace/packages/my-pkg".into()).unwrap(), - AbsolutePathBuf::new("/workspace".into()).unwrap(), - ) - } - } - - fn parent_config(includes_auto: bool, positive_globs: &[&str]) -> CacheConfig { - CacheConfig { - env_config: EnvConfig { - fingerprinted_envs: FxHashSet::default(), - untracked_env: FxHashSet::default(), - }, - input_config: ResolvedGlobConfig { - includes_auto, - positive_globs: positive_globs.iter().map(|s| Str::from(*s)).collect(), - negative_globs: BTreeSet::new(), - }, - output_config: ResolvedGlobConfig { - includes_auto: false, - positive_globs: BTreeSet::new(), - negative_globs: BTreeSet::new(), - }, - } - } - - fn globs(items: &[&str]) -> BTreeSet { - items.iter().map(|s| Str::from(*s)).collect() - } - - #[test] - fn synthetic_input_none_preserves_parent() { - let (pkg, ws) = test_paths(); - let parent = parent_config(true, &["src/**"]); - let result = resolve_synthetic_cache_config( - ParentCacheConfig::Inherited(parent), - UserCacheConfig::with_config(EnabledCacheConfig { - env: None, - untracked_env: None, - input: None, - output: None, - }), - &pkg, - &ws, - ) - .unwrap() - .unwrap(); - assert!(result.input_config.includes_auto); - assert_eq!(result.input_config.positive_globs, globs(&["src/**"])); - assert_eq!(result.input_config.negative_globs, globs(&[])); - } - - #[test] - fn synthetic_globs_merged_into_parent_default_auto() { - let (pkg, ws) = test_paths(); - let parent = parent_config(true, &[]); - let result = resolve_synthetic_cache_config( - ParentCacheConfig::Inherited(parent), - UserCacheConfig::with_config(EnabledCacheConfig { - env: None, - untracked_env: None, - input: Some(vec![UserInputEntry::Glob("config/**".into())]), - output: None, - }), - &pkg, - &ws, - ) - .unwrap() - .unwrap(); - assert!(result.input_config.includes_auto); - assert_eq!(result.input_config.positive_globs, globs(&["packages/my-pkg/config/**"])); - assert_eq!(result.input_config.negative_globs, globs(&[])); - } - - #[test] - fn synthetic_globs_merged_into_parent_explicit_input() { - let (pkg, ws) = test_paths(); - let parent = parent_config(false, &["src/**"]); - let result = resolve_synthetic_cache_config( - ParentCacheConfig::Inherited(parent), - UserCacheConfig::with_config(EnabledCacheConfig { - env: None, - untracked_env: None, - input: Some(vec![UserInputEntry::Glob("config/**".into())]), - output: None, - }), - &pkg, - &ws, - ) - .unwrap() - .unwrap(); - assert!(!result.input_config.includes_auto); - assert_eq!( - result.input_config.positive_globs, - globs(&["packages/my-pkg/config/**", "src/**"]) - ); - assert_eq!(result.input_config.negative_globs, globs(&[])); - } - - #[test] - fn synthetic_auto_ignored_when_parent_has_explicit_input() { - let (pkg, ws) = test_paths(); - let parent = parent_config(false, &["src/**"]); - let result = resolve_synthetic_cache_config( - ParentCacheConfig::Inherited(parent), - UserCacheConfig::with_config(EnabledCacheConfig { - env: None, - untracked_env: None, - input: Some(vec![ - UserInputEntry::Glob("config/**".into()), - UserInputEntry::Auto(vite_task_graph::config::user::AutoTracking { - auto: true, - }), - ]), - output: None, - }), - &pkg, - &ws, - ) - .unwrap() - .unwrap(); - // User's explicit choice (no auto) takes precedence - assert!(!result.input_config.includes_auto); - assert_eq!( - result.input_config.positive_globs, - globs(&["packages/my-pkg/config/**", "src/**"]) - ); - assert_eq!(result.input_config.negative_globs, globs(&[])); - } - - #[test] - fn parent_auto_preserved_with_synthetic_globs() { - let (pkg, ws) = test_paths(); - let parent = parent_config(true, &["tests/**"]); - let result = resolve_synthetic_cache_config( - ParentCacheConfig::Inherited(parent), - UserCacheConfig::with_config(EnabledCacheConfig { - env: None, - untracked_env: None, - input: Some(vec![UserInputEntry::Glob("config/**".into())]), - output: None, - }), - &pkg, - &ws, - ) - .unwrap() - .unwrap(); - assert!(result.input_config.includes_auto); - assert_eq!( - result.input_config.positive_globs, - globs(&["packages/my-pkg/config/**", "tests/**"]) - ); - assert_eq!(result.input_config.negative_globs, globs(&[])); - } - - #[test] - fn parent_cache_disabled_ignores_synthetic_input() { - let (pkg, ws) = test_paths(); - let result = resolve_synthetic_cache_config( - ParentCacheConfig::Disabled, - UserCacheConfig::with_config(EnabledCacheConfig { - env: None, - untracked_env: None, - input: Some(vec![UserInputEntry::Glob("config/**".into())]), - output: None, - }), - &pkg, - &ws, - ) - .unwrap(); - assert!(result.is_none()); - } - - #[test] - fn synthetic_disables_cache_despite_parent() { - let (pkg, ws) = test_paths(); - let parent = parent_config(true, &["src/**"]); - let result = resolve_synthetic_cache_config( - ParentCacheConfig::Inherited(parent), - UserCacheConfig::disabled(), - &pkg, - &ws, - ) - .unwrap(); - assert!(result.is_none()); - } - - #[test] - fn synthetic_negative_globs_merged() { - let (pkg, ws) = test_paths(); - let parent = parent_config(true, &["src/**"]); - let result = resolve_synthetic_cache_config( - ParentCacheConfig::Inherited(parent), - UserCacheConfig::with_config(EnabledCacheConfig { - env: None, - untracked_env: None, - input: Some(vec![UserInputEntry::Glob("!dist/**".into())]), - output: None, - }), - &pkg, - &ws, - ) - .unwrap() - .unwrap(); - assert_eq!(result.input_config.positive_globs, globs(&["src/**"])); - assert_eq!(result.input_config.negative_globs, globs(&["packages/my-pkg/dist/**"])); - } -} diff --git a/crates/vite_task_plan/src/plan_request.rs b/crates/vite_task_plan/src/plan_request.rs deleted file mode 100644 index cca039d10..000000000 --- a/crates/vite_task_plan/src/plan_request.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::{ffi::OsStr, sync::Arc}; - -use rustc_hash::FxHashMap; -use vite_path::AbsolutePath; -use vite_str::Str; -use vite_task_graph::{config::UserCacheConfig, query::TaskQuery}; - -/// A parsed command from a task script, passed to [`super::PlanRequestParser::get_plan_request`]. -/// -/// All fields use `Arc` for cheap reassignment. The implementation can mutate -/// these fields to modify how the command is executed when it falls through as a -/// normal process (i.e., when `get_plan_request` returns `None`). -#[derive(Debug)] -pub struct ScriptCommand { - pub program: Str, - pub args: Arc<[Str]>, - pub envs: Arc, Arc>>, - pub cwd: Arc, -} - -impl ScriptCommand { - /// Convert this `ScriptCommand` to a `SyntheticPlanRequest` with the given `cache_config`. - #[must_use] - pub fn to_synthetic_plan_request(&self, cache_config: UserCacheConfig) -> SyntheticPlanRequest { - SyntheticPlanRequest { - program: Arc::from(OsStr::new(&self.program)), - args: self.args.clone(), - cache_config, - envs: self.envs.clone(), - } - } -} - -/// CLI-level cache override from `--cache` / `--no-cache` flags. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub enum CacheOverride { - /// No override — inherit the parent's resolved cache config. - /// For a top-level `vp run`, this is the workspace config. - /// For a nested `vp run` inside a script, this is whatever the parent resolved. - #[default] - None, - /// Force all caching on (`--cache` flag). - ForceEnabled, - /// Force all caching off (`--no-cache` flag). - ForceDisabled, -} - -#[derive(Debug)] -pub struct PlanOptions { - pub extra_args: Arc<[Str]>, - pub cache_override: CacheOverride, - /// Per-level concurrency limit. `None` means inherit from the parent level - /// (or default to [`crate::DEFAULT_CONCURRENCY_LIMIT`] at the root). - pub concurrency_limit: Option, - /// When `true`, discard dependency edges between tasks at this level, - /// running all tasks as independent. If `concurrency` is also `None`, - /// this sets the effective concurrency to `usize::MAX`. - pub parallel: bool, - /// When `true`, an unmatched `--filter` expression turns the warning into - /// an [`crate::Error::NoPackagesMatched`] error and the run exits non-zero. - /// Mirrors pnpm's `--fail-if-no-match`. - pub fail_if_no_match: bool, -} - -#[derive(Debug)] -pub struct QueryPlanRequest { - /// The query to run against the task graph. For example: `-r build` - pub query: TaskQuery, - - /// Other options affecting the planning process, not the task graph querying itself. - /// - /// For example: `-- arg1 arg2` - pub plan_options: PlanOptions, -} - -/// The request to run a synthetic task (e.g., one generated by `TaskSynthesizer` from `vp lint` in a script). -/// Synthetic tasks are not defined in the task graph, but are generated on-the-fly. -#[derive(Debug)] -pub struct SyntheticPlanRequest { - /// The program to execute - pub program: Arc, - - /// The arguments to pass to the program - pub args: Arc<[Str]>, - - /// The cache config as if it's defined in `vite.config.*` - pub cache_config: UserCacheConfig, - - /// All environment variables to set for the synthetic task. - /// - /// This is set in the plan stage before resolving envs for caching. - /// Therefore, these envs are subject to env configurations in `UserTaskOptions`. - /// - /// - To set envs that are not subject to caching but still passed to the spawned child, use `task_options` to configure `untracked_env`. - /// - To set envs that should be fingerprinted, use `task_options` to configure `env`. - /// - If neither is set, and caching is enabled, these envs will have not effect. - pub envs: Arc, Arc>>, -} - -#[derive(Debug)] -pub enum PlanRequest { - /// The request to run tasks queried from the task graph, like `vp run ...`. - Query(QueryPlanRequest), - /// The request to run a synthetic task (not defined in the task graph), e.g., from `TaskSynthesizer`. - Synthetic(SyntheticPlanRequest), -} diff --git a/crates/vite_task_plan/src/ps1_shim.rs b/crates/vite_task_plan/src/ps1_shim.rs deleted file mode 100644 index 91d3a1da7..000000000 --- a/crates/vite_task_plan/src/ps1_shim.rs +++ /dev/null @@ -1,319 +0,0 @@ -//! Windows-specific: rewrite `.cmd` shims in `node_modules/.bin` so the plan -//! records a `powershell.exe -File ` invocation in place of the -//! `.cmd` hop. -//! -//! Running a `.cmd` shim from any shell causes `cmd.exe` to prompt "Terminate -//! batch job (Y/N)?" on Ctrl+C, which leaves the terminal corrupt. Rewriting -//! to the `.ps1` sibling, invoked via `powershell.exe -File`, sidesteps that -//! prompt. Doing the rewrite at plan time (rather than at spawn time) means -//! the command shown in the task graph and cache fingerprint is the command -//! that actually runs. -//! -//! The `.ps1` path recorded in args is **relative to the task's cwd**, with -//! `/` as the separator. That keeps `SpawnFingerprint.args` portable across -//! machines (no absolute paths leak into cache keys) and PowerShell resolves -//! `-File ` against its own working directory, which is the task's -//! cwd, so the spawn lands on the correct file. -//! -//! The rewrite is limited to `node_modules/.bin/` triplets **inside the -//! workspace** and produced by npm/pnpm/yarn (via cmd-shim, which only emits -//! `.cmd` — not `.bat`). Any `.cmd` file outside the workspace — e.g. a -//! globally installed tool's shim somewhere on the user's system PATH — is -//! left alone even if it happens to live under some other `node_modules/.bin`. -//! -//! Cross-platform primitives (`POWERSHELL_PREFIX`, `powershell_host`, -//! `find_ps1_sibling`, `is_stdin_terminal`) live in the `vite_powershell` -//! crate so `vite_command::ps1_shim` can share them. -//! -//! See . - -use std::sync::Arc; - -#[cfg(any(windows, test))] -use cow_utils::CowUtils as _; -use vite_path::AbsolutePath; -use vite_str::Str; - -/// Rewrite a `node_modules/.bin/*.cmd` invocation to go through PowerShell. -/// See the module docstring for the full contract; the short form: returns -/// `(powershell.exe, [-NoProfile, …, -File, , ...args])` -/// when the rewrite applies, otherwise `(resolved, args)` unchanged. -#[cfg(windows)] -#[must_use] -pub fn rewrite_cmd_shim_with_args( - resolved: Arc, - args: Arc<[Str]>, - cwd: &AbsolutePath, - workspace_root: &AbsolutePath, -) -> (Arc, Arc<[Str]>) { - if let Some(host) = vite_powershell::powershell_host() - && let Some(rewritten) = rewrite_with_host( - &resolved, - &args, - cwd, - workspace_root, - host, - vite_powershell::is_stdin_terminal(), - ) - { - return rewritten; - } - (resolved, args) -} - -#[cfg(not(windows))] -#[must_use] -pub const fn rewrite_cmd_shim_with_args( - resolved: Arc, - args: Arc<[Str]>, - _cwd: &AbsolutePath, - _workspace_root: &AbsolutePath, -) -> (Arc, Arc<[Str]>) { - (resolved, args) -} - -/// Pure rewrite logic, factored out so tests can exercise it on any platform -/// without depending on a real `powershell.exe` being on PATH. -#[cfg(any(windows, test))] -fn rewrite_with_host( - resolved: &Arc, - args: &Arc<[Str]>, - cwd: &AbsolutePath, - workspace_root: &AbsolutePath, - host: &Arc, - is_interactive: bool, -) -> Option<(Arc, Arc<[Str]>)> { - // Only route through PowerShell when stdin is an interactive terminal. The - // `.ps1` wrappers hang on a non-TTY stdin pipe (CI), and without a terminal - // there is no Ctrl+C prompt to protect against. See - // `vite_powershell::is_stdin_terminal`. - if !is_interactive { - return None; - } - if !is_in_workspace_node_modules_bin(resolved, workspace_root) { - return None; - } - let ps1 = vite_powershell::find_ps1_sibling(resolved)?; - let ps1_rel = pathdiff::diff_paths(ps1.as_path(), cwd.as_path())?; - let ps1_rel_str = ps1_rel.to_str()?.cow_replace('\\', "/"); - - tracing::debug!( - "rewriting .cmd shim to powershell: {} -> {} -File {}", - resolved.as_path().display(), - host.as_path().display(), - ps1_rel_str, - ); - - let new_args: Arc<[Str]> = vite_powershell::POWERSHELL_PREFIX - .iter() - .copied() - .map(Str::from) - .chain(std::iter::once(Str::from(ps1_rel_str.as_ref()))) - .chain(args.iter().cloned()) - .collect(); - - Some((Arc::clone(host), new_args)) -} - -/// True when `resolved` is a `/.../node_modules/.bin/` path -/// inside the workspace. Used to keep the rewrite hands-off for globally -/// installed shims (e.g. `%AppData%\npm\node_modules\.bin`). -#[cfg(any(windows, test))] -fn is_in_workspace_node_modules_bin( - resolved: &AbsolutePath, - workspace_root: &AbsolutePath, -) -> bool { - let path = resolved.as_path(); - if !path.starts_with(workspace_root.as_path()) { - return false; - } - let mut parents = path.components().rev(); - parents.next(); // shim filename - let Some(bin) = parents.next() else { return false }; - if !bin.as_os_str().eq_ignore_ascii_case(".bin") { - return false; - } - let Some(node_modules) = parents.next() else { return false }; - node_modules.as_os_str().eq_ignore_ascii_case("node_modules") -} - -#[cfg(test)] -mod tests { - use std::fs; - - use tempfile::tempdir; - use vite_path::AbsolutePathBuf; - - use super::{AbsolutePath, Arc, Str, rewrite_with_host}; - - #[expect(clippy::disallowed_types, reason = "tempdir bridges std PathBuf into AbsolutePath")] - fn abs(buf: std::path::PathBuf) -> Arc { - Arc::::from(AbsolutePathBuf::new(buf).unwrap()) - } - - #[expect(clippy::disallowed_types, reason = "tempdir hands out std Path for the test root")] - fn bin_dir(root: &std::path::Path) -> std::path::PathBuf { - let bin = root.join("node_modules").join(".bin"); - fs::create_dir_all(&bin).unwrap(); - bin - } - - fn host_arc(root: &AbsolutePath) -> Arc { - Arc::::from( - AbsolutePathBuf::new(root.as_path().join("powershell.exe")).unwrap(), - ) - } - - #[test] - fn rewrites_cmd_to_cwd_relative_ps1_at_workspace_root() { - let dir = tempdir().unwrap(); - let workspace = abs(dir.path().canonicalize().unwrap()); - let bin = bin_dir(workspace.as_path()); - fs::write(bin.join("vite.CMD"), "").unwrap(); - fs::write(bin.join("vite.ps1"), "").unwrap(); - - let host = host_arc(&workspace); - let resolved = abs(bin.join("vite.CMD")); - let args: Arc<[Str]> = Arc::from(vec![Str::from("--port"), Str::from("3000")]); - - let (program, rewritten_args) = - rewrite_with_host(&resolved, &args, &workspace, &workspace, &host, true) - .expect("should rewrite"); - - assert_eq!(program.as_path(), host.as_path()); - let as_strs: Vec<&str> = rewritten_args.iter().map(Str::as_str).collect(); - assert_eq!( - as_strs, - vec![ - "-NoProfile", - "-NoLogo", - "-ExecutionPolicy", - "Bypass", - "-File", - "node_modules/.bin/vite.ps1", - "--port", - "3000", - ] - ); - } - - /// Regression for the CI hang: the npm/pnpm/yarn `.ps1` wrappers read stdin - /// and block forever on a non-TTY pipe, so a structurally-valid shim must - /// NOT be rewritten when stdin is not an interactive terminal. The spawn - /// then falls back to the `.cmd` directly, which never reads stdin. - /// See . - #[test] - fn skips_rewrite_when_not_interactive() { - let dir = tempdir().unwrap(); - let workspace = abs(dir.path().canonicalize().unwrap()); - let bin = bin_dir(workspace.as_path()); - fs::write(bin.join("vite.cmd"), "").unwrap(); - fs::write(bin.join("vite.ps1"), "").unwrap(); - - let host = host_arc(&workspace); - let resolved = abs(bin.join("vite.cmd")); - let args: Arc<[Str]> = Arc::from(vec![Str::from("build")]); - - assert!( - rewrite_with_host(&resolved, &args, &workspace, &workspace, &host, false).is_none(), - "non-interactive spawns must not be rewritten through PowerShell" - ); - } - - #[test] - fn rewrites_cmd_to_cwd_relative_ps1_in_hoisted_monorepo_subpackage() { - // Task cwd is `/packages/foo`; shim lives at hoisted - // `/node_modules/.bin/vite.ps1`. The recorded argument should - // traverse up to the workspace and back down into node_modules/.bin. - let dir = tempdir().unwrap(); - let workspace = abs(dir.path().canonicalize().unwrap()); - let bin = bin_dir(workspace.as_path()); - fs::write(bin.join("vite.cmd"), "").unwrap(); - fs::write(bin.join("vite.ps1"), "").unwrap(); - - let sub_pkg_path = workspace.as_path().join("packages").join("foo"); - fs::create_dir_all(&sub_pkg_path).unwrap(); - let sub_pkg = abs(sub_pkg_path); - - let host = host_arc(&workspace); - let resolved = abs(bin.join("vite.cmd")); - let args: Arc<[Str]> = Arc::from(vec![]); - - let (_program, rewritten_args) = - rewrite_with_host(&resolved, &args, &sub_pkg, &workspace, &host, true) - .expect("should rewrite"); - - assert_eq!( - rewritten_args.get(5).map(Str::as_str), - Some("../../node_modules/.bin/vite.ps1") - ); - } - - #[test] - fn returns_none_when_no_ps1_sibling() { - let dir = tempdir().unwrap(); - let workspace = abs(dir.path().canonicalize().unwrap()); - let bin = bin_dir(workspace.as_path()); - fs::write(bin.join("vite.cmd"), "").unwrap(); - - let host = host_arc(&workspace); - let resolved = abs(bin.join("vite.cmd")); - let args: Arc<[Str]> = Arc::from(vec![Str::from("build")]); - - assert!(rewrite_with_host(&resolved, &args, &workspace, &workspace, &host, true).is_none()); - } - - #[test] - fn returns_none_for_cmd_outside_node_modules_bin() { - let dir = tempdir().unwrap(); - let workspace = abs(dir.path().canonicalize().unwrap()); - fs::write(workspace.as_path().join("where.cmd"), "").unwrap(); - fs::write(workspace.as_path().join("where.ps1"), "").unwrap(); - - let host = host_arc(&workspace); - let resolved = abs(workspace.as_path().join("where.cmd")); - let args: Arc<[Str]> = Arc::from(vec![]); - - assert!(rewrite_with_host(&resolved, &args, &workspace, &workspace, &host, true).is_none()); - } - - #[test] - fn returns_none_for_non_shim_extensions() { - let dir = tempdir().unwrap(); - let workspace = abs(dir.path().canonicalize().unwrap()); - let bin = bin_dir(workspace.as_path()); - fs::write(bin.join("node.exe"), "").unwrap(); - fs::write(bin.join("node.ps1"), "").unwrap(); - - let host = host_arc(&workspace); - let resolved = abs(bin.join("node.exe")); - let args: Arc<[Str]> = Arc::from(vec![Str::from("--version")]); - - assert!(rewrite_with_host(&resolved, &args, &workspace, &workspace, &host, true).is_none()); - } - - #[test] - fn returns_none_for_cmd_outside_workspace() { - // Globally installed shim (e.g. `%AppData%\npm\node_modules\.bin\foo.cmd`) - // that matches every structural check — `.cmd` extension, under - // `node_modules/.bin`, sibling `.ps1` present — but lives outside the - // project. The rewrite must stay hands-off so unrelated user tooling - // isn't silently retargeted. - let dir = tempdir().unwrap(); - let root = abs(dir.path().canonicalize().unwrap()); - let workspace_path = root.as_path().join("workspace"); - fs::create_dir_all(&workspace_path).unwrap(); - let workspace = abs(workspace_path); - - let global_bin = root.as_path().join("global").join("node_modules").join(".bin"); - fs::create_dir_all(&global_bin).unwrap(); - fs::write(global_bin.join("vite.cmd"), "").unwrap(); - fs::write(global_bin.join("vite.ps1"), "").unwrap(); - - let host = host_arc(&workspace); - let resolved = abs(global_bin.join("vite.cmd")); - let args: Arc<[Str]> = Arc::from(vec![]); - - assert!(rewrite_with_host(&resolved, &args, &workspace, &workspace, &host, true).is_none()); - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/README.md b/crates/vite_task_plan/tests/plan_snapshots/README.md deleted file mode 100644 index fe733b111..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Plan Snapshot Tests - -Tests for task graph construction and execution plan generation. - -## When to add tests here - -- Testing task graph structure (dependencies, task resolution) -- Testing execution plan generation from CLI arguments -- Testing task fingerprinting and cache key computation -- Testing workspace/package discovery and configuration parsing - -## How it works - -Each fixture in `fixtures/` is a self-contained workspace. Tests are defined in `snapshots.toml`: - -```toml -[[plan]] -name = "descriptive test name" -args = ["build", "--recursive"] -cwd = "packages/app" # optional, defaults to workspace root -``` - -The test runner: - -1. Copies the fixture to a temp directory -2. Loads the workspace and builds the task graph -3. Snapshots the task graph structure -4. For each plan test, parses CLI args and generates an execution plan -5. Compares against snapshots in `fixtures//snapshots/` - -## Adding a new test - -1. Create a new fixture directory under `fixtures/` -2. Add `package.json` (and `pnpm-workspace.yaml` for monorepos) -3. Add `snapshots.toml` with test cases (or omit for task-graph-only tests) -4. Run `cargo test -p vite_task_plan --test plan_snapshots` -5. Review and accept new snapshots diff --git a/crates/vite_task_plan/tests/plan_snapshots/fake-bin/README.md b/crates/vite_task_plan/tests/plan_snapshots/fake-bin/README.md deleted file mode 100644 index 0ecd751a7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fake-bin/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# fake-bin - -Stub executables used by plan snapshot tests. - -Plan tests need `vtt` to be resolvable on `PATH` so that `find_executable` can produce -an absolute path for task commands like `vtt print-file package.json`. The binary is -never actually executed during plan tests — only its resolved path appears in snapshots. - -- `vtt` — Unix stub (executable shell script) -- `vtt.cmd` — Windows stub (looked up via `PATHEXT`) diff --git a/crates/vite_task_plan/tests/plan_snapshots/fake-bin/vtt b/crates/vite_task_plan/tests/plan_snapshots/fake-bin/vtt deleted file mode 100755 index 39a3b707f..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fake-bin/vtt +++ /dev/null @@ -1 +0,0 @@ -#\!/bin/sh diff --git a/crates/vite_task_plan/tests/plan_snapshots/fake-bin/vtt.cmd b/crates/vite_task_plan/tests/plan_snapshots/fake-bin/vtt.cmd deleted file mode 100644 index e69de29bb..000000000 diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/package.json deleted file mode 100644 index 9e658aa6b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "additional-envs", - "private": true, - "scripts": { - "hello": "echo hello", - "env-test": "TEST_VAR=hello_world vt tool print-env TEST_VAR" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots.toml deleted file mode 100644 index 5d49cc6ad..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Tests tool synthetic command with environment variables - -[[plan]] -name = "tool_synthetic_task_in_user_task" -args = ["run", "env-test"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/query_tool_synthetic_task_in_user_task.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/query_tool_synthetic_task_in_user_task.jsonc deleted file mode 100644 index d175c94bf..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/query_tool_synthetic_task_in_user_task.jsonc +++ /dev/null @@ -1,93 +0,0 @@ -// run env-test -{ - "graph": [ - { - "key": [ - "/", - "env-test" - ], - "node": { - "task_display": { - "package_name": "additional-envs", - "task_name": "env-test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "additional-envs", - "task_name": "env-test", - "package_path": "/" - }, - "command": "TEST_VAR=hello_world vt tool print-env TEST_VAR", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-env", - "TEST_VAR" - ], - "env_fingerprints": { - "fingerprinted_envs": { - "TEST_VAR": "sha256:35072c1ae546350e0bfa7ab11d49dc6f129e72ccd57ec7eb671225bbd197c8f1" - }, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "env-test", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-env", - "TEST_VAR" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:", - "TEST_VAR": "hello_world" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/task_graph.md deleted file mode 100644 index 6bddb6bfc..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#env-test"] - task_1["/#hello"] -``` - -## `/#env-test` - -```json -{ - "task_display": { - "package_name": "additional-envs", - "task_name": "env-test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "TEST_VAR=hello_world vt tool print-env TEST_VAR" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#hello` - -```json -{ - "task_display": { - "package_name": "additional-envs", - "task_name": "hello", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo hello" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/additional_env/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/package.json deleted file mode 100644 index 3d1d3b052..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/cache-cli-override", - "scripts": { - "test": "vtt print-file package.json", - "lint": "vtt print-file vite-task.json" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots.toml deleted file mode 100644 index 89782ff40..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots.toml +++ /dev/null @@ -1,41 +0,0 @@ -# Tests --cache and --no-cache CLI flag overriding rules - -# Baseline: without flags, tasks are not cached because cache.tasks is false -[[plan]] -name = "baseline___tasks_not_cached_when_cache_tasks_is_false" -args = ["run", "build"] - -# --cache enables caching for tasks that don't have explicit cache: false -[[plan]] -name = "__cache_enables_task_caching_even_when_cache_tasks_is_false" -args = ["run", "--cache", "build"] - -# --cache does NOT force-enable tasks with explicit cache: false -[[plan]] -name = "__cache_does_not_override_per_task_cache_false" -args = ["run", "--cache", "deploy"] - -# --cache enables script caching even when cache.scripts is not set -[[plan]] -name = "__cache_enables_script_caching" -args = ["run", "--cache", "test"] - -# --no-cache disables caching for everything -[[plan]] -name = "__no_cache_disables_task_caching" -args = ["run", "--no-cache", "build"] - -# --no-cache overrides per-task cache: true -[[plan]] -name = "__no_cache_overrides_per_task_cache_true" -args = ["run", "--no-cache", "check"] - -# --cache on task with per-task cache: true (redundant but valid) -[[plan]] -name = "__cache_on_task_with_per_task_cache_true_enables_caching" -args = ["run", "--cache", "check"] - -# --cache and --no-cache conflict -[[plan]] -name = "__cache_and___no_cache_conflict" -args = ["run", "--cache", "--no-cache", "build"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_and___no_cache_conflict.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_and___no_cache_conflict.snap deleted file mode 100644 index e6f26f84c..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_and___no_cache_conflict.snap +++ /dev/null @@ -1,5 +0,0 @@ -error: the argument '--cache' cannot be used with '--no-cache' - -Usage: vt run --cache ... - -For more information, try '--help'. diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_does_not_override_per_task_cache_false.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_does_not_override_per_task_cache_false.jsonc deleted file mode 100644 index 5462e6f1b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_does_not_override_per_task_cache_false.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run --cache deploy -{ - "graph": [ - { - "key": [ - "/", - "deploy" - ], - "node": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "deploy", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "deploy", - "package_path": "/" - }, - "command": "vtt print-file vite-task.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "vite-task.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_script_caching.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_script_caching.jsonc deleted file mode 100644 index 62e2a3612..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_script_caching.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run --cache test -{ - "graph": [ - { - "key": [ - "/", - "test" - ], - "node": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "test", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "test", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_task_caching_even_when_cache_tasks_is_false.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_task_caching_even_when_cache_tasks_is_false.jsonc deleted file mode 100644 index d6600a3b6..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_enables_task_caching_even_when_cache_tasks_is_false.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run --cache build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "build", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "build", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_on_task_with_per_task_cache_true_enables_caching.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_on_task_with_per_task_cache_true_enables_caching.jsonc deleted file mode 100644 index 442d2ce30..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___cache_on_task_with_per_task_cache_true_enables_caching.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run --cache check -{ - "graph": [ - { - "key": [ - "/", - "check" - ], - "node": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "check", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "check", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "check", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___no_cache_disables_task_caching.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___no_cache_disables_task_caching.jsonc deleted file mode 100644 index 90793bdc4..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___no_cache_disables_task_caching.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run --no-cache build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "build", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___no_cache_overrides_per_task_cache_true.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___no_cache_overrides_per_task_cache_true.jsonc deleted file mode 100644 index 49c6c7b2b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query___no_cache_overrides_per_task_cache_true.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run --no-cache check -{ - "graph": [ - { - "key": [ - "/", - "check" - ], - "node": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "check", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "check", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query_baseline___tasks_not_cached_when_cache_tasks_is_false.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query_baseline___tasks_not_cached_when_cache_tasks_is_false.jsonc deleted file mode 100644 index 7ea762405..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/query_baseline___tasks_not_cached_when_cache_tasks_is_false.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "build", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/task_graph.md deleted file mode 100644 index 0709d62fc..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/snapshots/task_graph.md +++ /dev/null @@ -1,189 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#check"] - task_2["/#deploy"] - task_3["/#lint"] - task_4["/#test"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#check` - -```json -{ - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "check", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#deploy` - -```json -{ - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "deploy", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file vite-task.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": null - } - }, - "source": "TaskConfig" -} -``` - -## `/#lint` - -```json -{ - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "lint", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file vite-task.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/cache-cli-override", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/vite-task.json deleted file mode 100644 index e4c725179..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_cli_override/vite-task.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "cache": { - "tasks": false - }, - "tasks": { - "build": { - "command": "vtt print-file package.json" - }, - "deploy": { - "command": "vtt print-file vite-task.json", - "cache": false - }, - "check": { - "command": "vtt print-file package.json", - "cache": true - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/package.json deleted file mode 100644 index 1b6345192..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "scripts": { - "lint": "vt tool print lint", - "hello": "vtt print-file", - "lint-and-echo": "vt tool print lint && echo", - "echo-and-lint": "echo Linting && vt tool print lint" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots.toml deleted file mode 100644 index f2eb125c3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Tests cache key generation for various task configurations - -[[plan]] -name = "synthetic_task_in_user_task" -args = ["run", "lint"] - -[[plan]] -name = "synthetic_task_with_extra_args_in_user_task" -args = ["run", "lint", "--fix"] - -[[plan]] -name = "normal_task_with_extra_args" -args = ["run", "hello", "a.txt"] - -[[plan]] -name = "synthetic_task_in_user_task_with_cwd" -cwd = "subdir" -args = ["run", "lint"] - -[[plan]] -name = "lint_and_echo_with_extra_args" -args = ["run", "lint-and-echo", "Linting complete"] - -[[plan]] -name = "echo_and_lint_with_extra_args" -args = ["run", "echo-and-lint", "--fix"] - -[[e2e]] -name = "direct_lint" -steps = [ - "vt run lint # cache miss", - "echo debugger > main.js # add lint error", - "vt run lint # cache miss, lint fails", -] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_echo_and_lint_with_extra_args.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_echo_and_lint_with_extra_args.jsonc deleted file mode 100644 index da5b8aefa..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_echo_and_lint_with_extra_args.jsonc +++ /dev/null @@ -1,119 +0,0 @@ -// run echo-and-lint --fix -{ - "graph": [ - { - "key": [ - "/", - "echo-and-lint" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "echo-and-lint", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "echo-and-lint", - "package_path": "/" - }, - "command": "echo Linting", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "Linting" - ], - "trailing_newline": true - } - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "echo-and-lint", - "package_path": "/" - }, - "command": "vt tool print lint --fix", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint", - "--fix" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "echo-and-lint", - "command_item_index": 0, - "and_item_index": 1, - "extra_args": [ - "--fix" - ], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint", - "--fix" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_lint_and_echo_with_extra_args.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_lint_and_echo_with_extra_args.jsonc deleted file mode 100644 index 94e3756f7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_lint_and_echo_with_extra_args.jsonc +++ /dev/null @@ -1,115 +0,0 @@ -// run lint-and-echo Linting complete -{ - "graph": [ - { - "key": [ - "/", - "lint-and-echo" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "lint-and-echo", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "lint-and-echo", - "package_path": "/" - }, - "command": "vt tool print lint", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "lint-and-echo", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "lint-and-echo", - "package_path": "/" - }, - "command": "echo 'Linting complete'", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "Linting complete" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_normal_task_with_extra_args.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_normal_task_with_extra_args.jsonc deleted file mode 100644 index b9a276a86..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_normal_task_with_extra_args.jsonc +++ /dev/null @@ -1,92 +0,0 @@ -// run hello a.txt -{ - "graph": [ - { - "key": [ - "/", - "hello" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "hello", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "hello", - "package_path": "/" - }, - "command": "vtt print-file a.txt", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "a.txt" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "hello", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [ - "a.txt" - ], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "a.txt" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task.jsonc deleted file mode 100644 index cd55927ed..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run lint -{ - "graph": [ - { - "key": [ - "/", - "lint" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/" - }, - "command": "vt tool print lint", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "lint", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task_with_cwd.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task_with_cwd.jsonc deleted file mode 100644 index cd55927ed..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_in_user_task_with_cwd.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run lint -{ - "graph": [ - { - "key": [ - "/", - "lint" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/" - }, - "command": "vt tool print lint", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "lint", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_with_extra_args_in_user_task.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_with_extra_args_in_user_task.jsonc deleted file mode 100644 index 415e91aa2..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/query_synthetic_task_with_extra_args_in_user_task.jsonc +++ /dev/null @@ -1,94 +0,0 @@ -// run lint --fix -{ - "graph": [ - { - "key": [ - "/", - "lint" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/" - }, - "command": "vt tool print lint --fix", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint", - "--fix" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "lint", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [ - "--fix" - ], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint", - "--fix" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/task_graph.md deleted file mode 100644 index d38a20fca..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/snapshots/task_graph.md +++ /dev/null @@ -1,166 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#echo-and-lint"] - task_1["/#hello"] - task_2["/#lint"] - task_3["/#lint-and-echo"] -``` - -## `/#echo-and-lint` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "echo-and-lint", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo Linting && vt tool print lint" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#hello` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "hello", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#lint` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt tool print lint" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#lint-and-echo` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "lint-and-echo", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt tool print lint && echo" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_keys/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/package.json deleted file mode 100644 index c887e6ffc..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/cache-scripts-default", - "scripts": { - "build": "vtt print-file package.json", - "test": "vtt print-file package.json" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots.toml deleted file mode 100644 index e74ecb9a7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Verifies default behavior: scripts are not cached without explicit cache.scripts - -# Script should not be cached by default -[[plan]] -name = "script_not_cached_by_default" -args = ["run", "build"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots/query_script_not_cached_by_default.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots/query_script_not_cached_by_default.jsonc deleted file mode 100644 index 54edeba02..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots/query_script_not_cached_by_default.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/cache-scripts-default", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-scripts-default", - "task_name": "build", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots/task_graph.md deleted file mode 100644 index 8464f9660..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_default/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#test"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "@test/cache-scripts-default", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/cache-scripts-default", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/package.json deleted file mode 100644 index ee76392f1..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/cache-scripts-enabled", - "scripts": { - "build": "echo building", - "test": "echo testing" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/snapshots/task_graph.md deleted file mode 100644 index 52193c9d4..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#test"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "@test/cache-scripts-enabled", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo building" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/cache-scripts-enabled", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo testing" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/vite-task.json deleted file mode 100644 index 908833e05..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_enabled/vite-task.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - // Enables caching for all package.json scripts in the workspace. - "cache": { "scripts": true } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/package.json deleted file mode 100644 index 5c37b711d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/cache-scripts-error-non-root", - "scripts": { - "build": "echo building" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/packages/pkg-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/packages/pkg-a/package.json deleted file mode 100644 index 3fadeff85..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/packages/pkg-a/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/pkg-a", - "scripts": { - "build": "echo building pkg-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/packages/pkg-a/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/packages/pkg-a/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/packages/pkg-a/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/snapshots/task_graph_load_error.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/snapshots/task_graph_load_error.snap deleted file mode 100644 index 09ae8010c..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_error_non_root/snapshots/task_graph_load_error.snap +++ /dev/null @@ -1 +0,0 @@ -`cache` can only be set in the workspace root config, but found in /packages/pkg-a \ No newline at end of file diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/package.json deleted file mode 100644 index 9f516c256..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/cache-scripts-task-override", - "scripts": { - "test": "vtt print-file package.json" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots.toml deleted file mode 100644 index 32ae91d29..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots.toml +++ /dev/null @@ -1,16 +0,0 @@ -# Verifies default cache behavior: tasks cached, scripts not cached - -# Task should be cached (tasks default to true) -[[plan]] -name = "task_cached_by_default" -args = ["run", "build"] - -# Another task should also be cached -[[plan]] -name = "another_task_cached_by_default" -args = ["run", "deploy"] - -# Script not in the tasks map should not be cached -[[plan]] -name = "script_not_cached_by_default" -args = ["run", "test"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_another_task_cached_by_default.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_another_task_cached_by_default.jsonc deleted file mode 100644 index 0f4de9059..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_another_task_cached_by_default.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run deploy -{ - "graph": [ - { - "key": [ - "/", - "deploy" - ], - "node": { - "task_display": { - "package_name": "@test/cache-scripts-task-override", - "task_name": "deploy", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-scripts-task-override", - "task_name": "deploy", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "deploy", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_script_not_cached_by_default.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_script_not_cached_by_default.jsonc deleted file mode 100644 index d10e5ba11..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_script_not_cached_by_default.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run test -{ - "graph": [ - { - "key": [ - "/", - "test" - ], - "node": { - "task_display": { - "package_name": "@test/cache-scripts-task-override", - "task_name": "test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-scripts-task-override", - "task_name": "test", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_task_cached_by_default.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_task_cached_by_default.jsonc deleted file mode 100644 index 36e7e21e9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/query_task_cached_by_default.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/cache-scripts-task-override", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-scripts-task-override", - "task_name": "build", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "build", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/task_graph.md deleted file mode 100644 index e8de0a07e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/snapshots/task_graph.md +++ /dev/null @@ -1,126 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#deploy"] - task_2["/#test"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "@test/cache-scripts-task-override", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#deploy` - -```json -{ - "task_display": { - "package_name": "@test/cache-scripts-task-override", - "task_name": "deploy", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/cache-scripts-task-override", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/vite-task.json deleted file mode 100644 index 1439d982e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_scripts_task_override/vite-task.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "tasks": { - "build": { - "command": "vtt print-file package.json" - }, - "deploy": { - "command": "vtt print-file package.json" - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/package.json deleted file mode 100644 index 444236119..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@test/cache-sharing", - "version": "1.0.0", - "scripts": { - "a": "echo a", - "b": "echo a && echo b", - "c": "echo a && echo b && echo c" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/pnpm-lock.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/pnpm-lock.yaml deleted file mode 100644 index b6688e7cb..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/pnpm-lock.yaml +++ /dev/null @@ -1,8 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - .: {} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/pnpm-workspace.yaml deleted file mode 100644 index 4de91a383..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - '.' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/snapshots/task_graph.md deleted file mode 100644 index e08947457..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/snapshots/task_graph.md +++ /dev/null @@ -1,126 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#a"] - task_1["/#b"] - task_2["/#c"] -``` - -## `/#a` - -```json -{ - "task_display": { - "package_name": "@test/cache-sharing", - "task_name": "a", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo a" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#b` - -```json -{ - "task_display": { - "package_name": "@test/cache-sharing", - "task_name": "b", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo a && echo b" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#c` - -```json -{ - "task_display": { - "package_name": "@test/cache-sharing", - "task_name": "c", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo a && echo b && echo c" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_sharing/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/node_modules/.bin/vt b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/node_modules/.bin/vt deleted file mode 100755 index b43631fca..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/node_modules/.bin/vt +++ /dev/null @@ -1,2 +0,0 @@ -# Dummy executable so `which("vt")` succeeds during plan resolution. -# This file is never actually executed. diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/node_modules/.bin/vt.cmd b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/node_modules/.bin/vt.cmd deleted file mode 100644 index 91c459bde..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/node_modules/.bin/vt.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@REM Dummy executable so `which("vt")` succeeds during plan resolution on Windows. -@REM This file is never actually executed. diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/package.json deleted file mode 100644 index 7291f9ccf..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/cache-subcommand", - "scripts": { - "clean-cache": "vt cache clean" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots.toml deleted file mode 100644 index 2a9d5e674..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots.toml +++ /dev/null @@ -1,3 +0,0 @@ -[[plan]] -name = "cache_clean_in_script" -args = ["run", "clean-cache"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots/query_cache_clean_in_script.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots/query_cache_clean_in_script.jsonc deleted file mode 100644 index 7c6d38ecc..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots/query_cache_clean_in_script.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run clean-cache -{ - "graph": [ - { - "key": [ - "/", - "clean-cache" - ], - "node": { - "task_display": { - "package_name": "@test/cache-subcommand", - "task_name": "clean-cache", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-subcommand", - "task_name": "clean-cache", - "package_path": "/" - }, - "command": "vt cache clean", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/node_modules/.bin/vt", - "args": [ - "cache", - "clean" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots/task_graph.md deleted file mode 100644 index 82e845142..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/snapshots/task_graph.md +++ /dev/null @@ -1,46 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#clean-cache"] -``` - -## `/#clean-cache` - -```json -{ - "task_display": { - "package_name": "@test/cache-subcommand", - "task_name": "clean-cache", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt cache clean" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_subcommand/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/package.json deleted file mode 100644 index 7634cf122..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/cache-tasks-disabled", - "scripts": { - "test": "vtt print-file package.json" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots.toml deleted file mode 100644 index e3f58e3e4..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots.toml +++ /dev/null @@ -1,16 +0,0 @@ -# Verifies cache.tasks: false disables caching at plan time - -# Task without per-task cache override should not be cached -[[plan]] -name = "task_not_cached_when_cache_tasks_is_false" -args = ["run", "build"] - -# Task with per-task cache: true is still disabled by cache.tasks: false -[[plan]] -name = "per_task_cache_true_still_disabled_by_cache_tasks_false" -args = ["run", "deploy"] - -# Script should not be cached (scripts default to false) -[[plan]] -name = "script_not_cached" -args = ["run", "test"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/query_per_task_cache_true_still_disabled_by_cache_tasks_false.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/query_per_task_cache_true_still_disabled_by_cache_tasks_false.jsonc deleted file mode 100644 index 6c334db2f..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/query_per_task_cache_true_still_disabled_by_cache_tasks_false.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run deploy -{ - "graph": [ - { - "key": [ - "/", - "deploy" - ], - "node": { - "task_display": { - "package_name": "@test/cache-tasks-disabled", - "task_name": "deploy", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-tasks-disabled", - "task_name": "deploy", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/query_script_not_cached.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/query_script_not_cached.jsonc deleted file mode 100644 index 1feeadf22..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/query_script_not_cached.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run test -{ - "graph": [ - { - "key": [ - "/", - "test" - ], - "node": { - "task_display": { - "package_name": "@test/cache-tasks-disabled", - "task_name": "test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-tasks-disabled", - "task_name": "test", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/query_task_not_cached_when_cache_tasks_is_false.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/query_task_not_cached_when_cache_tasks_is_false.jsonc deleted file mode 100644 index 01cbb45a6..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/query_task_not_cached_when_cache_tasks_is_false.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/cache-tasks-disabled", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-tasks-disabled", - "task_name": "build", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/task_graph.md deleted file mode 100644 index 3fa32ca8d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/snapshots/task_graph.md +++ /dev/null @@ -1,126 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#deploy"] - task_2["/#test"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "@test/cache-tasks-disabled", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#deploy` - -```json -{ - "task_display": { - "package_name": "@test/cache-tasks-disabled", - "task_name": "deploy", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/cache-tasks-disabled", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/vite-task.json deleted file mode 100644 index e510e977b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_tasks_disabled/vite-task.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "cache": { - "tasks": false - }, - "tasks": { - "build": { - "command": "vtt print-file package.json" - }, - "deploy": { - "command": "vtt print-file package.json", - "cache": true - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/package.json deleted file mode 100644 index 07a8c4e28..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/cache-true-no-force-enable", - "scripts": { - "test": "vtt print-file package.json" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots.toml deleted file mode 100644 index 2506d297b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots.toml +++ /dev/null @@ -1,16 +0,0 @@ -# Verifies cache: true enables caching but does not override per-task cache: false - -# Task with per-task cache: false should not be cached -[[plan]] -name = "task_with_cache_false_not_cached_despite_global_cache_true" -args = ["run", "build"] - -# Task without per-task override should be cached -[[plan]] -name = "task_cached_when_global_cache_true" -args = ["run", "deploy"] - -# Script should be cached (cache: true enables scripts too) -[[plan]] -name = "script_cached_when_global_cache_true" -args = ["run", "test"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_script_cached_when_global_cache_true.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_script_cached_when_global_cache_true.jsonc deleted file mode 100644 index cc9a28696..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_script_cached_when_global_cache_true.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run test -{ - "graph": [ - { - "key": [ - "/", - "test" - ], - "node": { - "task_display": { - "package_name": "@test/cache-true-no-force-enable", - "task_name": "test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-true-no-force-enable", - "task_name": "test", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "test", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_task_cached_when_global_cache_true.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_task_cached_when_global_cache_true.jsonc deleted file mode 100644 index 60afcc33b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_task_cached_when_global_cache_true.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run deploy -{ - "graph": [ - { - "key": [ - "/", - "deploy" - ], - "node": { - "task_display": { - "package_name": "@test/cache-true-no-force-enable", - "task_name": "deploy", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-true-no-force-enable", - "task_name": "deploy", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "deploy", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_task_with_cache_false_not_cached_despite_global_cache_true.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_task_with_cache_false_not_cached_despite_global_cache_true.jsonc deleted file mode 100644 index 36a38f4c3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/query_task_with_cache_false_not_cached_despite_global_cache_true.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/cache-true-no-force-enable", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/cache-true-no-force-enable", - "task_name": "build", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/task_graph.md deleted file mode 100644 index 71b021553..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/snapshots/task_graph.md +++ /dev/null @@ -1,109 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#deploy"] - task_2["/#test"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "@test/cache-true-no-force-enable", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": null - } - }, - "source": "TaskConfig" -} -``` - -## `/#deploy` - -```json -{ - "task_display": { - "package_name": "@test/cache-true-no-force-enable", - "task_name": "deploy", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/cache-true-no-force-enable", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/vite-task.json deleted file mode 100644 index 2deb66a64..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cache_true_no_force_enable/vite-task.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "cache": true, - "tasks": { - "build": { - "command": "vtt print-file package.json", - "cache": false - }, - "deploy": { - "command": "vtt print-file package.json" - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/package.json deleted file mode 100644 index b38b6b7b8..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "scripts": { - "build": "vtt print-file package.json", - "cd-build": "cd src && vt run build", - "cd-lint": "cd src && vt tool print lint" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots.toml deleted file mode 100644 index 036bded9e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots.toml +++ /dev/null @@ -1,9 +0,0 @@ -# Tests that `cd` in scripts interacts correctly with nested vt commands - -[[plan]] -name = "cd_before_vt_run_should_not_affect_expanded_task_cwd" -args = ["run", "cd-build"] - -[[plan]] -name = "cd_before_vt_lint_should_put_synthetic_task_under_cwd" -args = ["run", "cd-lint"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_lint_should_put_synthetic_task_under_cwd.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_lint_should_put_synthetic_task_under_cwd.jsonc deleted file mode 100644 index dc31da6b3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_lint_should_put_synthetic_task_under_cwd.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run cd-lint -{ - "graph": [ - { - "key": [ - "/", - "cd-lint" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "cd-lint", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "cd-lint", - "package_path": "/" - }, - "command": "vt tool print lint", - "cwd": "/src" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "src", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "cd-lint", - "command_item_index": 0, - "and_item_index": 1, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/src" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_run_should_not_affect_expanded_task_cwd.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_run_should_not_affect_expanded_task_cwd.jsonc deleted file mode 100644 index 7c14c5649..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/query_cd_before_vt_run_should_not_affect_expanded_task_cwd.jsonc +++ /dev/null @@ -1,124 +0,0 @@ -// run cd-build -{ - "graph": [ - { - "key": [ - "/", - "cd-build" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "cd-build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "cd-build", - "package_path": "/" - }, - "command": "vt run build", - "cwd": "/src" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "build", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "build", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/task_graph.md deleted file mode 100644 index ac5d0b951..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/snapshots/task_graph.md +++ /dev/null @@ -1,126 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#cd-build"] - task_2["/#cd-lint"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#cd-build` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "cd-build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "cd src && vt run build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#cd-lint` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "cd-lint", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "cd src && vt tool print lint" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cd_in_scripts/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/package.json deleted file mode 100644 index 805c4cab9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "comprehensive-task-graph", - "version": "1.0.0", - "workspaces": [ - "packages/*" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/api/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/api/package.json deleted file mode 100644 index ae3e1fa6f..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/api/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@test/api", - "version": "1.0.0", - "scripts": { - "build": "echo Generate schemas && echo Compile TypeScript && echo Bundle API && echo Copy assets", - "test": "echo Testing API", - "start": "echo Starting API server", - "dev": "echo Watch mode && echo Start dev server" - }, - "dependencies": { - "@test/config": "workspace:*", - "@test/shared": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/app/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/app/package.json deleted file mode 100644 index 1e7e62bbd..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/app/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@test/app", - "version": "1.0.0", - "scripts": { - "build": "echo Clean dist && echo Build client && echo Build server && echo Generate manifest && echo Optimize assets", - "test": "echo Unit tests && echo Integration tests", - "dev": "echo Running dev server", - "preview": "echo Preview build", - "deploy": "echo Validate && echo Upload && echo Verify" - }, - "dependencies": { - "@test/api": "workspace:*", - "@test/pkg#special": "workspace:*", - "@test/shared": "workspace:*", - "@test/ui": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/config/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/config/package.json deleted file mode 100644 index 3490f7ce7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/config/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/config", - "version": "1.0.0", - "scripts": { - "build": "echo Building config", - "validate": "echo Validating config" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/pkg#special/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/pkg#special/package.json deleted file mode 100644 index 1ef61876b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/pkg#special/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@test/pkg#special", - "version": "1.0.0", - "scripts": { - "build": "echo Building package with hash", - "test": "echo Testing package with hash" - }, - "dependencies": { - "@test/shared": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/shared/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/shared/package.json deleted file mode 100644 index 10ec5fba3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/shared/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@test/shared", - "version": "1.0.0", - "scripts": { - "build": "echo Cleaning && echo Compiling shared && echo Generating types", - "test": "echo Setting up test env && echo Running tests && echo Cleanup", - "lint": "echo Linting shared", - "typecheck": "echo Type checking shared" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/tools/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/tools/package.json deleted file mode 100644 index 4696273ba..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/tools/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@test/tools", - "version": "1.0.0", - "scripts": { - "generate": "echo Generating tools", - "validate": "echo Validating" - }, - "dependencies": { - "@test/config": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/ui/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/ui/package.json deleted file mode 100644 index e3c2ac7dd..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/packages/ui/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@test/ui", - "version": "1.0.0", - "scripts": { - "build": "echo Compile styles && echo Build components && echo Generate types", - "test": "echo Testing UI", - "lint": "echo Linting UI", - "storybook": "echo Running storybook" - }, - "dependencies": { - "@test/shared": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/snapshots/task_graph.md deleted file mode 100644 index 05a44ec06..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/snapshots/task_graph.md +++ /dev/null @@ -1,926 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/api#build"] - task_1["/packages/api#dev"] - task_2["/packages/api#start"] - task_3["/packages/api#test"] - task_4["/packages/app#build"] - task_5["/packages/app#deploy"] - task_6["/packages/app#dev"] - task_7["/packages/app#preview"] - task_8["/packages/app#test"] - task_9["/packages/config#build"] - task_10["/packages/config#validate"] - task_11["/packages/pkg#special#build"] - task_12["/packages/pkg#special#test"] - task_13["/packages/shared#build"] - task_14["/packages/shared#lint"] - task_15["/packages/shared#test"] - task_16["/packages/shared#typecheck"] - task_17["/packages/tools#generate"] - task_18["/packages/tools#validate"] - task_19["/packages/ui#build"] - task_20["/packages/ui#lint"] - task_21["/packages/ui#storybook"] - task_22["/packages/ui#test"] -``` - -## `/packages/api#build` - -```json -{ - "task_display": { - "package_name": "@test/api", - "task_name": "build", - "package_path": "/packages/api" - }, - "resolved_config": { - "commands": [ - "echo Generate schemas && echo Compile TypeScript && echo Bundle API && echo Copy assets" - ], - "resolved_options": { - "cwd": "/packages/api", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/api#dev` - -```json -{ - "task_display": { - "package_name": "@test/api", - "task_name": "dev", - "package_path": "/packages/api" - }, - "resolved_config": { - "commands": [ - "echo Watch mode && echo Start dev server" - ], - "resolved_options": { - "cwd": "/packages/api", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/api#start` - -```json -{ - "task_display": { - "package_name": "@test/api", - "task_name": "start", - "package_path": "/packages/api" - }, - "resolved_config": { - "commands": [ - "echo Starting API server" - ], - "resolved_options": { - "cwd": "/packages/api", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/api#test` - -```json -{ - "task_display": { - "package_name": "@test/api", - "task_name": "test", - "package_path": "/packages/api" - }, - "resolved_config": { - "commands": [ - "echo Testing API" - ], - "resolved_options": { - "cwd": "/packages/api", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#build` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "build", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo Clean dist && echo Build client && echo Build server && echo Generate manifest && echo Optimize assets" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#deploy` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "deploy", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo Validate && echo Upload && echo Verify" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#dev` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "dev", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo Running dev server" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#preview` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "preview", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo Preview build" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#test` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "test", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo Unit tests && echo Integration tests" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/config#build` - -```json -{ - "task_display": { - "package_name": "@test/config", - "task_name": "build", - "package_path": "/packages/config" - }, - "resolved_config": { - "commands": [ - "echo Building config" - ], - "resolved_options": { - "cwd": "/packages/config", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/config#validate` - -```json -{ - "task_display": { - "package_name": "@test/config", - "task_name": "validate", - "package_path": "/packages/config" - }, - "resolved_config": { - "commands": [ - "echo Validating config" - ], - "resolved_options": { - "cwd": "/packages/config", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/pkg#special#build` - -```json -{ - "task_display": { - "package_name": "@test/pkg#special", - "task_name": "build", - "package_path": "/packages/pkg#special" - }, - "resolved_config": { - "commands": [ - "echo Building package with hash" - ], - "resolved_options": { - "cwd": "/packages/pkg#special", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/pkg#special#test` - -```json -{ - "task_display": { - "package_name": "@test/pkg#special", - "task_name": "test", - "package_path": "/packages/pkg#special" - }, - "resolved_config": { - "commands": [ - "echo Testing package with hash" - ], - "resolved_options": { - "cwd": "/packages/pkg#special", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/shared#build` - -```json -{ - "task_display": { - "package_name": "@test/shared", - "task_name": "build", - "package_path": "/packages/shared" - }, - "resolved_config": { - "commands": [ - "echo Cleaning && echo Compiling shared && echo Generating types" - ], - "resolved_options": { - "cwd": "/packages/shared", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/shared#lint` - -```json -{ - "task_display": { - "package_name": "@test/shared", - "task_name": "lint", - "package_path": "/packages/shared" - }, - "resolved_config": { - "commands": [ - "echo Linting shared" - ], - "resolved_options": { - "cwd": "/packages/shared", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/shared#test` - -```json -{ - "task_display": { - "package_name": "@test/shared", - "task_name": "test", - "package_path": "/packages/shared" - }, - "resolved_config": { - "commands": [ - "echo Setting up test env && echo Running tests && echo Cleanup" - ], - "resolved_options": { - "cwd": "/packages/shared", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/shared#typecheck` - -```json -{ - "task_display": { - "package_name": "@test/shared", - "task_name": "typecheck", - "package_path": "/packages/shared" - }, - "resolved_config": { - "commands": [ - "echo Type checking shared" - ], - "resolved_options": { - "cwd": "/packages/shared", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/tools#generate` - -```json -{ - "task_display": { - "package_name": "@test/tools", - "task_name": "generate", - "package_path": "/packages/tools" - }, - "resolved_config": { - "commands": [ - "echo Generating tools" - ], - "resolved_options": { - "cwd": "/packages/tools", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/tools#validate` - -```json -{ - "task_display": { - "package_name": "@test/tools", - "task_name": "validate", - "package_path": "/packages/tools" - }, - "resolved_config": { - "commands": [ - "echo Validating" - ], - "resolved_options": { - "cwd": "/packages/tools", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/ui#build` - -```json -{ - "task_display": { - "package_name": "@test/ui", - "task_name": "build", - "package_path": "/packages/ui" - }, - "resolved_config": { - "commands": [ - "echo Compile styles && echo Build components && echo Generate types" - ], - "resolved_options": { - "cwd": "/packages/ui", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/ui#lint` - -```json -{ - "task_display": { - "package_name": "@test/ui", - "task_name": "lint", - "package_path": "/packages/ui" - }, - "resolved_config": { - "commands": [ - "echo Linting UI" - ], - "resolved_options": { - "cwd": "/packages/ui", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/ui#storybook` - -```json -{ - "task_display": { - "package_name": "@test/ui", - "task_name": "storybook", - "package_path": "/packages/ui" - }, - "resolved_config": { - "commands": [ - "echo Running storybook" - ], - "resolved_options": { - "cwd": "/packages/ui", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/ui#test` - -```json -{ - "task_display": { - "package_name": "@test/ui", - "task_name": "test", - "package_path": "/packages/ui" - }, - "resolved_config": { - "commands": [ - "echo Testing UI" - ], - "resolved_options": { - "cwd": "/packages/ui", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/vite-task.json deleted file mode 100644 index a6d6c501e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/comprehensive_task_graph/vite-task.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - // Enables caching for all package.json scripts in the workspace. - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/package.json deleted file mode 100644 index b2e86d92d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "conflict-test-workspace", - "version": "1.0.0" -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/scope-a-b/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/scope-a-b/package.json deleted file mode 100644 index f87d50870..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/scope-a-b/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/scope-a#b", - "version": "1.0.0", - "scripts": { - "c": "echo Task c in scope-a#b" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/scope-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/scope-a/package.json deleted file mode 100644 index f3f301d21..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/scope-a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/scope-a", - "version": "1.0.0", - "scripts": { - "b#c": "echo Task b#c in scope-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/test-package/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/test-package/package.json deleted file mode 100644 index 8808c6ce4..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/test-package/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "@test/test-package", - "version": "1.0.0" -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/test-package/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/test-package/vite-task.json deleted file mode 100644 index aca6743cc..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/packages/test-package/vite-task.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "tasks": { - "test": { - "command": "echo Testing", - "cache": true, - "dependsOn": ["@test/scope-a#b#c"] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/snapshots/task_graph.md deleted file mode 100644 index 3aa5f911f..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/snapshots/task_graph.md +++ /dev/null @@ -1,127 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/scope-a#b#c"] - task_1["/packages/scope-a-b#c"] - task_2["/packages/test-package#test"] - task_2 --> task_1 -``` - -## `/packages/scope-a#b#c` - -```json -{ - "task_display": { - "package_name": "@test/scope-a", - "task_name": "b#c", - "package_path": "/packages/scope-a" - }, - "resolved_config": { - "commands": [ - "echo Task b#c in scope-a" - ], - "resolved_options": { - "cwd": "/packages/scope-a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/scope-a-b#c` - -```json -{ - "task_display": { - "package_name": "@test/scope-a#b", - "task_name": "c", - "package_path": "/packages/scope-a-b" - }, - "resolved_config": { - "commands": [ - "echo Task c in scope-a#b" - ], - "resolved_options": { - "cwd": "/packages/scope-a-b", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/test-package#test` - -```json -{ - "task_display": { - "package_name": "@test/test-package", - "task_name": "test", - "package_path": "/packages/test-package" - }, - "resolved_config": { - "commands": [ - "echo Testing" - ], - "resolved_options": { - "cwd": "/packages/test-package", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/conflict_test/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/package.json deleted file mode 100644 index 042bf6145..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "cycle-dependency-test" -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots.toml deleted file mode 100644 index c1ab0ab26..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Tests that cycle dependencies are detected at plan time - -[[plan]] -name = "cycle_dependency_error" -args = ["run", "task-a"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots/query_cycle_dependency_error.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots/query_cycle_dependency_error.snap deleted file mode 100644 index be30147e3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots/query_cycle_dependency_error.snap +++ /dev/null @@ -1 +0,0 @@ -Cycle dependency detected: cycle-dependency-test#task-a -> cycle-dependency-test#task-b -> cycle-dependency-test#task-a \ No newline at end of file diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots/task_graph.md deleted file mode 100644 index ffc1aea24..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/snapshots/task_graph.md +++ /dev/null @@ -1,88 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#task-a"] - task_0 --> task_1 - task_1["/#task-b"] - task_1 --> task_0 -``` - -## `/#task-a` - -```json -{ - "task_display": { - "package_name": "cycle-dependency-test", - "task_name": "task-a", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo a" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#task-b` - -```json -{ - "task_display": { - "package_name": "cycle-dependency-test", - "task_name": "task-b", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo b" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/vite-task.json deleted file mode 100644 index 82baa8300..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/cycle_dependency/vite-task.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "tasks": { - "task-a": { - "command": "echo a", - "dependsOn": ["task-b"] - }, - "task-b": { - "command": "echo b", - "dependsOn": ["task-a"] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/package.json deleted file mode 100644 index 0967ef424..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/package.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/packages/a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/packages/a/package.json deleted file mode 100644 index 7b44c34d2..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/packages/a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/a", - "version": "1.0.0", - "dependencies": { - "@test/b": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/packages/a/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/packages/a/vite-task.json deleted file mode 100644 index 6e52713b4..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/packages/a/vite-task.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tasks": { - "build": { - "command": "build a", - "dependsOn": ["@test/b#build"] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/packages/b/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/packages/b/package.json deleted file mode 100644 index 2d4981fc7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/packages/b/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/b", - "scripts": { - "build": "build b" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/snapshots/task_graph.md deleted file mode 100644 index 7106003d3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/snapshots/task_graph.md +++ /dev/null @@ -1,87 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/a#build"] - task_0 --> task_1 - task_1["/packages/b#build"] -``` - -## `/packages/a#build` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "build a" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/b#build` - -```json -{ - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "resolved_config": { - "commands": [ - "build b" - ], - "resolved_options": { - "cwd": "/packages/b", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/dependency_both_topo_and_explicit/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/package.json deleted file mode 100644 index b65cafad5..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "test-workspace", - "private": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/packages/pkg-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/packages/pkg-a/package.json deleted file mode 100644 index 59cfc4768..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/packages/pkg-a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/duplicate", - "version": "1.0.0", - "scripts": { - "build": "echo build-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/packages/pkg-b/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/packages/pkg-b/package.json deleted file mode 100644 index aaf60658a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/packages/pkg-b/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/duplicate", - "version": "1.0.0", - "scripts": { - "build": "echo build-b" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots.toml deleted file mode 100644 index d92619e44..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots.toml +++ /dev/null @@ -1,17 +0,0 @@ -# Tests that running pkg#task errors when multiple packages share the same name, -# while --filter with the same name still matches all packages. - -[[plan]] -compact = true -name = "ambiguous_package_name" -args = ["run", "@test/duplicate#build"] - -[[plan]] -compact = true -name = "ambiguous_package_name_with_transitive" -args = ["run", "-t", "@test/duplicate#build"] - -[[plan]] -compact = true -name = "filter_matches_both_duplicates" -args = ["run", "--filter", "@test/duplicate", "build"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/query_ambiguous_package_name.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/query_ambiguous_package_name.snap deleted file mode 100644 index 7fd2c448a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/query_ambiguous_package_name.snap +++ /dev/null @@ -1 +0,0 @@ -Package name '@test/duplicate' is ambiguous; found in multiple locations: /packages/pkg-a, /packages/pkg-b \ No newline at end of file diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/query_ambiguous_package_name_with_transitive.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/query_ambiguous_package_name_with_transitive.snap deleted file mode 100644 index 7fd2c448a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/query_ambiguous_package_name_with_transitive.snap +++ /dev/null @@ -1 +0,0 @@ -Package name '@test/duplicate' is ambiguous; found in multiple locations: /packages/pkg-a, /packages/pkg-b \ No newline at end of file diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/query_filter_matches_both_duplicates.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/query_filter_matches_both_duplicates.jsonc deleted file mode 100644 index 1554751d3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/query_filter_matches_both_duplicates.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -// run --filter @test/duplicate build -{ - "packages/pkg-a#build": [], - "packages/pkg-b#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/task_graph.md deleted file mode 100644 index 0908cab0b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/duplicate_package_names/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/pkg-a#build"] - task_1["/packages/pkg-b#build"] -``` - -## `/packages/pkg-a#build` - -```json -{ - "task_display": { - "package_name": "@test/duplicate", - "task_name": "build", - "package_path": "/packages/pkg-a" - }, - "resolved_config": { - "commands": [ - "echo build-a" - ], - "resolved_options": { - "cwd": "/packages/pkg-a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/pkg-b#build` - -```json -{ - "task_display": { - "package_name": "@test/duplicate", - "task_name": "build", - "package_path": "/packages/pkg-b" - }, - "resolved_config": { - "commands": [ - "echo build-b" - ], - "resolved_options": { - "cwd": "/packages/pkg-b", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/package.json deleted file mode 100644 index a6df9e575..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "empty-package-test-root", - "version": "1.0.0" -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/another-empty/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/another-empty/package.json deleted file mode 100644 index 1dc332816..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/another-empty/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "version": "2.0.0", - "dependencies": { - "normal-package": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/another-empty/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/another-empty/vite-task.json deleted file mode 100644 index b69972a3b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/another-empty/vite-task.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo 'Building another-empty package'", - "cache": true, - "dependsOn": ["lint", "normal-package#test"] - }, - "test": { - "command": "echo 'Testing another-empty package'", - "cache": true - }, - "lint": { - "command": "echo 'Linting another-empty package'", - "cache": true - }, - "deploy": { - "command": "echo 'Deploying another-empty package'", - "cache": false, - "dependsOn": ["build", "test"] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/empty-name/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/empty-name/package.json deleted file mode 100644 index 1587a6696..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/empty-name/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "version": "1.0.0" -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/empty-name/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/empty-name/vite-task.json deleted file mode 100644 index 8ddea45b5..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/empty-name/vite-task.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo 'Building empty-name package'", - "cache": true, - "dependsOn": ["test"] - }, - "test": { - "command": "echo 'Testing empty-name package'", - "cache": true - }, - "lint": { - "command": "echo 'Linting empty-name package'", - "cache": true - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/normal-package/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/normal-package/package.json deleted file mode 100644 index cfc6fd5d6..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/normal-package/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "normal-package", - "version": "1.0.0" -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/normal-package/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/normal-package/vite-task.json deleted file mode 100644 index 26981cf12..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/packages/normal-package/vite-task.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo 'Building normal-package'", - "cache": true - }, - "test": { - "command": "echo 'Testing normal-package'", - "cache": true - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/snapshots/task_graph.md deleted file mode 100644 index 095d71a19..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/snapshots/task_graph.md +++ /dev/null @@ -1,354 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/another-empty#build"] - task_0 --> task_2 - task_0 --> task_8 - task_1["/packages/another-empty#deploy"] - task_1 --> task_0 - task_1 --> task_3 - task_2["/packages/another-empty#lint"] - task_3["/packages/another-empty#test"] - task_4["/packages/empty-name#build"] - task_4 --> task_6 - task_5["/packages/empty-name#lint"] - task_6["/packages/empty-name#test"] - task_7["/packages/normal-package#build"] - task_8["/packages/normal-package#test"] -``` - -## `/packages/another-empty#build` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "build", - "package_path": "/packages/another-empty" - }, - "resolved_config": { - "commands": [ - "echo 'Building another-empty package'" - ], - "resolved_options": { - "cwd": "/packages/another-empty", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/another-empty#deploy` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "deploy", - "package_path": "/packages/another-empty" - }, - "resolved_config": { - "commands": [ - "echo 'Deploying another-empty package'" - ], - "resolved_options": { - "cwd": "/packages/another-empty", - "cache_config": null - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/another-empty#lint` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/packages/another-empty" - }, - "resolved_config": { - "commands": [ - "echo 'Linting another-empty package'" - ], - "resolved_options": { - "cwd": "/packages/another-empty", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/another-empty#test` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "test", - "package_path": "/packages/another-empty" - }, - "resolved_config": { - "commands": [ - "echo 'Testing another-empty package'" - ], - "resolved_options": { - "cwd": "/packages/another-empty", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/empty-name#build` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "build", - "package_path": "/packages/empty-name" - }, - "resolved_config": { - "commands": [ - "echo 'Building empty-name package'" - ], - "resolved_options": { - "cwd": "/packages/empty-name", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/empty-name#lint` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/packages/empty-name" - }, - "resolved_config": { - "commands": [ - "echo 'Linting empty-name package'" - ], - "resolved_options": { - "cwd": "/packages/empty-name", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/empty-name#test` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "test", - "package_path": "/packages/empty-name" - }, - "resolved_config": { - "commands": [ - "echo 'Testing empty-name package'" - ], - "resolved_options": { - "cwd": "/packages/empty-name", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/normal-package#build` - -```json -{ - "task_display": { - "package_name": "normal-package", - "task_name": "build", - "package_path": "/packages/normal-package" - }, - "resolved_config": { - "commands": [ - "echo 'Building normal-package'" - ], - "resolved_options": { - "cwd": "/packages/normal-package", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/normal-package#test` - -```json -{ - "task_display": { - "package_name": "normal-package", - "task_name": "test", - "package_path": "/packages/normal-package" - }, - "resolved_config": { - "commands": [ - "echo 'Testing normal-package'" - ], - "resolved_options": { - "cwd": "/packages/normal-package", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/empty_package_test/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/package.json deleted file mode 100644 index ea2822407..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "explicit-deps-workspace", - "version": "1.0.0", - "private": true, - "workspaces": [ - "packages/*" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/app/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/app/package.json deleted file mode 100644 index 65f6ee972..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/app/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "@test/app", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/app'", - "test": "echo 'Testing @test/app'", - "start": "echo 'Starting @test/app'" - }, - "dependencies": { - "@test/utils": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/app/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/app/vite-task.json deleted file mode 100644 index 5f218989c..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/app/vite-task.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "tasks": { - "deploy": { - "command": "deploy-script --prod", - "cache": false, - "dependsOn": ["@test/app#build", "@test/app#test", "@test/utils#lint"] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/core/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/core/package.json deleted file mode 100644 index 9cdb5f46d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/core/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@test/core", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/core'", - "test": "echo 'Testing @test/core'", - "clean": "echo 'Cleaning @test/core'" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/core/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/core/vite-task.json deleted file mode 100644 index 1e27bd969..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/core/vite-task.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "tasks": { - "lint": { - "command": "eslint src", - "cache": true, - "dependsOn": ["@test/core#clean"] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/utils/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/utils/package.json deleted file mode 100644 index 2e879bc08..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/utils/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@test/utils", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/utils'", - "test": "echo 'Testing @test/utils'" - }, - "dependencies": { - "@test/core": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/utils/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/utils/vite-task.json deleted file mode 100644 index f974f9b0d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/packages/utils/vite-task.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "tasks": { - "lint": { - "command": "eslint src", - "cache": true, - "dependsOn": ["@test/core#build", "@test/utils#build"] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/snapshots/task_graph.md deleted file mode 100644 index e38f11608..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/snapshots/task_graph.md +++ /dev/null @@ -1,435 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/app#build"] - task_1["/packages/app#deploy"] - task_1 --> task_0 - task_1 --> task_3 - task_1 --> task_9 - task_2["/packages/app#start"] - task_3["/packages/app#test"] - task_4["/packages/core#build"] - task_5["/packages/core#clean"] - task_6["/packages/core#lint"] - task_6 --> task_5 - task_7["/packages/core#test"] - task_8["/packages/utils#build"] - task_9["/packages/utils#lint"] - task_9 --> task_4 - task_9 --> task_8 - task_10["/packages/utils#test"] -``` - -## `/packages/app#build` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "build", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/app'" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#deploy` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "deploy", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "deploy-script --prod" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": null - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/app#start` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "start", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo 'Starting @test/app'" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#test` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "test", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo 'Testing @test/app'" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/core#build` - -```json -{ - "task_display": { - "package_name": "@test/core", - "task_name": "build", - "package_path": "/packages/core" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/core'" - ], - "resolved_options": { - "cwd": "/packages/core", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/core#clean` - -```json -{ - "task_display": { - "package_name": "@test/core", - "task_name": "clean", - "package_path": "/packages/core" - }, - "resolved_config": { - "commands": [ - "echo 'Cleaning @test/core'" - ], - "resolved_options": { - "cwd": "/packages/core", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/core#lint` - -```json -{ - "task_display": { - "package_name": "@test/core", - "task_name": "lint", - "package_path": "/packages/core" - }, - "resolved_config": { - "commands": [ - "eslint src" - ], - "resolved_options": { - "cwd": "/packages/core", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/core#test` - -```json -{ - "task_display": { - "package_name": "@test/core", - "task_name": "test", - "package_path": "/packages/core" - }, - "resolved_config": { - "commands": [ - "echo 'Testing @test/core'" - ], - "resolved_options": { - "cwd": "/packages/core", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/utils#build` - -```json -{ - "task_display": { - "package_name": "@test/utils", - "task_name": "build", - "package_path": "/packages/utils" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/utils'" - ], - "resolved_options": { - "cwd": "/packages/utils", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/utils#lint` - -```json -{ - "task_display": { - "package_name": "@test/utils", - "task_name": "lint", - "package_path": "/packages/utils" - }, - "resolved_config": { - "commands": [ - "eslint src" - ], - "resolved_options": { - "cwd": "/packages/utils", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/utils#test` - -```json -{ - "task_display": { - "package_name": "@test/utils", - "task_name": "test", - "package_path": "/packages/utils" - }, - "resolved_config": { - "commands": [ - "echo 'Testing @test/utils'" - ], - "resolved_options": { - "cwd": "/packages/utils", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/explicit_deps_workspace/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/package.json deleted file mode 100644 index 7f0805cc9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "@test/extra-args-not-forwarded-to-depends-on" -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots.toml deleted file mode 100644 index a0922c917..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots.toml +++ /dev/null @@ -1,7 +0,0 @@ -# Tests that extra args (`vt run test some-filter`) are forwarded only to the -# explicitly requested task (`test`), not to `dependsOn` tasks (`build`). -# https://github.com/voidzero-dev/vite-task/issues/324 - -[[plan]] -name = "extra_args_only_reach_requested_task" -args = ["run", "test", "some-filter"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/query_extra_args_only_reach_requested_task.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/query_extra_args_only_reach_requested_task.jsonc deleted file mode 100644 index c7ace7882..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/query_extra_args_only_reach_requested_task.jsonc +++ /dev/null @@ -1,183 +0,0 @@ -// run test some-filter -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/extra-args-not-forwarded-to-depends-on", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/extra-args-not-forwarded-to-depends-on", - "task_name": "build", - "package_path": "/" - }, - "command": "vt tool print build", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "build" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "build", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "build" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - }, - { - "key": [ - "/", - "test" - ], - "node": { - "task_display": { - "package_name": "@test/extra-args-not-forwarded-to-depends-on", - "task_name": "test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/extra-args-not-forwarded-to-depends-on", - "task_name": "test", - "package_path": "/" - }, - "command": "vt tool print test some-filter", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "test", - "some-filter" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "test", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [ - "some-filter" - ], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "test", - "some-filter" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [ - [ - "/", - "build" - ] - ] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/task_graph.md deleted file mode 100644 index 5f61403e1..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/snapshots/task_graph.md +++ /dev/null @@ -1,87 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#test"] - task_1 --> task_0 -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "@test/extra-args-not-forwarded-to-depends-on", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt tool print build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/extra-args-not-forwarded-to-depends-on", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt tool print test" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/vite-task.json deleted file mode 100644 index d52e0377a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/extra_args_not_forwarded_to_depends_on/vite-task.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "cache": true, - "tasks": { - "build": { - "command": "vt tool print build" - }, - "test": { - "command": "vt tool print test", - "dependsOn": ["build"] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/package.json deleted file mode 100644 index e859d763d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "test-workspace", - "private": true, - "scripts": { - "check": "echo 'Checking workspace'" - }, - "dependencies": { - "@test/core": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/app/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/app/package.json deleted file mode 100644 index f50587f18..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/app/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@test/app", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/app'", - "check": "echo 'Checking @test/app'", - "test": "echo 'Testing @test/app'", - "deploy": "vt run --filter .... build" - }, - "dependencies": { - "@test/lib": "workspace:*" - }, - "devDependencies": { - "@test/utils": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/cli/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/cli/package.json deleted file mode 100644 index 7256d6677..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/cli/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@test/cli", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/cli'", - "test": "echo 'Testing @test/cli'" - }, - "dependencies": { - "@test/core": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/core/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/core/package.json deleted file mode 100644 index 3b5bd74e0..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/core/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@test/core", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/core'", - "check": "echo 'Checking @test/core'", - "test": "echo 'Testing @test/core'" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/lib/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/lib/package.json deleted file mode 100644 index 91f1ff790..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/lib/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@test/lib", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/lib'", - "test": "echo 'Testing @test/lib'" - }, - "dependencies": { - "@test/core": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/utils/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/utils/package.json deleted file mode 100644 index a544e0c80..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/packages/utils/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/utils", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/utils'" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots.toml deleted file mode 100644 index ac9bdf3ce..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots.toml +++ /dev/null @@ -1,227 +0,0 @@ -# Tests pnpm-style --filter package selection - -# pnpm: "select just a package by name" -[[plan]] -compact = true -name = "filter_by_exact_name" -args = ["run", "--filter", "@test/app", "build"] - -# pnpm: name pattern matching with * -[[plan]] -compact = true -name = "filter_by_glob" -args = ["run", "--filter", "@test/*", "build"] - -# pnpm: "select package with dependencies" -[[plan]] -compact = true -name = "filter_with_dependencies" -args = ["run", "--filter", "@test/app...", "build"] - -# pnpm: "select only package dependencies (excluding the package itself)" -[[plan]] -compact = true -name = "filter_dependencies_only_exclude_self" -args = ["run", "--filter", "@test/app^...", "build"] - -# pnpm: "select package with dependents" -[[plan]] -compact = true -name = "filter_with_dependents" -args = ["run", "--filter", "...@test/core", "build"] - -# pnpm: "select dependents excluding package itself" -[[plan]] -compact = true -name = "filter_dependents_only_exclude_self" -args = ["run", "--filter", "...^@test/core", "build"] - -# pnpm: "select package with dependencies and dependents, including dependent dependencies" -[[plan]] -compact = true -name = "filter_both_deps_and_dependents" -args = ["run", "--filter", "...@test/lib...", "build"] - -# pnpm Example 4: two cherry-picked packages with a dependency between them. -# app depends on lib. Both cherry-picked (no "..."). The induced subgraph -# still preserves the app→lib edge — lib#build runs before app#build. -[[plan]] -compact = true -name = "cherry_picked_filters_respect_dependency_order" -args = ["run", "--filter", "@test/app", "--filter", "@test/lib", "build"] - -# pnpm: "filter using two selectors" (independent packages) -[[plan]] -compact = true -name = "multiple_filters_union" -args = ["run", "--filter", "@test/app", "--filter", "@test/cli", "build"] - -# pnpm: space-separated selectors in a single --filter value -[[plan]] -compact = true -name = "filter_space_separated_in_single_value" -args = ["run", "--filter", "@test/app @test/cli", "build"] - -# pnpm: "select by parentDir and exclude one package by pattern" -[[plan]] -compact = true -name = "filter_include_and_exclude" -args = ["run", "--filter", "@test/app...", "--filter", "!@test/utils", "build"] - -# pnpm: "select all packages except one" -[[plan]] -compact = true -name = "exclude_only_filter" -args = ["run", "--filter", "!@test/core", "build"] - -# pnpm: "select by parentDir" — exact path, no glob -[[plan]] -compact = true -name = "filter_by_directory" -args = ["run", "--filter", "./packages/app", "build"] - -# pnpm silently discards `...` on unbraced path selectors (ambiguity with `..`). -# `./packages/app...` is equivalent to `./packages/app`. Use `{./packages/app}...` for traversal. -# Ref: https://github.com/pnpm/pnpm/issues/1651 -[[plan]] -compact = true -name = "filter_by_directory_ignores_trailing_dots" -args = ["run", "--filter", "./packages/app...", "build"] - -# `....` = `.` + `...` — traversal discarded on unbraced path, equivalent to `--filter .` -[[plan]] -compact = true -name = "filter_dot_ignores_trailing_dots_from_package" -args = ["run", "--filter", "....", "build"] -cwd = "packages/app" - -# `.....` = `..` + `...` — traversal discarded on unbraced path, equivalent to `--filter ..` -[[plan]] -compact = true -name = "filter_dotdot_ignores_trailing_dots" -args = ["run", "--filter", ".....", "build"] -cwd = "packages/app/src" - -# pnpm glob-dir: one level under packages/ -[[plan]] -compact = true -name = "filter_by_directory_glob_star" -args = ["run", "--filter", "./packages/*", "build"] - -# pnpm glob-dir: recursive under packages/ -[[plan]] -compact = true -name = "filter_by_directory_glob_double_star" -args = ["run", "--filter", "./packages/**", "build"] - -# -t flag: current package + transitive deps (NOT equivalent to `--filter ....` -# because pnpm discards traversal on unbraced paths; -t bypasses parse_filter). -[[plan]] -compact = true -name = "transitive_flag" -args = ["run", "-t", "build"] -cwd = "packages/app" - -# -t from a subfolder inside a package — ContainingPackage walks up to find app. -[[plan]] -compact = true -name = "transitive_flag_from_package_subfolder" -args = ["run", "-t", "build"] -cwd = "packages/app/src/components" - -# -t with package#task specifier = --filter package... task -[[plan]] -compact = true -name = "transitive_with_package_specifier" -args = ["run", "-t", "@test/app#build"] - -# recursive flag = All -[[plan]] -compact = true -name = "recursive_flag" -args = ["run", "-r", "build"] - -# pnpm Example 5: lib... selects {lib,core}. cli is cherry-picked. -# ALL original edges between selected packages are preserved (induced subgraph). -# cli→core edge IS present — cli#build waits for core#build. -[[plan]] -compact = true -name = "mixed_traversal_filters" -args = ["run", "--filter", "@test/lib...", "--filter", "@test/cli", "build"] - -# pnpm Example 1 (partial): packages without the task are silently skipped. -# @test/utils has no "test" — silently skipped; app, lib, core run in order. -[[plan]] -compact = true -name = "filter_deps_skips_packages_without_task" -args = ["run", "--filter", "@test/app...", "test"] - -# pnpm: braced path selector — `{./path}` is equivalent to `./path` for exact directories -[[plan]] -compact = true -name = "filter_by_braced_path" -args = ["run", "--filter", "{./packages/app}", "build"] - -# braced path with dependency traversal — `{./path}...` -[[plan]] -compact = true -name = "filter_by_braced_path_with_dependencies" -args = ["run", "--filter", "{./packages/app}...", "build"] - -# braced path with dependents traversal — `...{./path}` -[[plan]] -compact = true -name = "filter_by_braced_path_with_dependents" -args = ["run", "--filter", "...{./packages/core}", "build"] - -# pnpm: name AND directory intersection — `pattern{./dir}` matches packages -# whose name matches the glob AND whose directory matches the path. -[[plan]] -compact = true -name = "filter_by_name_and_directory_intersection" -args = ["run", "--filter", "@test/*{./packages/lib}", "build"] - -# pnpm: multiple exclusion-only filters — each exclusion subtracts from the full set. -[[plan]] -compact = true -name = "multiple_exclusion_only_filters" -args = ["run", "--filter", "!@test/app", "--filter", "!@test/core", "build"] - -# pnpm: excluding a package that doesn't exist — no-op, returns all packages. -[[plan]] -compact = true -name = "exclude_nonexistent_package" -args = ["run", "--filter", "!nonexistent", "build"] - -# script containing "vt run --filter .... build" — expanded in plan -[[plan]] -compact = true -name = "nested_vt_run_with_filter_in_script" -args = ["run", "--filter", "@test/app", "deploy"] - -# -w / --workspace-root: run task on workspace root package only -[[plan]] -compact = true -name = "workspace_root_flag" -args = ["run", "-w", "check"] - -# pnpm: -w is additive — workspace root is unioned with filtered packages. -# Both root and app have "check", so both appear (no dependency edge between them). -[[plan]] -compact = true -name = "workspace_root_with_filter" -args = ["run", "-w", "--filter", "@test/app", "check"] - -# -w with -r: redundant (all packages already includes root). -# Root, app, and core all have "check" — root appears, proving -r already covers it. -[[plan]] -compact = true -name = "workspace_root_with_recursive" -args = ["run", "-w", "-r", "check"] - -# -w with -t: workspace root with transitive dependencies. -# Root depends on @test/core; both have "check", so core#check → root#check. -[[plan]] -compact = true -name = "workspace_root_with_transitive" -args = ["run", "-w", "-t", "check"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_cherry_picked_filters_respect_dependency_order.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_cherry_picked_filters_respect_dependency_order.jsonc deleted file mode 100644 index e98fff7a2..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_cherry_picked_filters_respect_dependency_order.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -// run --filter @test/app --filter @test/lib build -{ - "packages/app#build": [ - "packages/lib#build" - ], - "packages/lib#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_exclude_nonexistent_package.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_exclude_nonexistent_package.jsonc deleted file mode 100644 index 089c547ee..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_exclude_nonexistent_package.jsonc +++ /dev/null @@ -1,15 +0,0 @@ -// run --filter !nonexistent build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/cli#build": [ - "packages/core#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_exclude_only_filter.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_exclude_only_filter.jsonc deleted file mode 100644 index 683d4282d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_exclude_only_filter.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -// run --filter !@test/core build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/cli#build": [], - "packages/lib#build": [], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_both_deps_and_dependents.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_both_deps_and_dependents.jsonc deleted file mode 100644 index 224cf44e3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_both_deps_and_dependents.jsonc +++ /dev/null @@ -1,12 +0,0 @@ -// run --filter ...@test/lib... build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_braced_path.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_braced_path.jsonc deleted file mode 100644 index 244e8e154..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_braced_path.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run --filter {./packages/app} build -{ - "packages/app#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_braced_path_with_dependencies.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_braced_path_with_dependencies.jsonc deleted file mode 100644 index 21f2f58d7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_braced_path_with_dependencies.jsonc +++ /dev/null @@ -1,12 +0,0 @@ -// run --filter {./packages/app}... build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_braced_path_with_dependents.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_braced_path_with_dependents.jsonc deleted file mode 100644 index b6fdd46e0..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_braced_path_with_dependents.jsonc +++ /dev/null @@ -1,13 +0,0 @@ -// run --filter ...{./packages/core} build -{ - "packages/app#build": [ - "packages/lib#build" - ], - "packages/cli#build": [ - "packages/core#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory.jsonc deleted file mode 100644 index 6b10d6358..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run --filter ./packages/app build -{ - "packages/app#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory_glob_double_star.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory_glob_double_star.jsonc deleted file mode 100644 index f5aed6898..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory_glob_double_star.jsonc +++ /dev/null @@ -1,15 +0,0 @@ -// run --filter ./packages/** build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/cli#build": [ - "packages/core#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory_glob_star.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory_glob_star.jsonc deleted file mode 100644 index 8b5b1455d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory_glob_star.jsonc +++ /dev/null @@ -1,15 +0,0 @@ -// run --filter ./packages/* build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/cli#build": [ - "packages/core#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory_ignores_trailing_dots.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory_ignores_trailing_dots.jsonc deleted file mode 100644 index 8280250b8..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_directory_ignores_trailing_dots.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run --filter ./packages/app... build -{ - "packages/app#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_exact_name.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_exact_name.jsonc deleted file mode 100644 index d99036e3d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_exact_name.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run --filter @test/app build -{ - "packages/app#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_glob.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_glob.jsonc deleted file mode 100644 index cadfd6010..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_glob.jsonc +++ /dev/null @@ -1,15 +0,0 @@ -// run --filter @test/* build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/cli#build": [ - "packages/core#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_name_and_directory_intersection.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_name_and_directory_intersection.jsonc deleted file mode 100644 index de1ce0e20..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_by_name_and_directory_intersection.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run --filter @test/*{./packages/lib} build -{ - "packages/lib#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dependencies_only_exclude_self.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dependencies_only_exclude_self.jsonc deleted file mode 100644 index 5a72cc6f9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dependencies_only_exclude_self.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -// run --filter @test/app^... build -{ - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dependents_only_exclude_self.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dependents_only_exclude_self.jsonc deleted file mode 100644 index a075368e0..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dependents_only_exclude_self.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -// run --filter ...^@test/core build -{ - "packages/app#build": [ - "packages/lib#build" - ], - "packages/cli#build": [], - "packages/lib#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_deps_skips_packages_without_task.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_deps_skips_packages_without_task.jsonc deleted file mode 100644 index e8ca7e930..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_deps_skips_packages_without_task.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -// run --filter @test/app... test -{ - "packages/app#test": [ - "packages/lib#test" - ], - "packages/core#test": [], - "packages/lib#test": [ - "packages/core#test" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dot_ignores_trailing_dots_from_package.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dot_ignores_trailing_dots_from_package.jsonc deleted file mode 100644 index 8e7c2f024..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dot_ignores_trailing_dots_from_package.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run --filter .... build -{ - "packages/app#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dotdot_ignores_trailing_dots.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dotdot_ignores_trailing_dots.jsonc deleted file mode 100644 index 564da692f..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_dotdot_ignores_trailing_dots.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run --filter ..... build -{ - "packages/app#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_include_and_exclude.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_include_and_exclude.jsonc deleted file mode 100644 index 06a93da02..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_include_and_exclude.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -// run --filter @test/app... --filter !@test/utils build -{ - "packages/app#build": [ - "packages/lib#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_space_separated_in_single_value.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_space_separated_in_single_value.jsonc deleted file mode 100644 index 157c606a4..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_space_separated_in_single_value.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -// run --filter @test/app @test/cli build -{ - "packages/app#build": [], - "packages/cli#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_with_dependencies.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_with_dependencies.jsonc deleted file mode 100644 index 696f04cbb..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_with_dependencies.jsonc +++ /dev/null @@ -1,12 +0,0 @@ -// run --filter @test/app... build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_with_dependents.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_with_dependents.jsonc deleted file mode 100644 index 6a0f9a9ea..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_filter_with_dependents.jsonc +++ /dev/null @@ -1,13 +0,0 @@ -// run --filter ...@test/core build -{ - "packages/app#build": [ - "packages/lib#build" - ], - "packages/cli#build": [ - "packages/core#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_mixed_traversal_filters.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_mixed_traversal_filters.jsonc deleted file mode 100644 index ad86411ca..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_mixed_traversal_filters.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -// run --filter @test/lib... --filter @test/cli build -{ - "packages/cli#build": [ - "packages/core#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_multiple_exclusion_only_filters.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_multiple_exclusion_only_filters.jsonc deleted file mode 100644 index 1f8eb79a0..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_multiple_exclusion_only_filters.jsonc +++ /dev/null @@ -1,6 +0,0 @@ -// run --filter !@test/app --filter !@test/core build -{ - "packages/cli#build": [], - "packages/lib#build": [], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_multiple_filters_union.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_multiple_filters_union.jsonc deleted file mode 100644 index 46bf271f5..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_multiple_filters_union.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -// run --filter @test/app --filter @test/cli build -{ - "packages/app#build": [], - "packages/cli#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_nested_vt_run_with_filter_in_script.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_nested_vt_run_with_filter_in_script.jsonc deleted file mode 100644 index b9254537d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_nested_vt_run_with_filter_in_script.jsonc +++ /dev/null @@ -1,11 +0,0 @@ -// run --filter @test/app deploy -{ - "packages/app#deploy": { - "items": [ - { - "packages/app#build": [] - } - ], - "neighbors": [] - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_recursive_flag.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_recursive_flag.jsonc deleted file mode 100644 index 62a997a61..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_recursive_flag.jsonc +++ /dev/null @@ -1,15 +0,0 @@ -// run -r build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/cli#build": [ - "packages/core#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_transitive_flag.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_transitive_flag.jsonc deleted file mode 100644 index 902370b82..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_transitive_flag.jsonc +++ /dev/null @@ -1,12 +0,0 @@ -// run -t build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_transitive_flag_from_package_subfolder.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_transitive_flag_from_package_subfolder.jsonc deleted file mode 100644 index 902370b82..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_transitive_flag_from_package_subfolder.jsonc +++ /dev/null @@ -1,12 +0,0 @@ -// run -t build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_transitive_with_package_specifier.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_transitive_with_package_specifier.jsonc deleted file mode 100644 index 4c95ed76c..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_transitive_with_package_specifier.jsonc +++ /dev/null @@ -1,12 +0,0 @@ -// run -t @test/app#build -{ - "packages/app#build": [ - "packages/lib#build", - "packages/utils#build" - ], - "packages/core#build": [], - "packages/lib#build": [ - "packages/core#build" - ], - "packages/utils#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_flag.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_flag.jsonc deleted file mode 100644 index 2abe93c66..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_flag.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run -w check -{ - "#check": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_with_filter.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_with_filter.jsonc deleted file mode 100644 index b7a8bd031..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_with_filter.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -// run -w --filter @test/app check -{ - "#check": [], - "packages/app#check": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_with_recursive.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_with_recursive.jsonc deleted file mode 100644 index c50f78a9f..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_with_recursive.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -// run -w -r check -{ - "#check": [ - "packages/core#check" - ], - "packages/app#check": [ - "packages/core#check" - ], - "packages/core#check": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_with_transitive.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_with_transitive.jsonc deleted file mode 100644 index cf14f114e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/query_workspace_root_with_transitive.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -// run -w -t check -{ - "#check": [ - "packages/core#check" - ], - "packages/core#check": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/task_graph.md deleted file mode 100644 index 23067de0e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/snapshots/task_graph.md +++ /dev/null @@ -1,526 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#check"] - task_1["/packages/app#build"] - task_2["/packages/app#check"] - task_3["/packages/app#deploy"] - task_4["/packages/app#test"] - task_5["/packages/cli#build"] - task_6["/packages/cli#test"] - task_7["/packages/core#build"] - task_8["/packages/core#check"] - task_9["/packages/core#test"] - task_10["/packages/lib#build"] - task_11["/packages/lib#test"] - task_12["/packages/utils#build"] -``` - -## `/#check` - -```json -{ - "task_display": { - "package_name": "test-workspace", - "task_name": "check", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo 'Checking workspace'" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#build` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "build", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/app'" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#check` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "check", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo 'Checking @test/app'" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#deploy` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "deploy", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "vt run --filter .... build" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#test` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "test", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo 'Testing @test/app'" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/cli#build` - -```json -{ - "task_display": { - "package_name": "@test/cli", - "task_name": "build", - "package_path": "/packages/cli" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/cli'" - ], - "resolved_options": { - "cwd": "/packages/cli", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/cli#test` - -```json -{ - "task_display": { - "package_name": "@test/cli", - "task_name": "test", - "package_path": "/packages/cli" - }, - "resolved_config": { - "commands": [ - "echo 'Testing @test/cli'" - ], - "resolved_options": { - "cwd": "/packages/cli", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/core#build` - -```json -{ - "task_display": { - "package_name": "@test/core", - "task_name": "build", - "package_path": "/packages/core" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/core'" - ], - "resolved_options": { - "cwd": "/packages/core", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/core#check` - -```json -{ - "task_display": { - "package_name": "@test/core", - "task_name": "check", - "package_path": "/packages/core" - }, - "resolved_config": { - "commands": [ - "echo 'Checking @test/core'" - ], - "resolved_options": { - "cwd": "/packages/core", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/core#test` - -```json -{ - "task_display": { - "package_name": "@test/core", - "task_name": "test", - "package_path": "/packages/core" - }, - "resolved_config": { - "commands": [ - "echo 'Testing @test/core'" - ], - "resolved_options": { - "cwd": "/packages/core", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/lib#build` - -```json -{ - "task_display": { - "package_name": "@test/lib", - "task_name": "build", - "package_path": "/packages/lib" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/lib'" - ], - "resolved_options": { - "cwd": "/packages/lib", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/lib#test` - -```json -{ - "task_display": { - "package_name": "@test/lib", - "task_name": "test", - "package_path": "/packages/lib" - }, - "resolved_config": { - "commands": [ - "echo 'Testing @test/lib'" - ], - "resolved_options": { - "cwd": "/packages/lib", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/utils#build` - -```json -{ - "task_display": { - "package_name": "@test/utils", - "task_name": "build", - "package_path": "/packages/utils" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/utils'" - ], - "resolved_options": { - "cwd": "/packages/utils", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/filter_workspace/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/package.json deleted file mode 100644 index 600e5f1a7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "test" -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/snapshots.toml deleted file mode 100644 index 8370d8ceb..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/snapshots.toml +++ /dev/null @@ -1 +0,0 @@ -# Test that trailing `/` in glob patterns is expanded to `/**` diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/snapshots/task_graph.md deleted file mode 100644 index 0a59e2e64..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/snapshots/task_graph.md +++ /dev/null @@ -1,50 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "test", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": false, - "positive_globs": [ - "src/**" - ], - "negative_globs": [ - "dist/**" - ] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/vite-task.json deleted file mode 100644 index 14f8abfd8..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_trailing_slash/vite-task.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo build", - "input": ["src/", "!dist/"], - "cache": true - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/package.json deleted file mode 100644 index 7636edf1a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "root" -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/packages/app/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/packages/app/package.json deleted file mode 100644 index fd1f8f638..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/packages/app/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "app" -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/packages/app/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/packages/app/vite-task.json deleted file mode 100644 index 4808e3198..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/packages/app/vite-task.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo build", - "input": [ - "src/**", - { "pattern": "configs/tsconfig.json", "base": "workspace" }, - { "pattern": "!dist/**", "base": "workspace" } - ], - "cache": true - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/snapshots.toml deleted file mode 100644 index d27359b37..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/snapshots.toml +++ /dev/null @@ -1 +0,0 @@ -# Test glob patterns with workspace base directory diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/snapshots/task_graph.md deleted file mode 100644 index 9b24c886e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/input_workspace_base/snapshots/task_graph.md +++ /dev/null @@ -1,51 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/app#build"] -``` - -## `/packages/app#build` - -```json -{ - "task_display": { - "package_name": "app", - "task_name": "build", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo build" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": false, - "positive_globs": [ - "configs/tsconfig.json", - "packages/app/src/**" - ], - "negative_globs": [ - "dist/**" - ] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/package.json deleted file mode 100644 index b826c7857..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@test/nested-cache-override", - "scripts": { - "inner": "vtt print-file package.json", - "outer-no-cache": "vt run --no-cache inner", - "outer-cache": "vt run --cache inner", - "outer-inherit": "vt run inner" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots.toml deleted file mode 100644 index 2f8399a79..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots.toml +++ /dev/null @@ -1,31 +0,0 @@ -# Tests scope of --cache/--no-cache in nested vt run commands - -# Nested vt run --no-cache disables caching for inner task -[[plan]] -name = "nested___no_cache_disables_inner_task_caching" -args = ["run", "outer-no-cache"] - -# Nested vt run --cache enables caching for inner task -[[plan]] -name = "nested___cache_enables_inner_task_caching" -args = ["run", "outer-cache"] - -# Nested vt run without flags inherits parent resolved cache -[[plan]] -name = "nested_run_without_flags_inherits_parent_cache" -args = ["run", "outer-inherit"] - -# Outer --cache propagates to nested run without flags -[[plan]] -name = "outer___cache_propagates_to_nested_run_without_flags" -args = ["run", "--cache", "outer-inherit"] - -# Outer --no-cache with nested --cache: inner overrides outer -[[plan]] -name = "outer___no_cache_does_not_propagate_into_nested___cache" -args = ["run", "--no-cache", "outer-cache"] - -# Outer --no-cache with nested run (no flags): inner inherits outer -[[plan]] -name = "outer___no_cache_propagates_to_nested_run_without_flags" -args = ["run", "--no-cache", "outer-inherit"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested___cache_enables_inner_task_caching.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested___cache_enables_inner_task_caching.jsonc deleted file mode 100644 index 3f46916b4..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested___cache_enables_inner_task_caching.jsonc +++ /dev/null @@ -1,124 +0,0 @@ -// run outer-cache -{ - "graph": [ - { - "key": [ - "/", - "outer-cache" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-cache", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-cache", - "package_path": "/" - }, - "command": "vt run --cache inner", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "inner" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "inner", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested___no_cache_disables_inner_task_caching.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested___no_cache_disables_inner_task_caching.jsonc deleted file mode 100644 index 3139a3dd7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested___no_cache_disables_inner_task_caching.jsonc +++ /dev/null @@ -1,86 +0,0 @@ -// run outer-no-cache -{ - "graph": [ - { - "key": [ - "/", - "outer-no-cache" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-no-cache", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-no-cache", - "package_path": "/" - }, - "command": "vt run --no-cache inner", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "inner" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested_run_without_flags_inherits_parent_cache.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested_run_without_flags_inherits_parent_cache.jsonc deleted file mode 100644 index 7cc30f7e5..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_nested_run_without_flags_inherits_parent_cache.jsonc +++ /dev/null @@ -1,86 +0,0 @@ -// run outer-inherit -{ - "graph": [ - { - "key": [ - "/", - "outer-inherit" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-inherit", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-inherit", - "package_path": "/" - }, - "command": "vt run inner", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "inner" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___cache_propagates_to_nested_run_without_flags.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___cache_propagates_to_nested_run_without_flags.jsonc deleted file mode 100644 index b6f8507ac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___cache_propagates_to_nested_run_without_flags.jsonc +++ /dev/null @@ -1,124 +0,0 @@ -// run --cache outer-inherit -{ - "graph": [ - { - "key": [ - "/", - "outer-inherit" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-inherit", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-inherit", - "package_path": "/" - }, - "command": "vt run inner", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "inner" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "inner", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___no_cache_does_not_propagate_into_nested___cache.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___no_cache_does_not_propagate_into_nested___cache.jsonc deleted file mode 100644 index a88a1937c..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___no_cache_does_not_propagate_into_nested___cache.jsonc +++ /dev/null @@ -1,124 +0,0 @@ -// run --no-cache outer-cache -{ - "graph": [ - { - "key": [ - "/", - "outer-cache" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-cache", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-cache", - "package_path": "/" - }, - "command": "vt run --cache inner", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "inner" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "inner", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___no_cache_propagates_to_nested_run_without_flags.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___no_cache_propagates_to_nested_run_without_flags.jsonc deleted file mode 100644 index aad2c92be..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/query_outer___no_cache_propagates_to_nested_run_without_flags.jsonc +++ /dev/null @@ -1,86 +0,0 @@ -// run --no-cache outer-inherit -{ - "graph": [ - { - "key": [ - "/", - "outer-inherit" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-inherit", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-inherit", - "package_path": "/" - }, - "command": "vt run inner", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "inner" - ], - "node": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/task_graph.md deleted file mode 100644 index ff8dd6598..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/snapshots/task_graph.md +++ /dev/null @@ -1,166 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#inner"] - task_1["/#outer-cache"] - task_2["/#outer-inherit"] - task_3["/#outer-no-cache"] -``` - -## `/#inner` - -```json -{ - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "inner", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#outer-cache` - -```json -{ - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-cache", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run --cache inner" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#outer-inherit` - -```json -{ - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-inherit", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run inner" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#outer-no-cache` - -```json -{ - "task_display": { - "package_name": "@test/nested-cache-override", - "task_name": "outer-no-cache", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run --no-cache inner" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/vite-task.json deleted file mode 100644 index 0967ef424..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_cache_override/vite-task.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/package.json deleted file mode 100644 index bd2f45e12..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "scripts": { - "script1": "echo hello", - "script2": "vt run script1" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots.toml deleted file mode 100644 index 232a3a6ce..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Tests nested vt run resolution - -[[plan]] -name = "nested_vt_run" -args = ["run", "script2"] -compact = true diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots/query_nested_vt_run.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots/query_nested_vt_run.jsonc deleted file mode 100644 index 0bf2dfabc..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots/query_nested_vt_run.jsonc +++ /dev/null @@ -1,11 +0,0 @@ -// run script2 -{ - "#script2": { - "items": [ - { - "#script1": [] - } - ], - "neighbors": [] - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots/task_graph.md deleted file mode 100644 index 399b9d95e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#script1"] - task_1["/#script2"] -``` - -## `/#script1` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "script1", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo hello" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#script2` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "script2", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run script1" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/nested_tasks/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/package.json deleted file mode 100644 index 0c3aa8c37..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/object-depends-on-duplicate-fields", - "version": "1.0.0", - "workspaces": [ - "packages/*" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/packages/a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/packages/a/package.json deleted file mode 100644 index d0e3e47e9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/packages/a/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@test/a", - "version": "1.0.0", - "dependencies": { - "@test/b": "workspace:*" - }, - "devDependencies": { - "@test/b": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/packages/a/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/packages/a/vite-task.json deleted file mode 100644 index 041f208c9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/packages/a/vite-task.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "tasks": { - "from_dependencies": { - "command": "vtt from dependencies", - "dependsOn": [{ "task": "build", "from": "dependencies" }] - }, - "from_dev_dependencies": { - "command": "vtt from devDependencies", - "dependsOn": [{ "task": "build", "from": "devDependencies" }] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/packages/b/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/packages/b/package.json deleted file mode 100644 index 077fc3916..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/packages/b/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/b", - "version": "1.0.0", - "scripts": { - "build": "vtt build b" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/snapshots/task_graph.md deleted file mode 100644 index c8645f770..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/snapshots/task_graph.md +++ /dev/null @@ -1,128 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/a#from_dependencies"] - task_0 --> task_2 - task_1["/packages/a#from_dev_dependencies"] - task_1 --> task_2 - task_2["/packages/b#build"] -``` - -## `/packages/a#from_dependencies` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "from_dependencies", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "vtt from dependencies" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/a#from_dev_dependencies` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "from_dev_dependencies", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "vtt from devDependencies" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/b#build` - -```json -{ - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "resolved_config": { - "commands": [ - "vtt build b" - ], - "resolved_options": { - "cwd": "/packages/b", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_duplicate_fields/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/package.json deleted file mode 100644 index 73b7c3ebe..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/object-depends-on-global-graph", - "version": "1.0.0", - "workspaces": [ - "packages/*" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/package.json deleted file mode 100644 index 04137006d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@test/app", - "version": "1.0.0", - "dependencies": { - "@test/prod-a": "workspace:*", - "@test/prod-missing-task": "workspace:*" - }, - "devDependencies": { - "@test/dev-a": "workspace:*" - }, - "peerDependencies": { - "@test/peer-a": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/vite-task.json deleted file mode 100644 index 05008ba16..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/app/vite-task.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tasks": { - "test_dependencies": { - "command": "vtt test dependencies", - "dependsOn": [{ "task": "build", "from": "dependencies" }] - }, - "test_dev": { - "command": "vtt test dev", - "dependsOn": [{ "task": "build", "from": "devDependencies" }] - }, - "test_union": { - "command": "vtt test union", - "dependsOn": [{ "task": "build", "from": ["dependencies", "devDependencies"] }] - }, - "test_recursive": { - "command": "vtt test recursive", - "dependsOn": [{ "task": "build_recursive", "from": "devDependencies" }] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/package.json deleted file mode 100644 index 0d34156a7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@test/dev-a", - "version": "1.0.0", - "scripts": { - "build": "vtt build dev-a" - }, - "devDependencies": { - "@test/shared": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/vite-task.json deleted file mode 100644 index 2410dd913..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/dev-a/vite-task.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tasks": { - "build_recursive": { - "command": "vtt build recursive dev-a", - "dependsOn": [{ "task": "build_recursive", "from": "devDependencies" }] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/peer-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/peer-a/package.json deleted file mode 100644 index 6cd065a8c..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/peer-a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/peer-a", - "version": "1.0.0", - "scripts": { - "build": "vtt build peer-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-a/package.json deleted file mode 100644 index 40f1f23e8..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/prod-a", - "version": "1.0.0", - "scripts": { - "build": "vtt build prod-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-missing-task/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-missing-task/package.json deleted file mode 100644 index 9659e3961..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/prod-missing-task/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/prod-missing-task", - "version": "1.0.0", - "scripts": { - "lint": "vtt lint prod-missing-task" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/shared/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/shared/package.json deleted file mode 100644 index 37434e0bc..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/packages/shared/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/shared", - "version": "1.0.0", - "scripts": { - "build_recursive": "vtt build recursive shared" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots.toml deleted file mode 100644 index 9c6a0b6b1..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Object-form `dependsOn` selections are materialized as global task graph -# edges, so the edge structure for every `from` variant (dependencies, -# devDependencies, unions, recursive chains, and excluded peer/missing-task -# deps) is asserted by `snapshots/task_graph.md`. The only behavior the static -# graph cannot express is query-time `--ignore-depends-on` dropping those -# materialized edges, so that is the one plan case kept here. -[[plan]] -compact = true -name = "ignore_depends_on_omits_object_edges" -args = ["run", "--ignore-depends-on", "test_union"] -cwd = "packages/app" diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/query_ignore_depends_on_omits_object_edges.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/query_ignore_depends_on_omits_object_edges.jsonc deleted file mode 100644 index 001458484..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/query_ignore_depends_on_omits_object_edges.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run --ignore-depends-on test_union -{ - "packages/app#test_union": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/task_graph.md deleted file mode 100644 index a889d36c3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/snapshots/task_graph.md +++ /dev/null @@ -1,412 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/app#test_dependencies"] - task_0 --> task_7 - task_1["/packages/app#test_dev"] - task_1 --> task_4 - task_2["/packages/app#test_recursive"] - task_2 --> task_5 - task_3["/packages/app#test_union"] - task_3 --> task_4 - task_3 --> task_7 - task_4["/packages/dev-a#build"] - task_5["/packages/dev-a#build_recursive"] - task_5 --> task_9 - task_6["/packages/peer-a#build"] - task_7["/packages/prod-a#build"] - task_8["/packages/prod-missing-task#lint"] - task_9["/packages/shared#build_recursive"] -``` - -## `/packages/app#test_dependencies` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "test_dependencies", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "vtt test dependencies" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/app#test_dev` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "test_dev", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "vtt test dev" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/app#test_recursive` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "test_recursive", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "vtt test recursive" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/app#test_union` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "test_union", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "vtt test union" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/dev-a#build` - -```json -{ - "task_display": { - "package_name": "@test/dev-a", - "task_name": "build", - "package_path": "/packages/dev-a" - }, - "resolved_config": { - "commands": [ - "vtt build dev-a" - ], - "resolved_options": { - "cwd": "/packages/dev-a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/dev-a#build_recursive` - -```json -{ - "task_display": { - "package_name": "@test/dev-a", - "task_name": "build_recursive", - "package_path": "/packages/dev-a" - }, - "resolved_config": { - "commands": [ - "vtt build recursive dev-a" - ], - "resolved_options": { - "cwd": "/packages/dev-a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/peer-a#build` - -```json -{ - "task_display": { - "package_name": "@test/peer-a", - "task_name": "build", - "package_path": "/packages/peer-a" - }, - "resolved_config": { - "commands": [ - "vtt build peer-a" - ], - "resolved_options": { - "cwd": "/packages/peer-a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/prod-a#build` - -```json -{ - "task_display": { - "package_name": "@test/prod-a", - "task_name": "build", - "package_path": "/packages/prod-a" - }, - "resolved_config": { - "commands": [ - "vtt build prod-a" - ], - "resolved_options": { - "cwd": "/packages/prod-a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/prod-missing-task#lint` - -```json -{ - "task_display": { - "package_name": "@test/prod-missing-task", - "task_name": "lint", - "package_path": "/packages/prod-missing-task" - }, - "resolved_config": { - "commands": [ - "vtt lint prod-missing-task" - ], - "resolved_options": { - "cwd": "/packages/prod-missing-task", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/shared#build_recursive` - -```json -{ - "task_display": { - "package_name": "@test/shared", - "task_name": "build_recursive", - "package_path": "/packages/shared" - }, - "resolved_config": { - "commands": [ - "vtt build recursive shared" - ], - "resolved_options": { - "cwd": "/packages/shared", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/object_depends_on_global_graph/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/package.json deleted file mode 100644 index b65cafad5..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "test-workspace", - "private": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/packages/self-dep/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/packages/self-dep/package.json deleted file mode 100644 index 42f65dcfc..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/packages/self-dep/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "self-dep", - "version": "1.0.0", - "scripts": { - "build": "echo building" - }, - "dependencies": { - "self-dep": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots.toml deleted file mode 100644 index b57048f27..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Tests that a package with a self-referential workspace dependency (e.g. for -# testing purposes) does not produce a false cycle-dependency error. - -[[plan]] -name = "build_in_self_referential_package" -args = ["run", "build"] -cwd = "packages/self-dep" -compact = true - -[[plan]] -name = "recursive_build_across_workspace" -args = ["run", "-r", "build"] -compact = true diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/query_build_in_self_referential_package.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/query_build_in_self_referential_package.jsonc deleted file mode 100644 index 546ba723a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/query_build_in_self_referential_package.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run build -{ - "packages/self-dep#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/query_recursive_build_across_workspace.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/query_recursive_build_across_workspace.jsonc deleted file mode 100644 index 5f46d07de..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/query_recursive_build_across_workspace.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run -r build -{ - "packages/self-dep#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/task_graph.md deleted file mode 100644 index a2975e663..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/package_self_dependency/snapshots/task_graph.md +++ /dev/null @@ -1,46 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/self-dep#build"] -``` - -## `/packages/self-dep#build` - -```json -{ - "task_display": { - "package_name": "self-dep", - "task_name": "build", - "package_path": "/packages/self-dep" - }, - "resolved_config": { - "commands": [ - "echo building" - ], - "resolved_options": { - "cwd": "/packages/self-dep", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/package.json deleted file mode 100644 index c2e0d4711..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "test-workspace", - "private": true, - "scripts": { - "build": "vt run -r build", - "build-with-concurrency": "vt run -r --concurrency-limit 5 build" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/packages/a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/packages/a/package.json deleted file mode 100644 index e429c6e02..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/packages/a/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@test/a", - "version": "1.0.0", - "scripts": { - "build": "echo building a" - }, - "dependencies": { - "@test/b": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/packages/b/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/packages/b/package.json deleted file mode 100644 index 47183f5a3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/packages/b/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@test/b", - "version": "1.0.0", - "scripts": { - "build": "echo building b" - }, - "dependencies": { - "@test/c": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/packages/c/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/packages/c/package.json deleted file mode 100644 index fb2d4b42a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/packages/c/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/c", - "version": "1.0.0", - "scripts": { - "build": "echo building c" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots.toml deleted file mode 100644 index b8059c515..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots.toml +++ /dev/null @@ -1,49 +0,0 @@ -# Baseline: normal recursive build preserves topo edges -[[plan]] -name = "baseline_recursive_build" -args = ["run", "-r", "build"] -compact = true - -# --parallel discards all edges at this level -[[plan]] -name = "parallel_discards_edges" -args = ["run", "-r", "--parallel", "build"] -compact = true - -# --parallel only affects the current level; nested Expanded graph preserves edges -[[plan]] -name = "parallel_only_affects_current_level" -args = ["run", "--parallel", "build"] -compact = true - -# --concurrency-limit with explicit value -[[plan]] -name = "concurrency_limit_with_explicit_value" -args = ["run", "-r", "--concurrency-limit", "5", "build"] - -# --parallel without --concurrency-limit sets concurrency to max -[[plan]] -name = "parallel_without_concurrency_limit" -args = ["run", "-r", "--parallel", "build"] - -# --parallel with --concurrency-limit uses explicit concurrency, not max -[[plan]] -name = "parallel_with_concurrency_limit" -args = ["run", "-r", "--parallel", "--concurrency-limit", "3", "build"] - -# Nested vt run with explicit --concurrency-limit -[[plan]] -name = "nested_with_explicit_concurrency_limit" -args = ["run", "build-with-concurrency"] - -# VP_RUN_CONCURRENCY_LIMIT env var sets concurrency -[[plan]] -name = "env_var_sets_concurrency" -args = ["run", "-r", "build"] -env = { VP_RUN_CONCURRENCY_LIMIT = "7" } - -# --concurrency-limit flag takes priority over VP_RUN_CONCURRENCY_LIMIT env -[[plan]] -name = "cli_flag_overrides_env_var" -args = ["run", "-r", "--concurrency-limit", "3", "build"] -env = { VP_RUN_CONCURRENCY_LIMIT = "7" } diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_baseline_recursive_build.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_baseline_recursive_build.jsonc deleted file mode 100644 index 1a874cc40..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_baseline_recursive_build.jsonc +++ /dev/null @@ -1,11 +0,0 @@ -// run -r build -{ - "#build": [], - "packages/a#build": [ - "packages/b#build" - ], - "packages/b#build": [ - "packages/c#build" - ], - "packages/c#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_cli_flag_overrides_env_var.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_cli_flag_overrides_env_var.jsonc deleted file mode 100644 index f28f29041..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_cli_flag_overrides_env_var.jsonc +++ /dev/null @@ -1,157 +0,0 @@ -// run -r --concurrency-limit 3 build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "items": [] - }, - "neighbors": [] - }, - { - "key": [ - "/packages/a", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "command": "echo building a", - "cwd": "/packages/a" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "a" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [ - [ - "/packages/b", - "build" - ] - ] - }, - { - "key": [ - "/packages/b", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "command": "echo building b", - "cwd": "/packages/b" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "b" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [ - [ - "/packages/c", - "build" - ] - ] - }, - { - "key": [ - "/packages/c", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "command": "echo building c", - "cwd": "/packages/c" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "c" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 3 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_concurrency_limit_with_explicit_value.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_concurrency_limit_with_explicit_value.jsonc deleted file mode 100644 index 55ab25a34..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_concurrency_limit_with_explicit_value.jsonc +++ /dev/null @@ -1,157 +0,0 @@ -// run -r --concurrency-limit 5 build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "items": [] - }, - "neighbors": [] - }, - { - "key": [ - "/packages/a", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "command": "echo building a", - "cwd": "/packages/a" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "a" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [ - [ - "/packages/b", - "build" - ] - ] - }, - { - "key": [ - "/packages/b", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "command": "echo building b", - "cwd": "/packages/b" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "b" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [ - [ - "/packages/c", - "build" - ] - ] - }, - { - "key": [ - "/packages/c", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "command": "echo building c", - "cwd": "/packages/c" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "c" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 5 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_env_var_sets_concurrency.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_env_var_sets_concurrency.jsonc deleted file mode 100644 index 6811d5025..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_env_var_sets_concurrency.jsonc +++ /dev/null @@ -1,157 +0,0 @@ -// run -r build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "items": [] - }, - "neighbors": [] - }, - { - "key": [ - "/packages/a", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "command": "echo building a", - "cwd": "/packages/a" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "a" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [ - [ - "/packages/b", - "build" - ] - ] - }, - { - "key": [ - "/packages/b", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "command": "echo building b", - "cwd": "/packages/b" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "b" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [ - [ - "/packages/c", - "build" - ] - ] - }, - { - "key": [ - "/packages/c", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "command": "echo building c", - "cwd": "/packages/c" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "c" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 7 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_nested_with_explicit_concurrency_limit.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_nested_with_explicit_concurrency_limit.jsonc deleted file mode 100644 index 485cea7e9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_nested_with_explicit_concurrency_limit.jsonc +++ /dev/null @@ -1,191 +0,0 @@ -// run build-with-concurrency -{ - "graph": [ - { - "key": [ - "/", - "build-with-concurrency" - ], - "node": { - "task_display": { - "package_name": "test-workspace", - "task_name": "build-with-concurrency", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "test-workspace", - "task_name": "build-with-concurrency", - "package_path": "/" - }, - "command": "vt run -r --concurrency-limit 5 build", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "items": [] - }, - "neighbors": [] - }, - { - "key": [ - "/packages/a", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "command": "echo building a", - "cwd": "/packages/a" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "a" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [ - [ - "/packages/b", - "build" - ] - ] - }, - { - "key": [ - "/packages/b", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "command": "echo building b", - "cwd": "/packages/b" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "b" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [ - [ - "/packages/c", - "build" - ] - ] - }, - { - "key": [ - "/packages/c", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "command": "echo building c", - "cwd": "/packages/c" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "c" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 5 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_discards_edges.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_discards_edges.jsonc deleted file mode 100644 index f0a3f1b16..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_discards_edges.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -// run -r --parallel build -{ - "#build": [], - "packages/a#build": [], - "packages/b#build": [], - "packages/c#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_only_affects_current_level.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_only_affects_current_level.jsonc deleted file mode 100644 index 6a348e03a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_only_affects_current_level.jsonc +++ /dev/null @@ -1,17 +0,0 @@ -// run --parallel build -{ - "#build": { - "items": [ - { - "packages/a#build": [ - "packages/b#build" - ], - "packages/b#build": [ - "packages/c#build" - ], - "packages/c#build": [] - } - ], - "neighbors": [] - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_with_concurrency_limit.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_with_concurrency_limit.jsonc deleted file mode 100644 index db17c91a2..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_with_concurrency_limit.jsonc +++ /dev/null @@ -1,147 +0,0 @@ -// run -r --parallel --concurrency-limit 3 build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "items": [] - }, - "neighbors": [] - }, - { - "key": [ - "/packages/a", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "command": "echo building a", - "cwd": "/packages/a" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "a" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - }, - { - "key": [ - "/packages/b", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "command": "echo building b", - "cwd": "/packages/b" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "b" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - }, - { - "key": [ - "/packages/c", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "command": "echo building c", - "cwd": "/packages/c" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "c" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 3 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_without_concurrency_limit.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_without_concurrency_limit.jsonc deleted file mode 100644 index 7835755a9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/query_parallel_without_concurrency_limit.jsonc +++ /dev/null @@ -1,147 +0,0 @@ -// run -r --parallel build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "items": [] - }, - "neighbors": [] - }, - { - "key": [ - "/packages/a", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "command": "echo building a", - "cwd": "/packages/a" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "a" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - }, - { - "key": [ - "/packages/b", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "command": "echo building b", - "cwd": "/packages/b" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "b" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - }, - { - "key": [ - "/packages/c", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "command": "echo building c", - "cwd": "/packages/c" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "building", - "c" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 18446744073709551615 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/task_graph.md deleted file mode 100644 index 2eaae24fb..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/snapshots/task_graph.md +++ /dev/null @@ -1,206 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#build-with-concurrency"] - task_2["/packages/a#build"] - task_3["/packages/b#build"] - task_4["/packages/c#build"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run -r build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#build-with-concurrency` - -```json -{ - "task_display": { - "package_name": "test-workspace", - "task_name": "build-with-concurrency", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run -r --concurrency-limit 5 build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/a#build` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "echo building a" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/b#build` - -```json -{ - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "resolved_config": { - "commands": [ - "echo building b" - ], - "resolved_options": { - "cwd": "/packages/b", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/c#build` - -```json -{ - "task_display": { - "package_name": "@test/c", - "task_name": "build", - "package_path": "/packages/c" - }, - "resolved_config": { - "commands": [ - "echo building c" - ], - "resolved_options": { - "cwd": "/packages/c", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/parallel_and_concurrency/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/package.json deleted file mode 100644 index a3f69d927..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "additional-envs", - "private": true, - "scripts": { - "hello": "echo hello" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/pnpm-workspace.yaml deleted file mode 100644 index a62fa0dce..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/pnpm-workspace.yaml +++ /dev/null @@ -1 +0,0 @@ -# no `packages` field diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots.toml deleted file mode 100644 index facdd5397..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots.toml +++ /dev/null @@ -1,4 +0,0 @@ -[[plan]] -name = "allow__packages__in_pnpm_workspace_yaml_to_be_optional" -args = ["run", "hello"] -compact = true diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots/query_allow__packages__in_pnpm_workspace_yaml_to_be_optional.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots/query_allow__packages__in_pnpm_workspace_yaml_to_be_optional.jsonc deleted file mode 100644 index 9b4718302..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots/query_allow__packages__in_pnpm_workspace_yaml_to_be_optional.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run hello -{ - "#hello": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots/task_graph.md deleted file mode 100644 index 7b4ff3be5..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/snapshots/task_graph.md +++ /dev/null @@ -1,46 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#hello"] -``` - -## `/#hello` - -```json -{ - "task_display": { - "package_name": "additional-envs", - "task_name": "hello", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo hello" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/pnpm_workspace_packages_optional/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/package.json deleted file mode 100644 index 372537b0e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "check": "PATH= vtt" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/snapshots.toml deleted file mode 100644 index 2ed5ce8f0..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/snapshots.toml +++ /dev/null @@ -1,3 +0,0 @@ -[[plan]] -name = "prefix_path_hides_tool_binary" -args = ["run", "check"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/snapshots/query_prefix_path_hides_tool_binary.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/snapshots/query_prefix_path_hides_tool_binary.snap deleted file mode 100644 index ee075ad18..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/snapshots/query_prefix_path_hides_tool_binary.snap +++ /dev/null @@ -1 +0,0 @@ -Failed to find executable vtt under cwd / with PATH: : cannot find binary path \ No newline at end of file diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/snapshots/task_graph.md deleted file mode 100644 index d9891a1dd..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/snapshots/task_graph.md +++ /dev/null @@ -1,46 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#check"] -``` - -## `/#check` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "check", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "PATH= vtt" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/prefix_env_path_lookup/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/apps/web/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/apps/web/package.json deleted file mode 100644 index 10709ae55..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/apps/web/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "@test/web", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/web'", - "dev": "echo 'Running @test/web in dev mode'" - }, - "dependencies": { - "@test/app": "workspace:*", - "@test/core": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/package.json deleted file mode 100644 index 1e8301b96..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "test-workspace", - "private": true, - "workspaces": [ - "packages/*", - "apps/*" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/packages/app/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/packages/app/package.json deleted file mode 100644 index 134362e12..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/packages/app/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@test/app", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/app'", - "test": "echo 'Testing @test/app'" - }, - "dependencies": { - "@test/utils": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/packages/core/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/packages/core/package.json deleted file mode 100644 index c2864056e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/packages/core/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/core", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/core'", - "test": "echo 'Testing @test/core'" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/packages/utils/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/packages/utils/package.json deleted file mode 100644 index f358bbeea..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/packages/utils/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@test/utils", - "version": "1.0.0", - "scripts": { - "build": "echo 'Preparing @test/utils' && echo 'Building @test/utils' && echo 'Done @test/utils'", - "test": "echo 'Testing @test/utils'" - }, - "dependencies": { - "@test/core": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/pnpm-workspace.yaml deleted file mode 100644 index 4e708bd3c..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -packages: - - 'packages/*' - - 'apps/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/snapshots/task_graph.md deleted file mode 100644 index 0a7d7c918..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/snapshots/task_graph.md +++ /dev/null @@ -1,326 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/apps/web#build"] - task_1["/apps/web#dev"] - task_2["/packages/app#build"] - task_3["/packages/app#test"] - task_4["/packages/core#build"] - task_5["/packages/core#test"] - task_6["/packages/utils#build"] - task_7["/packages/utils#test"] -``` - -## `/apps/web#build` - -```json -{ - "task_display": { - "package_name": "@test/web", - "task_name": "build", - "package_path": "/apps/web" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/web'" - ], - "resolved_options": { - "cwd": "/apps/web", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/apps/web#dev` - -```json -{ - "task_display": { - "package_name": "@test/web", - "task_name": "dev", - "package_path": "/apps/web" - }, - "resolved_config": { - "commands": [ - "echo 'Running @test/web in dev mode'" - ], - "resolved_options": { - "cwd": "/apps/web", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#build` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "build", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/app'" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/app#test` - -```json -{ - "task_display": { - "package_name": "@test/app", - "task_name": "test", - "package_path": "/packages/app" - }, - "resolved_config": { - "commands": [ - "echo 'Testing @test/app'" - ], - "resolved_options": { - "cwd": "/packages/app", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/core#build` - -```json -{ - "task_display": { - "package_name": "@test/core", - "task_name": "build", - "package_path": "/packages/core" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/core'" - ], - "resolved_options": { - "cwd": "/packages/core", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/core#test` - -```json -{ - "task_display": { - "package_name": "@test/core", - "task_name": "test", - "package_path": "/packages/core" - }, - "resolved_config": { - "commands": [ - "echo 'Testing @test/core'" - ], - "resolved_options": { - "cwd": "/packages/core", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/utils#build` - -```json -{ - "task_display": { - "package_name": "@test/utils", - "task_name": "build", - "package_path": "/packages/utils" - }, - "resolved_config": { - "commands": [ - "echo 'Preparing @test/utils' && echo 'Building @test/utils' && echo 'Done @test/utils'" - ], - "resolved_options": { - "cwd": "/packages/utils", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/utils#test` - -```json -{ - "task_display": { - "package_name": "@test/utils", - "task_name": "test", - "package_path": "/packages/utils" - }, - "resolved_config": { - "commands": [ - "echo 'Testing @test/utils'" - ], - "resolved_options": { - "cwd": "/packages/utils", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/recursive_topological_workspace/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_conflict/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_conflict/package.json deleted file mode 100644 index fec247d21..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_conflict/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/script-conflict", - "scripts": { - "build": "echo from script" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_conflict/snapshots/task_graph_load_error.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_conflict/snapshots/task_graph_load_error.snap deleted file mode 100644 index e756b946e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_conflict/snapshots/task_graph_load_error.snap +++ /dev/null @@ -1 +0,0 @@ -Task @test/script-conflict#build conflicts with a package.json script of the same name. Remove the script from package.json or rename the task \ No newline at end of file diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_conflict/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_conflict/vite-task.json deleted file mode 100644 index 9fa380a8d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_conflict/vite-task.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tasks": { - "build": { - "command": "echo from task" - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/package.json deleted file mode 100644 index 11b251a05..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@test/script-hooks", - "scripts": { - "prepretest": "echo prepretest", - "pretest": "echo pretest", - "test": "echo test", - "posttest": "echo posttest", - "build": "echo build", - "prebuild": "echo prebuild" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots.toml deleted file mode 100644 index 74758f842..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots.toml +++ /dev/null @@ -1,17 +0,0 @@ -# Tests pre/post lifecycle hooks for package.json scripts. - -[[plan]] -name = "test_runs_with_pre_and_post_hooks" -args = ["run", "test"] - -[[plan]] -name = "build_runs_with_pre_hook_only" -args = ["run", "build"] - -[[plan]] -name = "pretest_directly_expands_prepretest_but_not_when_called_as_hook" -args = ["run", "pretest"] - -[[plan]] -name = "extra_args_not_passed_to_hooks" -args = ["run", "test", "--coverage"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_build_runs_with_pre_hook_only.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_build_runs_with_pre_hook_only.jsonc deleted file mode 100644 index be152e8c3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_build_runs_with_pre_hook_only.jsonc +++ /dev/null @@ -1,72 +0,0 @@ -// run build -{ - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "prebuild", - "package_path": "/" - }, - "command": "echo prebuild", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "prebuild" - ], - "trailing_newline": true - } - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "build", - "package_path": "/" - }, - "command": "echo build", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "build" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_extra_args_not_passed_to_hooks.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_extra_args_not_passed_to_hooks.jsonc deleted file mode 100644 index f086d0ee1..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_extra_args_not_passed_to_hooks.jsonc +++ /dev/null @@ -1,98 +0,0 @@ -// run test --coverage -{ - "graph": [ - { - "key": [ - "/", - "test" - ], - "node": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "pretest", - "package_path": "/" - }, - "command": "echo pretest", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "pretest" - ], - "trailing_newline": true - } - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "test", - "package_path": "/" - }, - "command": "echo test --coverage", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "test", - "--coverage" - ], - "trailing_newline": true - } - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "posttest", - "package_path": "/" - }, - "command": "echo posttest", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "posttest" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_pretest_directly_expands_prepretest_but_not_when_called_as_hook.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_pretest_directly_expands_prepretest_but_not_when_called_as_hook.jsonc deleted file mode 100644 index ee1c3279b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_pretest_directly_expands_prepretest_but_not_when_called_as_hook.jsonc +++ /dev/null @@ -1,72 +0,0 @@ -// run pretest -{ - "graph": [ - { - "key": [ - "/", - "pretest" - ], - "node": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "pretest", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "prepretest", - "package_path": "/" - }, - "command": "echo prepretest", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "prepretest" - ], - "trailing_newline": true - } - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "pretest", - "package_path": "/" - }, - "command": "echo pretest", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "pretest" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_test_runs_with_pre_and_post_hooks.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_test_runs_with_pre_and_post_hooks.jsonc deleted file mode 100644 index c41cf8842..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/query_test_runs_with_pre_and_post_hooks.jsonc +++ /dev/null @@ -1,97 +0,0 @@ -// run test -{ - "graph": [ - { - "key": [ - "/", - "test" - ], - "node": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "pretest", - "package_path": "/" - }, - "command": "echo pretest", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "pretest" - ], - "trailing_newline": true - } - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "test", - "package_path": "/" - }, - "command": "echo test", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "test" - ], - "trailing_newline": true - } - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "posttest", - "package_path": "/" - }, - "command": "echo posttest", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "posttest" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/task_graph.md deleted file mode 100644 index 36459e47e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks/snapshots/task_graph.md +++ /dev/null @@ -1,246 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#posttest"] - task_2["/#prebuild"] - task_3["/#prepretest"] - task_4["/#pretest"] - task_5["/#test"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#posttest` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "posttest", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo posttest" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#prebuild` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "prebuild", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo prebuild" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#prepretest` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "prepretest", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo prepretest" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#pretest` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "pretest", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo pretest" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo test" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/package.json deleted file mode 100644 index 45f66a497..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/script-hooks-disabled", - "scripts": { - "pretest": "echo pretest", - "test": "echo test", - "posttest": "echo posttest" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots.toml deleted file mode 100644 index b7e8c3df3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Tests that pre/post hooks are NOT expanded when enablePrePostScripts is false. - -[[plan]] -name = "test_runs_without_hooks_when_disabled" -args = ["run", "test"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots/query_test_runs_without_hooks_when_disabled.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots/query_test_runs_without_hooks_when_disabled.jsonc deleted file mode 100644 index f5d883081..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots/query_test_runs_without_hooks_when_disabled.jsonc +++ /dev/null @@ -1,47 +0,0 @@ -// run test -{ - "graph": [ - { - "key": [ - "/", - "test" - ], - "node": { - "task_display": { - "package_name": "@test/script-hooks-disabled", - "task_name": "test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks-disabled", - "task_name": "test", - "package_path": "/" - }, - "command": "echo test", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "test" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots/task_graph.md deleted file mode 100644 index 2a9daca4e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/snapshots/task_graph.md +++ /dev/null @@ -1,126 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#posttest"] - task_1["/#pretest"] - task_2["/#test"] -``` - -## `/#posttest` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks-disabled", - "task_name": "posttest", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo posttest" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#pretest` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks-disabled", - "task_name": "pretest", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo pretest" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks-disabled", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo test" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/vite-task.json deleted file mode 100644 index f96c98c4c..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_disabled/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "enablePrePostScripts": false -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/package.json deleted file mode 100644 index ef69df12b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@test/script-hooks-nested-run", - "scripts": { - "prescriptInHook": "echo prescriptInHook", - "scriptInHook": "echo scriptInHook", - "pretest": "vt run scriptInHook", - "test": "echo test" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots.toml deleted file mode 100644 index 44f3f6e42..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots.toml +++ /dev/null @@ -1,7 +0,0 @@ -# Tests that hooks of scripts called via `vt run` within a hook are properly expanded. -# When `pretest` (a hook of `test`) runs `vt run scriptInHook`, the `prescriptInHook` -# hook of `scriptInHook` should still be found and executed. - -[[plan]] -name = "prescriptInHook_runs_when_scriptInHook_is_called_from_a_hook" -args = ["run", "test"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots/query_prescriptInHook_runs_when_scriptInHook_is_called_from_a_hook.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots/query_prescriptInHook_runs_when_scriptInHook_is_called_from_a_hook.jsonc deleted file mode 100644 index a1355b785..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots/query_prescriptInHook_runs_when_scriptInHook_is_called_from_a_hook.jsonc +++ /dev/null @@ -1,131 +0,0 @@ -// run test -{ - "graph": [ - { - "key": [ - "/", - "test" - ], - "node": { - "task_display": { - "package_name": "@test/script-hooks-nested-run", - "task_name": "test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks-nested-run", - "task_name": "pretest", - "package_path": "/" - }, - "command": "vt run scriptInHook", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "scriptInHook" - ], - "node": { - "task_display": { - "package_name": "@test/script-hooks-nested-run", - "task_name": "scriptInHook", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks-nested-run", - "task_name": "prescriptInHook", - "package_path": "/" - }, - "command": "echo prescriptInHook", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "prescriptInHook" - ], - "trailing_newline": true - } - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks-nested-run", - "task_name": "scriptInHook", - "package_path": "/" - }, - "command": "echo scriptInHook", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "scriptInHook" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks-nested-run", - "task_name": "test", - "package_path": "/" - }, - "command": "echo test", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "test" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots/task_graph.md deleted file mode 100644 index c5b8ba1ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_nested_run/snapshots/task_graph.md +++ /dev/null @@ -1,166 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#prescriptInHook"] - task_1["/#pretest"] - task_2["/#scriptInHook"] - task_3["/#test"] -``` - -## `/#prescriptInHook` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks-nested-run", - "task_name": "prescriptInHook", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo prescriptInHook" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#pretest` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks-nested-run", - "task_name": "pretest", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run scriptInHook" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#scriptInHook` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks-nested-run", - "task_name": "scriptInHook", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo scriptInHook" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks-nested-run", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo test" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/package.json deleted file mode 100644 index f729664b9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/script-hooks-task-no-hook", - "scripts": { - "pretest": "echo pretest-script" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots.toml deleted file mode 100644 index bc15f9d4d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots.toml +++ /dev/null @@ -1,8 +0,0 @@ -# Tests that TaskConfig tasks do NOT get pre/post hooks expanded, -# even if matching pre/post package.json scripts exist. -# `test` is a TaskConfig; `pretest` is a PackageJsonScript. -# Running `test` must NOT expand `pretest` as a hook. - -[[plan]] -name = "task_config_test_does_not_expand_pretest_hook" -args = ["run", "test"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots/query_task_config_test_does_not_expand_pretest_hook.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots/query_task_config_test_does_not_expand_pretest_hook.jsonc deleted file mode 100644 index e10b65ba7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots/query_task_config_test_does_not_expand_pretest_hook.jsonc +++ /dev/null @@ -1,47 +0,0 @@ -// run test -{ - "graph": [ - { - "key": [ - "/", - "test" - ], - "node": { - "task_display": { - "package_name": "@test/script-hooks-task-no-hook", - "task_name": "test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/script-hooks-task-no-hook", - "task_name": "test", - "package_path": "/" - }, - "command": "echo test-task", - "cwd": "/" - }, - "kind": { - "Leaf": { - "InProcess": { - "kind": { - "Echo": { - "strings": [ - "test-task" - ], - "trailing_newline": true - } - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots/task_graph.md deleted file mode 100644 index 19b6bedff..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#pretest"] - task_1["/#test"] -``` - -## `/#pretest` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks-task-no-hook", - "task_name": "pretest", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo pretest-script" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "@test/script-hooks-task-no-hook", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo test-task" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/vite-task.json deleted file mode 100644 index 0ea13d52c..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/script_hooks_task_no_hook/vite-task.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "tasks": { - // `test` is a TaskConfig — pre/post hooks must NOT apply to it. - // `pretest` is a package.json script, but since `test` is a TaskConfig, - // `pretest` must not be expanded as a hook when running `test`. - "test": { - "command": "echo test-task" - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/package.json deleted file mode 100644 index 094121094..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "pipe-test": "echo hello | node -e \"process.stdin.pipe(process.stdout)\"" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots.toml deleted file mode 100644 index 9cbaffec8..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Tests shell fallback for pipe commands - -[[plan]] -name = "shell_fallback_for_pipe_command" -args = ["run", "pipe-test"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/query_shell_fallback_for_pipe_command.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/query_shell_fallback_for_pipe_command.jsonc deleted file mode 100644 index 283a111bb..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/query_shell_fallback_for_pipe_command.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run pipe-test -{ - "graph": [ - { - "key": [ - "/", - "pipe-test" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "pipe-test", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "pipe-test", - "package_path": "/" - }, - "command": "echo hello | node -e \"process.stdin.pipe(process.stdout)\"", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "" - } - }, - "args": [ - "", - "echo hello | node -e \"process.stdin.pipe(process.stdout)\"" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "pipe-test", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "", - "args": [ - "", - "echo hello | node -e \"process.stdin.pipe(process.stdout)\"" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/task_graph.md deleted file mode 100644 index 5bf34186e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/snapshots/task_graph.md +++ /dev/null @@ -1,46 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#pipe-test"] -``` - -## `/#pipe-test` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "pipe-test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo hello | node -e \"process.stdin.pipe(process.stdout)\"" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/shell_fallback/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/package.json deleted file mode 100644 index cbf2c66a5..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/synthetic-cache-disabled", - "scripts": { - "lint": "vt tool print lint", - "run-build-cache-false": "vt run build" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots.toml deleted file mode 100644 index 2f0e76ed1..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots.toml +++ /dev/null @@ -1,23 +0,0 @@ -[[plan]] -name = "script_without_cache_scripts_defaults_to_no_cache" -args = ["run", "lint"] - -[[plan]] -name = "task_with_cache_false_disables_synthetic_cache" -args = ["run", "lint-no-cache"] - -[[plan]] -name = "task_with_cache_true_enables_synthetic_cache" -args = ["run", "lint-with-cache"] - -[[plan]] -name = "task_untrackedEnv_inherited_by_synthetic" -args = ["run", "lint-with-untracked-env"] - -[[plan]] -name = "parent_cache_false_does_not_affect_expanded_query_tasks" -args = ["run", "run-build-no-cache"] - -[[plan]] -name = "script_cache_false_does_not_affect_expanded_synthetic_cache" -args = ["run", "run-build-cache-false"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_parent_cache_false_does_not_affect_expanded_query_tasks.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_parent_cache_false_does_not_affect_expanded_query_tasks.jsonc deleted file mode 100644 index 024175076..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_parent_cache_false_does_not_affect_expanded_query_tasks.jsonc +++ /dev/null @@ -1,124 +0,0 @@ -// run run-build-no-cache -{ - "graph": [ - { - "key": [ - "/", - "run-build-no-cache" - ], - "node": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "run-build-no-cache", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "run-build-no-cache", - "package_path": "/" - }, - "command": "vt run build", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "build", - "package_path": "/" - }, - "command": "vt tool print lint", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "build", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_script_cache_false_does_not_affect_expanded_synthetic_cache.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_script_cache_false_does_not_affect_expanded_synthetic_cache.jsonc deleted file mode 100644 index 670e73213..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_script_cache_false_does_not_affect_expanded_synthetic_cache.jsonc +++ /dev/null @@ -1,124 +0,0 @@ -// run run-build-cache-false -{ - "graph": [ - { - "key": [ - "/", - "run-build-cache-false" - ], - "node": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "run-build-cache-false", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "run-build-cache-false", - "package_path": "/" - }, - "command": "vt run build", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "build" - ], - "node": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "build", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "build", - "package_path": "/" - }, - "command": "vt tool print lint", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "build", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_script_without_cache_scripts_defaults_to_no_cache.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_script_without_cache_scripts_defaults_to_no_cache.jsonc deleted file mode 100644 index fdf0955d3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_script_without_cache_scripts_defaults_to_no_cache.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run lint -{ - "graph": [ - { - "key": [ - "/", - "lint" - ], - "node": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint", - "package_path": "/" - }, - "command": "vt tool print lint", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_untrackedEnv_inherited_by_synthetic.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_untrackedEnv_inherited_by_synthetic.jsonc deleted file mode 100644 index f8895042e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_untrackedEnv_inherited_by_synthetic.jsonc +++ /dev/null @@ -1,91 +0,0 @@ -// run lint-with-untracked-env -{ - "graph": [ - { - "key": [ - "/", - "lint-with-untracked-env" - ], - "node": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint-with-untracked-env", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint-with-untracked-env", - "package_path": "/" - }, - "command": "vt tool print lint", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "CUSTOM_VAR", - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "lint-with-untracked-env", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_with_cache_false_disables_synthetic_cache.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_with_cache_false_disables_synthetic_cache.jsonc deleted file mode 100644 index aafb74817..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_with_cache_false_disables_synthetic_cache.jsonc +++ /dev/null @@ -1,52 +0,0 @@ -// run lint-no-cache -{ - "graph": [ - { - "key": [ - "/", - "lint-no-cache" - ], - "node": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint-no-cache", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint-no-cache", - "package_path": "/" - }, - "command": "vt tool print lint", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": null, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "NO_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_with_cache_true_enables_synthetic_cache.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_with_cache_true_enables_synthetic_cache.jsonc deleted file mode 100644 index 618261dfa..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/query_task_with_cache_true_enables_synthetic_cache.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run lint-with-cache -{ - "graph": [ - { - "key": [ - "/", - "lint-with-cache" - ], - "node": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint-with-cache", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint-with-cache", - "package_path": "/" - }, - "command": "vt tool print lint", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "lint-with-cache", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/task_graph.md deleted file mode 100644 index a281443e0..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/snapshots/task_graph.md +++ /dev/null @@ -1,253 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#lint"] - task_2["/#lint-no-cache"] - task_3["/#lint-with-cache"] - task_4["/#lint-with-untracked-env"] - task_5["/#run-build-cache-false"] - task_6["/#run-build-no-cache"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt tool print lint" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#lint` - -```json -{ - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt tool print lint" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#lint-no-cache` - -```json -{ - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint-no-cache", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt tool print lint" - ], - "resolved_options": { - "cwd": "/", - "cache_config": null - } - }, - "source": "TaskConfig" -} -``` - -## `/#lint-with-cache` - -```json -{ - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint-with-cache", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt tool print lint" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#lint-with-untracked-env` - -```json -{ - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "lint-with-untracked-env", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt tool print lint" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "CUSTOM_VAR", - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#run-build-cache-false` - -```json -{ - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "run-build-cache-false", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#run-build-no-cache` - -```json -{ - "task_display": { - "package_name": "@test/synthetic-cache-disabled", - "task_name": "run-build-no-cache", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": null - } - }, - "source": "TaskConfig" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/vite-task.json deleted file mode 100644 index ce6474eda..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_cache_disabled/vite-task.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "tasks": { - "lint-no-cache": { - "command": "vt tool print lint", - "cache": false - }, - "lint-with-cache": { - "command": "vt tool print lint", - "cache": true - }, - "lint-with-untracked-env": { - "command": "vt tool print lint", - "untrackedEnv": ["CUSTOM_VAR"] - }, - "build": { - "command": "vt tool print lint", - "cache": true - }, - "run-build-no-cache": { - "command": "vt run build", - "cache": false - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/package.json deleted file mode 100644 index d85514239..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "lint": "vt run a#lint" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/packages/a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/packages/a/package.json deleted file mode 100644 index ba249a77a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/packages/a/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "a", - "scripts": { - "lint": "vt tool print lint" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots.toml deleted file mode 100644 index 0d39fc450..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Tests synthetic task in subpackage - -[[plan]] -name = "synthetic_in_subpackage" -args = ["run", "lint"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/query_synthetic_in_subpackage.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/query_synthetic_in_subpackage.jsonc deleted file mode 100644 index 4e6fedf03..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/query_synthetic_in_subpackage.jsonc +++ /dev/null @@ -1,124 +0,0 @@ -// run lint -{ - "graph": [ - { - "key": [ - "/", - "lint" - ], - "node": { - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/" - }, - "command": "vt run a#lint", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/packages/a", - "lint" - ], - "node": { - "task_display": { - "package_name": "a", - "task_name": "lint", - "package_path": "/packages/a" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "a", - "task_name": "lint", - "package_path": "/packages/a" - }, - "command": "vt tool print lint", - "cwd": "/packages/a" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "packages/a", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "lint" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "lint", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "packages/a" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "lint" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/packages/a/node_modules/.bin:/node_modules/.bin:" - }, - "cwd": "/packages/a" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/task_graph.md deleted file mode 100644 index 32338a1b9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#lint"] - task_1["/packages/a#lint"] -``` - -## `/#lint` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "lint", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run a#lint" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/a#lint` - -```json -{ - "task_display": { - "package_name": "a", - "task_name": "lint", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "vt tool print lint" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/synthetic_in_subpackage/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/package.json deleted file mode 100644 index a2538e229..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "@test/task-command-shorthands", - "private": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots.toml deleted file mode 100644 index d178c4774..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots.toml +++ /dev/null @@ -1,27 +0,0 @@ -[[plan]] -name = "string_shorthand" -args = ["run", "string_shorthand"] - -[[plan]] -name = "array_shorthand" -args = ["run", "array_shorthand"] - -[[plan]] -name = "array_with_and" -args = ["run", "array_with_and"] - -[[plan]] -name = "nested_vt_array" -args = ["run", "nested_vt_array"] - -[[plan]] -name = "array_cd" -args = ["run", "array_cd"] - -[[plan]] -name = "array_unbalanced_quotes" -args = ["run", "array_unbalanced_quotes"] - -[[plan]] -name = "object_array_depends_on" -args = ["run", "object_array_depends_on"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_cd.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_cd.jsonc deleted file mode 100644 index 7d755741d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_cd.jsonc +++ /dev/null @@ -1,226 +0,0 @@ -// run array_cd -{ - "graph": [ - { - "key": [ - "/", - "array_cd" - ], - "node": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_cd", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_cd", - "package_path": "/" - }, - "command": "vtt print before_cd", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "before_cd" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_cd", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "before_cd" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_cd", - "package_path": "/" - }, - "command": "vtt print after_cd", - "cwd": "/snapshots" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "snapshots", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "after_cd" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_cd", - "command_item_index": 1, - "and_item_index": 1, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "after_cd" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/snapshots" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_cd", - "package_path": "/" - }, - "command": "vtt print after_cd2", - "cwd": "/snapshots" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "snapshots", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print", - "after_cd2" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_cd", - "command_item_index": 2, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print", - "after_cd2" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/snapshots" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_shorthand.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_shorthand.jsonc deleted file mode 100644 index 50e3d4a79..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_shorthand.jsonc +++ /dev/null @@ -1,226 +0,0 @@ -// run array_shorthand -{ - "graph": [ - { - "key": [ - "/", - "array_shorthand" - ], - "node": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_shorthand", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_shorthand", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_shorthand", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_shorthand", - "package_path": "/" - }, - "command": "vtt print-file vite-task.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "vite-task.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_shorthand", - "command_item_index": 1, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "vite-task.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_shorthand", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_shorthand", - "command_item_index": 2, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_unbalanced_quotes.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_unbalanced_quotes.jsonc deleted file mode 100644 index fab46abe4..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_unbalanced_quotes.jsonc +++ /dev/null @@ -1,158 +0,0 @@ -// run array_unbalanced_quotes -{ - "graph": [ - { - "key": [ - "/", - "array_unbalanced_quotes" - ], - "node": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_unbalanced_quotes", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_unbalanced_quotes", - "package_path": "/" - }, - "command": "foo '", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "" - } - }, - "args": [ - "", - "foo '" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_unbalanced_quotes", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "", - "args": [ - "", - "foo '" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_unbalanced_quotes", - "package_path": "/" - }, - "command": "' bar", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "" - } - }, - "args": [ - "", - "' bar" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_unbalanced_quotes", - "command_item_index": 1, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "", - "args": [ - "", - "' bar" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_with_and.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_with_and.jsonc deleted file mode 100644 index b8b1ce63d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_array_with_and.jsonc +++ /dev/null @@ -1,226 +0,0 @@ -// run array_with_and -{ - "graph": [ - { - "key": [ - "/", - "array_with_and" - ], - "node": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_with_and", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_with_and", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_with_and", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_with_and", - "package_path": "/" - }, - "command": "vtt print-file vite-task.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "vite-task.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_with_and", - "command_item_index": 1, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "vite-task.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_with_and", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "array_with_and", - "command_item_index": 1, - "and_item_index": 1, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_nested_vt_array.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_nested_vt_array.jsonc deleted file mode 100644 index 29e0f1aa2..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_nested_vt_array.jsonc +++ /dev/null @@ -1,192 +0,0 @@ -// run nested_vt_array -{ - "graph": [ - { - "key": [ - "/", - "nested_vt_array" - ], - "node": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "nested_vt_array", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "nested_vt_array", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "nested_vt_array", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "nested_vt_array", - "package_path": "/" - }, - "command": "vt run string_shorthand", - "cwd": "/" - }, - "kind": { - "Expanded": { - "graph": [ - { - "key": [ - "/", - "string_shorthand" - ], - "node": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "string_shorthand", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "string_shorthand", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "string_shorthand", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_object_array_depends_on.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_object_array_depends_on.jsonc deleted file mode 100644 index 4c28fc732..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_object_array_depends_on.jsonc +++ /dev/null @@ -1,315 +0,0 @@ -// run object_array_depends_on -{ - "graph": [ - { - "key": [ - "/", - "object_array_depends_on" - ], - "node": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "object_array_depends_on", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "object_array_depends_on", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "object_array_depends_on", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "object_array_depends_on", - "package_path": "/" - }, - "command": "vtt print-file vite-task.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "vite-task.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "object_array_depends_on", - "command_item_index": 1, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "vite-task.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - }, - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "object_array_depends_on", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "object_array_depends_on", - "command_item_index": 2, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [ - [ - "/", - "string_shorthand" - ] - ] - }, - { - "key": [ - "/", - "string_shorthand" - ], - "node": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "string_shorthand", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "string_shorthand", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "string_shorthand", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_string_shorthand.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_string_shorthand.jsonc deleted file mode 100644 index 9551ce0a8..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/query_string_shorthand.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run string_shorthand -{ - "graph": [ - { - "key": [ - "/", - "string_shorthand" - ], - "node": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "string_shorthand", - "package_path": "/" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "string_shorthand", - "package_path": "/" - }, - "command": "vtt print-file package.json", - "cwd": "/" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "", - "program_fingerprint": { - "OutsideWorkspace": { - "program_name": "vtt" - } - }, - "args": [ - "print-file", - "package.json" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "string_shorthand", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/vtt", - "args": [ - "print-file", - "package.json" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/node_modules/.bin:" - }, - "cwd": "/" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/task_graph.md deleted file mode 100644 index 60ac5b57b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/snapshots/task_graph.md +++ /dev/null @@ -1,296 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#array_cd"] - task_1["/#array_shorthand"] - task_2["/#array_unbalanced_quotes"] - task_3["/#array_with_and"] - task_4["/#nested_vt_array"] - task_5["/#object_array_depends_on"] - task_5 --> task_6 - task_6["/#string_shorthand"] -``` - -## `/#array_cd` - -```json -{ - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_cd", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print before_cd", - "cd snapshots && vtt print after_cd", - "vtt print after_cd2" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#array_shorthand` - -```json -{ - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_shorthand", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json", - "vtt print-file vite-task.json", - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#array_unbalanced_quotes` - -```json -{ - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_unbalanced_quotes", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "foo '", - "' bar" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#array_with_and` - -```json -{ - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "array_with_and", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json", - "vtt print-file vite-task.json && vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#nested_vt_array` - -```json -{ - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "nested_vt_array", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json", - "vt run string_shorthand" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#object_array_depends_on` - -```json -{ - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "object_array_depends_on", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json", - "vtt print-file vite-task.json", - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#string_shorthand` - -```json -{ - "task_display": { - "package_name": "@test/task-command-shorthands", - "task_name": "string_shorthand", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vtt print-file package.json" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/vite-task.json deleted file mode 100644 index 286a139fa..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/task_command_shorthands/vite-task.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "tasks": { - "string_shorthand": "vtt print-file package.json", - "array_shorthand": [ - "vtt print-file package.json", - "vtt print-file vite-task.json", - "vtt print-file package.json" - ], - "array_with_and": [ - "vtt print-file package.json", - "vtt print-file vite-task.json && vtt print-file package.json" - ], - "nested_vt_array": ["vtt print-file package.json", "vt run string_shorthand"], - "array_cd": [ - "vtt print before_cd", - "cd snapshots && vtt print after_cd", - "vtt print after_cd2" - ], - "array_unbalanced_quotes": ["foo '", "' bar"], - "object_array_depends_on": { - "command": [ - "vtt print-file package.json", - "vtt print-file vite-task.json", - "vtt print-file package.json" - ], - "dependsOn": ["string_shorthand"] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/package.json deleted file mode 100644 index b65cafad5..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "test-workspace", - "private": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/packages/bottom/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/packages/bottom/package.json deleted file mode 100644 index de5f557e3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/packages/bottom/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/bottom", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/bottom'" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/packages/middle/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/packages/middle/package.json deleted file mode 100644 index 5a3584d13..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/packages/middle/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@test/middle", - "version": "1.0.0", - "scripts": { - "lint": "echo 'Linting @test/middle'" - }, - "dependencies": { - "@test/bottom": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/packages/top/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/packages/top/package.json deleted file mode 100644 index c41f45ab5..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/packages/top/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@test/top", - "version": "1.0.0", - "scripts": { - "build": "echo 'Building @test/top'" - }, - "dependencies": { - "@test/middle": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots.toml deleted file mode 100644 index e41b61726..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots.toml +++ /dev/null @@ -1,42 +0,0 @@ -# Tests skip-intermediate reconnection when a package lacks the requested task. -# Topology: top → middle → bottom -# top and bottom have `build`; middle does NOT. - -# pnpm Example 1: top... selects {top, middle, bottom}. -# middle lacks build — reconnected (top→bottom). Result: bottom#build → top#build. -[[plan]] -compact = true -name = "filter_intermediate_lacks_task_is_skipped" -args = ["run", "--filter", "@test/top...", "build"] - -# Same scenario via -t flag (ContainingPackage traversal). -[[plan]] -compact = true -name = "transitive_build_skips_intermediate_without_task" -args = ["run", "-t", "build"] -cwd = "packages/top" - -# pnpm Example 2: middle... selects {middle, bottom}. middle lacks build. -# middle is reconnected (no preds → bottom runs independently). -# Result: bottom#build only. -[[plan]] -compact = true -name = "filter_entry_lacks_task_dependency_has_it" -args = ["run", "--filter", "@test/middle...", "build"] - -# Same as above but via -t flag from the package that lacks the task. -# middle has no build, but its dependency bottom does. -# Result: bottom#build only. -[[plan]] -compact = true -name = "transitive_from_package_without_task" -args = ["run", "-t", "build"] -cwd = "packages/middle" - -# -t with package#task: middle has no build, but its dep bottom does. -# Equivalent to --filter @test/middle... build. -# Result: bottom#build only. -[[plan]] -compact = true -name = "transitive_with_package_specifier_lacking_task" -args = ["run", "-t", "@test/middle#build"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_filter_entry_lacks_task_dependency_has_it.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_filter_entry_lacks_task_dependency_has_it.jsonc deleted file mode 100644 index c74c50d29..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_filter_entry_lacks_task_dependency_has_it.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run --filter @test/middle... build -{ - "packages/bottom#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_filter_intermediate_lacks_task_is_skipped.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_filter_intermediate_lacks_task_is_skipped.jsonc deleted file mode 100644 index 4949faeba..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_filter_intermediate_lacks_task_is_skipped.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -// run --filter @test/top... build -{ - "packages/bottom#build": [], - "packages/top#build": [ - "packages/bottom#build" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_transitive_build_skips_intermediate_without_task.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_transitive_build_skips_intermediate_without_task.jsonc deleted file mode 100644 index b740f0e22..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_transitive_build_skips_intermediate_without_task.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -// run -t build -{ - "packages/bottom#build": [], - "packages/top#build": [ - "packages/bottom#build" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_transitive_from_package_without_task.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_transitive_from_package_without_task.jsonc deleted file mode 100644 index 6a8fb1dcf..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_transitive_from_package_without_task.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run -t build -{ - "packages/bottom#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_transitive_with_package_specifier_lacking_task.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_transitive_with_package_specifier_lacking_task.jsonc deleted file mode 100644 index c980c2aab..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/query_transitive_with_package_specifier_lacking_task.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -// run -t @test/middle#build -{ - "packages/bottom#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/task_graph.md deleted file mode 100644 index dcd37494b..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/transitive_skip_intermediate/snapshots/task_graph.md +++ /dev/null @@ -1,126 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/bottom#build"] - task_1["/packages/middle#lint"] - task_2["/packages/top#build"] -``` - -## `/packages/bottom#build` - -```json -{ - "task_display": { - "package_name": "@test/bottom", - "task_name": "build", - "package_path": "/packages/bottom" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/bottom'" - ], - "resolved_options": { - "cwd": "/packages/bottom", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/middle#lint` - -```json -{ - "task_display": { - "package_name": "@test/middle", - "task_name": "lint", - "package_path": "/packages/middle" - }, - "resolved_config": { - "commands": [ - "echo 'Linting @test/middle'" - ], - "resolved_options": { - "cwd": "/packages/middle", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/top#build` - -```json -{ - "task_display": { - "package_name": "@test/top", - "task_name": "build", - "package_path": "/packages/top" - }, - "resolved_config": { - "commands": [ - "echo 'Building @test/top'" - ], - "resolved_options": { - "cwd": "/packages/top", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/package.json deleted file mode 100644 index 314d6388d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "scripts": { - "build": "echo building", - "all": "vpr build" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots.toml deleted file mode 100644 index 763596397..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots.toml +++ /dev/null @@ -1,4 +0,0 @@ -[[plan]] -name = "vpr_expands_to_vt_run" -args = ["run", "all"] -compact = true diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots/query_vpr_expands_to_vt_run.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots/query_vpr_expands_to_vt_run.jsonc deleted file mode 100644 index 374ab860d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots/query_vpr_expands_to_vt_run.jsonc +++ /dev/null @@ -1,11 +0,0 @@ -// run all -{ - "#all": { - "items": [ - { - "#build": [] - } - ], - "neighbors": [] - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots/task_graph.md deleted file mode 100644 index 5c05ed175..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#all"] - task_1["/#build"] -``` - -## `/#all` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "all", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vpr build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo building" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/vpr_shorthand/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/package.json deleted file mode 100644 index 0967ef424..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/package.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/packages/foo/node_modules/.bin/vite.cmd b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/packages/foo/node_modules/.bin/vite.cmd deleted file mode 100644 index e69de29bb..000000000 diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/packages/foo/node_modules/.bin/vite.ps1 b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/packages/foo/node_modules/.bin/vite.ps1 deleted file mode 100644 index e69de29bb..000000000 diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/packages/foo/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/packages/foo/package.json deleted file mode 100644 index 2392b6f18..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/packages/foo/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "foo", - "scripts": { - "dev": "vite --port 3000" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots.toml deleted file mode 100644 index 31de8bb34..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots.toml +++ /dev/null @@ -1,21 +0,0 @@ -# Windows-only. The `.cmd` → PowerShell rewrite at plan time is gated on an -# interactive terminal (see `vite_task_plan::ps1_shim::is_stdin_terminal`): the -# `.ps1` wrappers hang on a non-TTY stdin pipe, as on CI. The test runner has no -# TTY on stdin, so the rewrite is skipped here and the plan records the -# `node_modules/.bin` shim directly instead of `powershell -File …vite.ps1`. -# This still verifies the cache key stays portable regardless of how the user -# navigated to the sub-package task: both plan cases below target the same task -# and their snapshots should be byte-identical apart from the plan query itself, -# proving the cache key doesn't depend on invocation style. -platform = "windows" - -# Direct invocation: user `cd`'d into the sub-package before running. -[[plan]] -name = "dev_in_subpackage" -cwd = "packages/foo" -args = ["run", "dev"] - -# Same sub-package task reached via `--filter` from the workspace root. -[[plan]] -name = "dev_filter_from_root" -args = ["run", "--filter", "./packages/foo", "dev"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_filter_from_root.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_filter_from_root.jsonc deleted file mode 100644 index f082e3b25..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_filter_from_root.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run --filter ./packages/foo dev -{ - "graph": [ - { - "key": [ - "/packages/foo", - "dev" - ], - "node": { - "task_display": { - "package_name": "foo", - "task_name": "dev", - "package_path": "/packages/foo" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "foo", - "task_name": "dev", - "package_path": "/packages/foo" - }, - "command": "vite --port 3000", - "cwd": "/packages/foo" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "packages/foo", - "program_fingerprint": { - "InsideWorkspace": { - "relative_program_path": "packages/foo/node_modules/.bin/vite.cmd" - } - }, - "args": [ - "--port", - "3000" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "dev", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "packages/foo" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/packages/foo/node_modules/.bin/vite", - "args": [ - "--port", - "3000" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/packages/foo/node_modules/.bin:/node_modules/.bin:" - }, - "cwd": "/packages/foo" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_in_subpackage.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_in_subpackage.jsonc deleted file mode 100644 index f8e12919d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/query_dev_in_subpackage.jsonc +++ /dev/null @@ -1,90 +0,0 @@ -// run dev -{ - "graph": [ - { - "key": [ - "/packages/foo", - "dev" - ], - "node": { - "task_display": { - "package_name": "foo", - "task_name": "dev", - "package_path": "/packages/foo" - }, - "items": [ - { - "execution_item_display": { - "task_display": { - "package_name": "foo", - "task_name": "dev", - "package_path": "/packages/foo" - }, - "command": "vite --port 3000", - "cwd": "/packages/foo" - }, - "kind": { - "Leaf": { - "Spawn": { - "cache_metadata": { - "spawn_fingerprint": { - "cwd": "packages/foo", - "program_fingerprint": { - "InsideWorkspace": { - "relative_program_path": "packages/foo/node_modules/.bin/vite.cmd" - } - }, - "args": [ - "--port", - "3000" - ], - "env_fingerprints": { - "fingerprinted_envs": {}, - "untracked_env_config": [ - "" - ] - } - }, - "execution_cache_key": { - "UserTask": { - "task_name": "dev", - "command_item_index": 0, - "and_item_index": 0, - "extra_args": [], - "package_path": "packages/foo" - } - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - }, - "spawn_command": { - "program_path": "/packages/foo/node_modules/.bin/vite", - "args": [ - "--port", - "3000" - ], - "spawn_envs": { - "FORCE_COLOR": "1", - "PATH": "/packages/foo/node_modules/.bin:/node_modules/.bin:" - }, - "cwd": "/packages/foo" - } - } - } - } - } - ] - }, - "neighbors": [] - } - ], - "concurrency_limit": 4 -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/task_graph.md deleted file mode 100644 index 24743f10d..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/snapshots/task_graph.md +++ /dev/null @@ -1,46 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/packages/foo#dev"] -``` - -## `/packages/foo#dev` - -```json -{ - "task_display": { - "package_name": "foo", - "task_name": "dev", - "package_path": "/packages/foo" - }, - "resolved_config": { - "commands": [ - "vite --port 3000" - ], - "resolved_options": { - "cwd": "/packages/foo", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/windows_cmd_shim_rewrite/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/package.json deleted file mode 100644 index 1a95b7d40..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "test-workspace", - "private": true, - "scripts": { - "deploy": "cd packages/a && vt run deploy" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/packages/a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/packages/a/package.json deleted file mode 100644 index 08a9ce0df..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/packages/a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/a", - "version": "1.0.0", - "scripts": { - "deploy": "echo deploying-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots.toml deleted file mode 100644 index ffd396db3..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Tests that `cd` before `vt run` prevents the skip rule from firing. -# Root deploy = `cd packages/a && vt run deploy`. -# -# The skip rule compares TaskQuery, which includes ContainingPackage(cwd). -# After `cd packages/a`, the cwd changes, so the nested query resolves to -# ContainingPackage(packages/a) — different from the parent's -# ContainingPackage(root). The queries don't match and the skip rule -# does NOT fire, allowing the nested expansion to proceed normally. - -[[plan]] -name = "cd_changes_cwd_so_skip_rule_does_not_fire" -args = ["run", "deploy"] -compact = true diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots/query_cd_changes_cwd_so_skip_rule_does_not_fire.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots/query_cd_changes_cwd_so_skip_rule_does_not_fire.jsonc deleted file mode 100644 index 3e7514e70..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots/query_cd_changes_cwd_so_skip_rule_does_not_fire.jsonc +++ /dev/null @@ -1,11 +0,0 @@ -// run deploy -{ - "#deploy": { - "items": [ - { - "packages/a#deploy": [] - } - ], - "neighbors": [] - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots/task_graph.md deleted file mode 100644 index be88151cf..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#deploy"] - task_1["/packages/a#deploy"] -``` - -## `/#deploy` - -```json -{ - "task_display": { - "package_name": "test-workspace", - "task_name": "deploy", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "cd packages/a && vt run deploy" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/a#deploy` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "deploy", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "echo deploying-a" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_cd_no_skip/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/package.json deleted file mode 100644 index fc2ede25a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "test-workspace", - "private": true, - "scripts": { - "lint": "echo linting" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/packages/a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/packages/a/package.json deleted file mode 100644 index 259f240c1..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/packages/a/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/a", - "version": "1.0.0", - "scripts": { - "build": "echo building-a", - "lint": "echo linting-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots.toml deleted file mode 100644 index a7c814f99..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots.toml +++ /dev/null @@ -1,8 +0,0 @@ -# Tests that dependsOn works through a passthrough root task. -# Root build = "vt run -r build", with dependsOn: ["lint"]. -# The skip rule skips the recursive part, but the dependsOn lint tasks still run. - -[[plan]] -name = "depends_on_through_passthrough" -args = ["run", "-r", "build"] -compact = true diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots/query_depends_on_through_passthrough.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots/query_depends_on_through_passthrough.jsonc deleted file mode 100644 index 1c48367cc..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots/query_depends_on_through_passthrough.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -// run -r build -{ - "#build": [ - "#lint" - ], - "#lint": [], - "packages/a#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots/task_graph.md deleted file mode 100644 index 431efd8e5..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/snapshots/task_graph.md +++ /dev/null @@ -1,167 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_0 --> task_1 - task_1["/#lint"] - task_2["/packages/a#build"] - task_3["/packages/a#lint"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run -r build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/#lint` - -```json -{ - "task_display": { - "package_name": "test-workspace", - "task_name": "lint", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo linting" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/a#build` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "echo building-a" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/a#lint` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "lint", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "echo linting-a" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/vite-task.json deleted file mode 100644 index 51e7455a6..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_depends_on_passthrough/vite-task.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "cache": true, - "tasks": { - "build": { - "command": "vt run -r build", - "dependsOn": ["lint"] - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/package.json deleted file mode 100644 index bd957321c..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "test-workspace", - "private": true, - "scripts": { - "build": "echo pre && vt run -r build" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/packages/a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/packages/a/package.json deleted file mode 100644 index e0c6c3f2f..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/packages/a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/a", - "version": "1.0.0", - "scripts": { - "build": "echo building-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots.toml deleted file mode 100644 index 3993fb3d7..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots.toml +++ /dev/null @@ -1,7 +0,0 @@ -# Tests multi-command with self-referencing: root build = "echo pre && vt run -r build" -# The skip rule skips the recursive `vt run -r build` part, echo pre still runs. - -[[plan]] -name = "multi_command_skips_recursive_part" -args = ["run", "-r", "build"] -compact = true diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots/query_multi_command_skips_recursive_part.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots/query_multi_command_skips_recursive_part.jsonc deleted file mode 100644 index 19421f4da..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots/query_multi_command_skips_recursive_part.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -// run -r build -{ - "#build": [], - "packages/a#build": [] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots/task_graph.md deleted file mode 100644 index 550c93254..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/packages/a#build"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo pre && vt run -r build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/a#build` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "echo building-a" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_multi_command/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/package.json deleted file mode 100644 index 93a798861..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "test-workspace", - "private": true, - "scripts": { - "build": "vt run -r test", - "test": "vt run -r build" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/packages/a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/packages/a/package.json deleted file mode 100644 index 6fafd4d1a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/packages/a/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/a", - "version": "1.0.0", - "scripts": { - "build": "echo building-a", - "test": "echo testing-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots.toml deleted file mode 100644 index 8f10d3002..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Tests mutual recursion detection: root#build → vt run -r test → root#test → vt run -r build → root#build -# This is NOT self-recursion — it's caught by check_recursion as mutual recursion. - -[[plan]] -name = "mutual_recursion_error" -args = ["run", "-r", "build"] diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots/query_mutual_recursion_error.snap b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots/query_mutual_recursion_error.snap deleted file mode 100644 index 2c40ba382..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots/query_mutual_recursion_error.snap +++ /dev/null @@ -1 +0,0 @@ -Failed to plan tasks from `vt run -r test` in task test-workspace#build: Failed to plan tasks from `vt run -r build` in task test-workspace#test: Detected a recursion in task call stack: the last frame calls the 1th frame \ No newline at end of file diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots/task_graph.md deleted file mode 100644 index d64962355..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/snapshots/task_graph.md +++ /dev/null @@ -1,166 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/#test"] - task_2["/packages/a#build"] - task_3["/packages/a#test"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run -r test" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/#test` - -```json -{ - "task_display": { - "package_name": "test-workspace", - "task_name": "test", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run -r build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/a#build` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "echo building-a" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/a#test` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "test", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "echo testing-a" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_mutual_recursion/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/packages/pkg-a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/packages/pkg-a/package.json deleted file mode 100644 index 3fadeff85..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/packages/pkg-a/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@test/pkg-a", - "scripts": { - "build": "echo building pkg-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/snapshots.toml deleted file mode 100644 index 432a4504a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/snapshots.toml +++ /dev/null @@ -1 +0,0 @@ -# Test cases for workspace root without package.json diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/snapshots/task_graph.md deleted file mode 100644 index f04e05493..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/snapshots/task_graph.md +++ /dev/null @@ -1,86 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#deploy"] - task_1["/packages/pkg-a#build"] -``` - -## `/#deploy` - -```json -{ - "task_display": { - "package_name": "", - "task_name": "deploy", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "echo deploying workspace" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "TaskConfig" -} -``` - -## `/packages/pkg-a#build` - -```json -{ - "task_display": { - "package_name": "@test/pkg-a", - "task_name": "build", - "package_path": "/packages/pkg-a" - }, - "resolved_config": { - "commands": [ - "echo building pkg-a" - ], - "resolved_options": { - "cwd": "/packages/pkg-a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/vite-task.json deleted file mode 100644 index 942c99fe2..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_no_package_json/vite-task.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - // This workspace root has no package.json — only pnpm-workspace.yaml. - // vite-task.json should still be loaded and applied. - "cache": true, - "tasks": { - "deploy": { - "command": "echo deploying workspace" - } - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/package.json deleted file mode 100644 index ea186e967..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "test-workspace", - "private": true, - "scripts": { - "build": "vt run -r build" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/packages/a/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/packages/a/package.json deleted file mode 100644 index e0c6c3f2f..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/packages/a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@test/a", - "version": "1.0.0", - "scripts": { - "build": "echo building-a" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/packages/b/package.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/packages/b/package.json deleted file mode 100644 index aa937a790..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/packages/b/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@test/b", - "version": "1.0.0", - "scripts": { - "build": "echo building-b" - }, - "dependencies": { - "@test/a": "workspace:*" - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/pnpm-workspace.yaml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/pnpm-workspace.yaml deleted file mode 100644 index 18ec407ef..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots.toml b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots.toml deleted file mode 100644 index 16f7895e9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots.toml +++ /dev/null @@ -1,36 +0,0 @@ -# Tests workspace root self-reference handling (skip rule and prune rule). -# Root build = `vt run -r build`. -# -# The skip rule compares TaskQuery (package_query + task_name + include_explicit_deps). -# Extra args are in PlanOptions, not TaskQuery, so they don't affect skip/prune. - -# Skip rule: the nested `vt run -r build` produces the same query as the -# top-level `vt run -r build`, so the expansion is skipped. Root#build -# becomes a passthrough (no items). Siblings a and b run normally. -[[plan]] -name = "recursive_build_skips_self" -args = ["run", "-r", "build"] -compact = true - -# Skip rule with extra_arg: same as above — extra_arg goes into PlanOptions, -# not TaskQuery, so the query still matches and the skip rule fires. -[[plan]] -name = "recursive_build_with_extra_arg_skips_self" -args = ["run", "-r", "build", "extra_arg"] -compact = true - -# Prune rule: top-level query is `vt run build` (ContainingPackage), but root's -# script produces `vt run -r build` (All). The queries differ, so the skip rule -# does not fire. The nested All query finds root + a + b; the prune rule removes -# root (the expanding task) from the result, leaving a and b. -[[plan]] -name = "build_from_root_expands_without_self" -args = ["run", "build"] -compact = true - -# Prune rule with extra_arg: same as above — extra_arg doesn't affect the query, -# so the prune rule still fires. -[[plan]] -name = "build_from_root_with_extra_arg_expands_without_self" -args = ["run", "build", "extra_arg"] -compact = true diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_build_from_root_expands_without_self.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_build_from_root_expands_without_self.jsonc deleted file mode 100644 index 4d238d7a9..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_build_from_root_expands_without_self.jsonc +++ /dev/null @@ -1,14 +0,0 @@ -// run build -{ - "#build": { - "items": [ - { - "packages/a#build": [], - "packages/b#build": [ - "packages/a#build" - ] - } - ], - "neighbors": [] - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_build_from_root_with_extra_arg_expands_without_self.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_build_from_root_with_extra_arg_expands_without_self.jsonc deleted file mode 100644 index ec5c1a180..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_build_from_root_with_extra_arg_expands_without_self.jsonc +++ /dev/null @@ -1,14 +0,0 @@ -// run build extra_arg -{ - "#build": { - "items": [ - { - "packages/a#build": [], - "packages/b#build": [ - "packages/a#build" - ] - } - ], - "neighbors": [] - } -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_recursive_build_skips_self.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_recursive_build_skips_self.jsonc deleted file mode 100644 index f6fc6faee..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_recursive_build_skips_self.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -// run -r build -{ - "#build": [], - "packages/a#build": [], - "packages/b#build": [ - "packages/a#build" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_recursive_build_with_extra_arg_skips_self.jsonc b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_recursive_build_with_extra_arg_skips_self.jsonc deleted file mode 100644 index 6e7b2c90a..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/query_recursive_build_with_extra_arg_skips_self.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -// run -r build extra_arg -{ - "#build": [], - "packages/a#build": [], - "packages/b#build": [ - "packages/a#build" - ] -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/task_graph.md b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/task_graph.md deleted file mode 100644 index ea93658c0..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/snapshots/task_graph.md +++ /dev/null @@ -1,126 +0,0 @@ -# task graph - -```mermaid -flowchart TD - task_0["/#build"] - task_1["/packages/a#build"] - task_2["/packages/b#build"] -``` - -## `/#build` - -```json -{ - "task_display": { - "package_name": "test-workspace", - "task_name": "build", - "package_path": "/" - }, - "resolved_config": { - "commands": [ - "vt run -r build" - ], - "resolved_options": { - "cwd": "/", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/a#build` - -```json -{ - "task_display": { - "package_name": "@test/a", - "task_name": "build", - "package_path": "/packages/a" - }, - "resolved_config": { - "commands": [ - "echo building-a" - ], - "resolved_options": { - "cwd": "/packages/a", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - -## `/packages/b#build` - -```json -{ - "task_display": { - "package_name": "@test/b", - "task_name": "build", - "package_path": "/packages/b" - }, - "resolved_config": { - "commands": [ - "echo building-b" - ], - "resolved_options": { - "cwd": "/packages/b", - "cache_config": { - "env_config": { - "fingerprinted_envs": [], - "untracked_env": [ - "" - ] - }, - "input_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - }, - "output_config": { - "includes_auto": true, - "positive_globs": [], - "negative_globs": [] - } - } - } - }, - "source": "PackageJsonScript" -} -``` - diff --git a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/vite-task.json b/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/vite-task.json deleted file mode 100644 index d548edfac..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/fixtures/workspace_root_self_reference/vite-task.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cache": true -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/main.rs b/crates/vite_task_plan/tests/plan_snapshots/main.rs deleted file mode 100644 index 36801e754..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/main.rs +++ /dev/null @@ -1,359 +0,0 @@ -mod redact; -mod task_graph_markdown; - -use std::{ - collections::{BTreeMap, BTreeSet}, - ffi::OsStr, - sync::Arc, -}; - -use clap::Parser; -use copy_dir::copy_dir; -use cow_utils::CowUtils as _; -use redact::redact_snapshot; -use rustc_hash::FxHashMap; -use serde::Serialize; -use task_graph_markdown::render_task_graph_markdown; -use tokio::runtime::Runtime; -use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf}; -use vite_str::Str; -use vite_task::{Command, Session}; -use vite_task_graph::display::TaskDisplay; -use vite_task_plan::{ExecutionGraph, ExecutionItemKind}; -use vite_workspace::find_workspace_root; - -/// Local parser wrapper for `BuiltInCommand` -#[derive(Parser)] -#[command(name = "vt")] -enum Cli { - #[clap(flatten)] - Command(Command), -} - -#[derive(serde::Deserialize, Debug)] -struct Plan { - pub name: Str, - pub args: Vec, - #[serde(default)] - pub cwd: RelativePathBuf, - #[serde(default)] - pub compact: bool, - #[serde(default)] - pub env: BTreeMap, -} - -#[derive(serde::Deserialize, Default)] -struct SnapshotsFile { - /// Optional platform filter: `"unix"` or `"windows"`. If set, the whole - /// fixture only runs on that platform. Fixtures whose filter doesn't - /// match the current build are dropped during trial enumeration so they - /// don't show up as "passed" when they never ran. Mirrors the per-case - /// filter used by the e2e harness. - #[serde(default)] - pub platform: Option, - #[serde(rename = "plan", default)] // toml usually uses singular for arrays - pub plan_cases: Vec, -} - -/// Returns whether the current build should run the fixture. Panics on an -/// unknown platform string so typos surface loudly. -fn should_run_on_this_platform(platform: Option<&Str>) -> bool { - match platform.map(Str::as_str) { - None => true, - Some("unix") => cfg!(unix), - Some("windows") => cfg!(windows), - Some(other) => { - panic!("Unknown platform filter '{other}' — expected \"unix\" or \"windows\"") - } - } -} - -/// Compact plan: maps `"relative_path#task_name"` to either just neighbors (simple) -/// or `{ items, neighbors }` when the node has nested `Expanded` execution items. -#[derive(Serialize)] -#[serde(transparent)] -struct CompactPlan(BTreeMap); - -/// Untagged enum so simple nodes serialize as just an array, and nodes with -/// expanded items serialize as `{ "items": [...], "neighbors": [...] }`. -#[derive(Serialize)] -#[serde(untagged)] -enum CompactNode { - /// No nested `Expanded` items — just the neighbor list - Simple(BTreeSet), - /// Has nested `Expanded` items - WithItems { items: Vec, neighbors: BTreeSet }, -} - -impl CompactPlan { - fn from_execution_graph(graph: &ExecutionGraph, workspace_root: &AbsolutePath) -> Self { - use petgraph::visit::EdgeRef as _; - let mut map = BTreeMap::::new(); - for node_idx in graph.graph.node_indices() { - let node = &graph.graph[node_idx]; - let key = Self::task_key(&node.task_display, workspace_root); - - let neighbors: BTreeSet = graph - .graph - .edges(node_idx) - .map(|edge| { - Self::task_key(&graph.graph[edge.target()].task_display, workspace_root) - }) - .collect(); - - let expanded_items: Vec = node - .items - .iter() - .filter_map(|item| { - if let ExecutionItemKind::Expanded(sub_graph) = &item.kind { - Some(Self::from_execution_graph(sub_graph, workspace_root)) - } else { - None - } - }) - .collect(); - - let compact_node = if expanded_items.is_empty() { - CompactNode::Simple(neighbors) - } else { - CompactNode::WithItems { items: expanded_items, neighbors } - }; - map.insert(key, compact_node); - } - Self(map) - } - - fn task_key(task_display: &TaskDisplay, workspace_root: &AbsolutePath) -> Str { - let relative = task_display - .package_path - .strip_prefix(workspace_root) - .expect("strip_prefix should not produce invalid path data") - .expect("package_path must be under workspace_root"); - vite_str::format!("{}#{}", relative, task_display.task_name) - } -} - -/// Fixture folder names and `[[plan]].name` values must be made of -/// `[A-Za-z0-9_]` only so trial names round-trip through shell filters -/// and snapshot filenames don't carry whitespace or special characters. -fn assert_identifier_like(kind: &str, value: &str) { - assert!( - !value.is_empty() && value.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_'), - "{kind} '{value}' must contain only ASCII letters, digits, and '_'" - ); -} - -#[expect( - clippy::disallowed_types, - reason = "Path required for fixture handling; String required by std::fs::read and toml::from_slice" -)] -fn load_snapshots_file(fixture_path: &std::path::Path, fixture_name: &str) -> SnapshotsFile { - let cases_toml_path = fixture_path.join("snapshots.toml"); - match std::fs::read(&cases_toml_path) { - Ok(content) => toml::from_slice(&content).unwrap_or_else(|err| { - panic!("Failed to parse snapshots.toml for fixture {fixture_name}: {err}") - }), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => SnapshotsFile::default(), - Err(err) => panic!("Failed to read snapshots.toml for fixture {fixture_name}: {err}"), - } -} - -#[expect(clippy::disallowed_types, reason = "Path required for fixture path handling")] -fn run_case( - runtime: &Runtime, - tmpdir: &AbsolutePath, - fixture_path: &std::path::Path, - cases_file: SnapshotsFile, -) -> Result<(), String> { - let fixture_name = fixture_path.file_name().unwrap().to_str().unwrap(); - assert_identifier_like("fixture folder", fixture_name); - let snapshots = snapshot_test::Snapshots::new(fixture_path.join("snapshots")); - run_case_inner(runtime, tmpdir, fixture_path, fixture_name, &snapshots, cases_file) -} - -#[expect( - clippy::disallowed_types, - reason = "Path required for fixture handling; String required by std::fs::read and toml::from_slice" -)] -#[expect(clippy::too_many_lines, reason = "test setup and assertion logic in a single function")] -fn run_case_inner( - runtime: &Runtime, - tmpdir: &AbsolutePath, - fixture_path: &std::path::Path, - fixture_name: &str, - snapshots: &snapshot_test::Snapshots, - cases_file: SnapshotsFile, -) -> Result<(), String> { - // Copy the case directory to a temporary directory to avoid discovering workspace outside of the test case. - let stage_path = tmpdir.join(fixture_name); - copy_dir(fixture_path, &stage_path).unwrap(); - - let (workspace_root, _cwd) = find_workspace_root(&stage_path).unwrap(); - - assert_eq!( - &stage_path, &*workspace_root.path, - "folder '{fixture_name}' should be a workspace root" - ); - - let manifest_dir = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()); - let fake_bin_dir = manifest_dir.join("tests/plan_snapshots/fake-bin"); - let combined_path = - Arc::::from(std::ffi::OsString::from(fake_bin_dir.to_str().unwrap())); - - let plan_envs: FxHashMap, Arc> = [ - (Arc::::from(OsStr::new("PATH")), combined_path), - (Arc::::from(OsStr::new("NO_COLOR")), Arc::::from(OsStr::new("1"))), - ] - .into_iter() - .collect(); - - runtime.block_on(async { - let workspace_root_str = workspace_root.path.as_path().to_str().unwrap(); - let mut owned_config = vite_task_bin::OwnedSessionConfig::default(); - let mut session = Session::init_with( - plan_envs.clone(), - Arc::clone(&workspace_root.path), - owned_config.as_config(), - ) - .unwrap(); - - let task_graph_result = session.ensure_task_graph_loaded().await; - let task_graph = match task_graph_result { - Ok(task_graph) => task_graph, - Err(err) => { - let err_formatted = vite_str::format!("{err:#}"); - let err_str = err_formatted.as_str().cow_replace(workspace_root_str, ""); - let err_str = - if cfg!(windows) { err_str.as_ref().cow_replace('\\', "/") } else { err_str }; - snapshots.check_snapshot("task_graph_load_error.snap", err_str.as_ref())?; - return Ok(()); - } - }; - let task_graph_markdown = - render_task_graph_markdown(task_graph.task_graph(), &workspace_root.path); - snapshots.check_snapshot("task_graph.md", task_graph_markdown.as_str())?; - - for plan in cases_file.plan_cases { - assert_identifier_like("plan case name", plan.name.as_str()); - let snapshot_base = vite_str::format!("query_{}", plan.name); - let compact = plan.compact; - let args_display = - plan.args.iter().map(vite_str::Str::as_str).collect::>().join(" "); - - let cli = match Cli::try_parse_from( - std::iter::once("vt") // dummy program name - .chain(plan.args.iter().map(vite_str::Str::as_str)), - ) { - Ok(ok) => ok, - Err(err) => { - snapshots.check_snapshot( - vite_str::format!("{snapshot_base}.snap").as_str(), - &err.to_string(), - )?; - continue; - } - }; - let Cli::Command(parsed) = cli; - let Command::Run(run_command) = parsed else { - panic!("only `run` commands supported in plan tests") - }; - - // Create a fresh session per plan case with case-specific env vars and cwd. - let mut case_envs = plan_envs.clone(); - for (k, v) in &plan.env { - case_envs - .insert(Arc::from(OsStr::new(k.as_str())), Arc::from(OsStr::new(v.as_str()))); - } - let case_cwd: Arc = workspace_root.path.join(plan.cwd).into(); - let mut case_owned_config = vite_task_bin::OwnedSessionConfig::default(); - let mut case_session = - Session::init_with(case_envs, Arc::clone(&case_cwd), case_owned_config.as_config()) - .unwrap(); - case_session.ensure_task_graph_loaded().await.unwrap(); - - let plan_result = case_session.plan_from_cli_run(case_cwd, run_command).await; - - let plan = match plan_result { - Ok(graph) => graph, - Err(err) => { - // Format the full error chain using anyhow's `{:#}` formatter - // and redact workspace paths for snapshot stability. - let anyhow_err: anyhow::Error = err.into(); - let err_formatted = vite_str::format!("{anyhow_err:#}"); - let err_str = - err_formatted.as_str().cow_replace(workspace_root_str, ""); - let err_str = if cfg!(windows) { - err_str.as_ref().cow_replace('\\', "/") - } else { - err_str - }; - snapshots.check_snapshot( - vite_str::format!("{snapshot_base}.snap").as_str(), - err_str.as_ref(), - )?; - continue; - } - }; - - let comment = vite_str::format!("{args_display}"); - if compact { - let compact_plan = CompactPlan::from_execution_graph(&plan, &workspace_root.path); - snapshots.check_json_snapshot( - snapshot_base.as_str(), - comment.as_str(), - &compact_plan, - )?; - } else { - let plan_json = redact_snapshot(&plan, workspace_root_str); - snapshots.check_json_snapshot( - snapshot_base.as_str(), - comment.as_str(), - &plan_json, - )?; - } - } - Ok(()) - }) -} - -#[expect(clippy::disallowed_types, reason = "Path required for CARGO_MANIFEST_DIR path traversal")] -fn main() { - let tokio_runtime = Arc::new(Runtime::new().unwrap()); - let tmp_dir = tempfile::tempdir().unwrap(); - let tmp_dir_path = AbsolutePathBuf::new(tmp_dir.path().canonicalize().unwrap()).unwrap(); - - let fixtures_dir = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()) - .join("tests/plan_snapshots/fixtures"); - - let mut fixture_paths = std::fs::read_dir(fixtures_dir) - .unwrap() - .map(|entry| entry.unwrap().path()) - .filter(|p| p.file_name().and_then(|n| n.to_str()).is_some_and(|n| !n.starts_with('.'))) - .collect::>(); - fixture_paths.sort(); - - let args = libtest_mimic::Arguments::from_args(); - - let tests: Vec = fixture_paths - .into_iter() - .filter_map(|fixture_path| { - // Parse `snapshots.toml` once. Fixtures whose platform filter - // doesn't match the current build are dropped entirely — if we - // early-returned from the test body instead they'd report as - // "passed" without having run. - let fixture_name = fixture_path.file_name().unwrap().to_str().unwrap().to_owned(); - let cases_file = load_snapshots_file(&fixture_path, &fixture_name); - if !should_run_on_this_platform(cases_file.platform.as_ref()) { - return None; - } - - let tmp_dir_path = tmp_dir_path.clone(); - let runtime = Arc::clone(&tokio_runtime); - Some(libtest_mimic::Trial::test(fixture_name, move || { - run_case(&runtime, &tmp_dir_path, &fixture_path, cases_file).map_err(Into::into) - })) - }) - .collect(); - - libtest_mimic::run(&args, tests).exit(); -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/redact.rs b/crates/vite_task_plan/tests/plan_snapshots/redact.rs deleted file mode 100644 index ee2aa9e29..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/redact.rs +++ /dev/null @@ -1,231 +0,0 @@ -use std::borrow::Cow; - -use cow_utils::CowUtils as _; -use serde::Serialize; -use vite_path::AbsolutePath; -use vite_task_graph::config::DEFAULT_UNTRACKED_ENV; - -fn visit_json(value: &mut serde_json::Value, f: &mut impl FnMut(&mut serde_json::Value)) { - f(value); - match value { - serde_json::Value::Array(arr) => { - for item in arr { - visit_json(item, f); - } - } - serde_json::Value::Object(map) => { - for (_key, val) in map { - visit_json(val, f); - } - } - _ => {} - } -} - -fn redact_string_in_json(value: &mut serde_json::Value, redactions: &[(&str, &str)]) { - visit_json(value, &mut |v| { - if let serde_json::Value::String(s) = v { - redact_string(s, redactions); - } - }); -} - -/// Replace the bare name `pwsh` / `powershell` (post-extension-strip) with a -/// stable placeholder. The two hosts are interchangeable for our purposes, -/// so the snapshot shouldn't care which one Windows CI happens to find. -#[expect( - clippy::disallowed_types, - reason = "String mutation required by serde_json::Value::String which stores a String" -)] -fn redact_powershell_program_name(s: &mut String) { - if s == "pwsh" || s == "powershell" { - *s = "".to_string(); - } -} - -/// Replace an absolute PowerShell-host path (post-extension-strip) with a -/// stable placeholder so the snapshot doesn't pin a particular runner image's -/// system layout (e.g. `C:\Windows\System32\WindowsPowerShell\v1.0\powershell` -/// vs `C:\Program Files\PowerShell\7\pwsh`). -#[expect( - clippy::disallowed_types, - reason = "String mutation required by serde_json::Value::String which stores a String" -)] -fn redact_powershell_program_path(s: &mut String) { - // Short-circuit by scanning for a substring would risk missing Windows - // paths with unexpected casing (e.g. `WINDOWSPOWERSHELL`), so just - // normalize. `cow_replace` and `cow_to_lowercase` both return - // `Cow::Borrowed` when there's nothing to change — typical lowercase - // POSIX paths cost one chained copy via `into_owned`, not a full scan. - let replaced = s.as_str().cow_replace('\\', "/"); - let normalized = replaced.cow_to_lowercase(); - if normalized.ends_with("/powershell") || normalized.ends_with("/pwsh") { - *s = "".to_string(); - } -} - -/// Strip Windows executable extensions (case-insensitive) for cross-platform consistency -#[expect( - clippy::disallowed_types, - reason = "String mutation required by serde_json::Value::String which stores a String" -)] -fn strip_windows_executable_extension(s: &mut String) { - let lower = s.as_str().cow_to_lowercase(); - for ext in [".cmd", ".bat", ".exe", ".com"] { - if lower.ends_with(ext) { - s.truncate(s.len() - ext.len()); - break; - } - } -} - -#[expect( - clippy::disallowed_types, - reason = "String mutation required by serde_json::Value::String which stores a String" -)] -fn redact_string(s: &mut String, redactions: &[(&str, &str)]) { - for (from, to) in redactions { - if let Cow::Owned(mut replaced) = s.as_str().cow_replace(from, to) { - if cfg!(windows) { - // Also replace with backslashes on Windows - replaced = replaced.cow_replace("\\", "/").into_owned(); - } - *s = replaced; - } - } -} - -#[expect(clippy::too_many_lines, reason = "linear sequence of redaction passes")] -pub fn redact_snapshot(value: &impl Serialize, workspace_root: &str) -> serde_json::Value { - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - #[expect(clippy::disallowed_types, reason = "PathBuf needed to build fake-bin path from env")] - let tools_dir_str = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()) - .join("tests/plan_snapshots/fake-bin") - .to_str() - .unwrap() - .to_owned(); - let mut json_value = serde_json::to_value(value).unwrap(); - - // On Windows, paths might use either backslashes or forward slashes. - let workspace_root_forward = workspace_root.cow_replace('\\', "/"); - let manifest_dir_forward = manifest_dir.as_str().cow_replace('\\', "/"); - let tools_dir_forward = tools_dir_str.as_str().cow_replace('\\', "/"); - - redact_string_in_json( - &mut json_value, - &[ - (workspace_root, ""), - (workspace_root_forward.as_ref(), ""), - (tools_dir_str.as_str(), ""), - (tools_dir_forward.as_ref(), ""), - (manifest_dir.as_str(), ""), - (manifest_dir_forward.as_ref(), ""), - ], - ); - - // Normalize PATH separators for cross-platform consistency (Windows uses ; Unix uses :) - visit_json(&mut json_value, &mut |v| { - let serde_json::Value::Object(map) = v else { - return; - }; - if let Some(serde_json::Value::String(path)) = map.get_mut("PATH") - && let Cow::Owned(replaced) = path.as_str().cow_replace(';', ":") - { - *path = replaced; - } - }); - - // Normalize Windows program names and paths by stripping common extensions for cross-platform consistency - // This must happen BEFORE shell redaction so that "cmd.exe" becomes "cmd" before comparison - visit_json(&mut json_value, &mut |v| { - let serde_json::Value::Object(map) = v else { - return; - }; - // Normalize program_name field - if let Some(serde_json::Value::String(program_name)) = map.get_mut("program_name") { - strip_windows_executable_extension(program_name); - redact_powershell_program_name(program_name); - } - // Normalize program_path field - if let Some(serde_json::Value::String(program_path)) = map.get_mut("program_path") { - strip_windows_executable_extension(program_path); - redact_powershell_program_path(program_path); - } - }); - - // Redact shell program and arguments for cross-platform consistency - // Note: os_shell_path still includes .exe because we compare against program_path before extension stripping - let os_shell_path = if cfg!(windows) { "C:\\Windows\\System32\\cmd" } else { "/bin/sh" }; - let os_shell_name = if cfg!(windows) { "cmd" } else { "sh" }; - let os_shell_args: &[&str] = if cfg!(windows) { &["/d", "/s", "/c"] } else { &["-c"] }; - visit_json(&mut json_value, &mut |v| { - if let serde_json::Value::String(s) = v { - // Use case-insensitive comparison on Windows since path casing can vary - let matches_shell_path = if cfg!(windows) { - s.eq_ignore_ascii_case(os_shell_path) - } else { - s == os_shell_path - }; - if matches_shell_path { - *s = "".to_string(); - } else if s == os_shell_name { - *s = "".to_string(); - } - } else if let serde_json::Value::Array(array) = v { - // Check if the beginning of the array matches the shell args - for (n, arg) in os_shell_args.iter().enumerate() { - if !matches!(array.get(n), Some(serde_json::Value::String(s)) if s == *arg) { - return; - } - } - // Redact the shell args - array.drain(0..os_shell_args.len()); - array.insert(0, serde_json::Value::String("".to_string())); - } - }); - - visit_json(&mut json_value, &mut |v| { - let serde_json::Value::Array(array) = v else { - return; - }; - let contains_all_default_untracked_env = - DEFAULT_UNTRACKED_ENV.iter().all(|default_untracked_env| { - array.iter().any(|item| { - if let serde_json::Value::String(s) = item { - s == *default_untracked_env - } else { - false - } - }) - }); - // Remove default untracked envs from snapshots to reduce noise - if contains_all_default_untracked_env { - array.retain(|item| { - if let serde_json::Value::String(s) = item { - !DEFAULT_UNTRACKED_ENV.contains(&s.as_str()) - } else { - true - } - }); - // Sort remaining entries for deterministic snapshots (FxHashSet has non-deterministic order) - array.sort_by(|a, b| { - let a_str = if let serde_json::Value::String(s) = a { s.as_str() } else { "" }; - let b_str = if let serde_json::Value::String(s) = b { s.as_str() } else { "" }; - a_str.cmp(b_str) - }); - array.push(serde_json::Value::String("".to_string())); - } - }); - - json_value -} - -#[expect(clippy::disallowed_types, reason = "Snapshot details are emitted as JSON text")] -pub fn redact_snapshot_pretty_json( - value: &impl Serialize, - workspace_root: &AbsolutePath, -) -> String { - let workspace_root = workspace_root.as_path().to_str().unwrap(); - serde_json::to_string_pretty(&redact_snapshot(value, workspace_root)) - .expect("redacted snapshot must serialize as pretty JSON") -} diff --git a/crates/vite_task_plan/tests/plan_snapshots/task_graph_markdown.rs b/crates/vite_task_plan/tests/plan_snapshots/task_graph_markdown.rs deleted file mode 100644 index 891923c5e..000000000 --- a/crates/vite_task_plan/tests/plan_snapshots/task_graph_markdown.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::collections::BTreeMap; - -use petgraph::visit::{EdgeRef as _, IntoNodeReferences}; -use vite_path::AbsolutePath; -use vite_str::Str; -use vite_task_graph::{TaskGraph, TaskNode}; - -use crate::redact::redact_snapshot_pretty_json; - -type TaskGraphKey<'a> = (&'a AbsolutePath, &'a str); - -struct TaskGraphEntry<'a> { - key: TaskGraphKey<'a>, - node: &'a TaskNode, - neighbors: Vec>, -} - -impl<'a> TaskGraphEntry<'a> { - fn from_graph_node( - task_graph: &'a TaskGraph, - node_index: vite_task_graph::TaskNodeIndex, - node: &'a TaskNode, - ) -> Self { - let mut neighbors = task_graph - .edges(node_index) - .map(|edge| task_graph_key(&task_graph[edge.target()])) - .collect::>(); - neighbors.sort_unstable(); - Self { key: task_graph_key(node), node, neighbors } - } -} - -fn task_graph_key(node: &TaskNode) -> TaskGraphKey<'_> { - (node.task_display.package_path.as_ref(), node.task_display.task_name.as_str()) -} - -fn sorted_task_graph_entries(task_graph: &TaskGraph) -> Vec> { - // Sort by the visible task identity so node IDs stay stable and snapshot - // diffs don't depend on petgraph insertion order. - let mut entries = task_graph - .node_references() - .map(|(node_index, node)| TaskGraphEntry::from_graph_node(task_graph, node_index, node)) - .collect::>(); - entries.sort_unstable_by(|a, b| a.key.cmp(&b.key)); - entries -} - -fn redacted_package_path(path: &AbsolutePath, workspace_root: &AbsolutePath) -> Str { - let relative = path - .strip_prefix(workspace_root) - .expect("package path must strip cleanly from workspace root") - .expect("package path must be under workspace root"); - if relative.as_str().is_empty() { - Str::from("/") - } else { - vite_str::format!("/{}", relative) - } -} - -fn task_graph_label(key: TaskGraphKey<'_>, workspace_root: &AbsolutePath) -> Str { - let package_path = redacted_package_path(key.0, workspace_root); - vite_str::format!("{package_path}#{}", key.1) -} - -#[expect( - clippy::disallowed_types, - reason = "Mermaid label rendering needs a mutable string buffer" -)] -fn push_mermaid_label_text(out: &mut String, text: &str) { - for ch in text.chars() { - match ch { - '"' => out.push_str("#quot;"), - _ => out.push(ch), - } - } -} - -#[expect( - clippy::disallowed_types, - reason = "Markdown snapshot rendering needs a mutable string buffer" -)] -pub fn render_task_graph_markdown(task_graph: &TaskGraph, workspace_root: &AbsolutePath) -> String { - let entries = sorted_task_graph_entries(task_graph); - let mut ids_by_key = BTreeMap::, Str>::new(); - - for (index, entry) in entries.iter().enumerate() { - ids_by_key.insert(entry.key, vite_str::format!("task_{index}")); - } - - let mut out = String::from("# task graph\n\n```mermaid\nflowchart TD\n"); - - for entry in &entries { - let node_id = ids_by_key.get(&entry.key).expect("task graph entry key must have a node id"); - let label = task_graph_label(entry.key, workspace_root); - - out.push_str(" "); - out.push_str(node_id.as_str()); - out.push_str("[\""); - push_mermaid_label_text(&mut out, label.as_str()); - out.push_str("\"]\n"); - - for neighbor in &entry.neighbors { - let target_id = - ids_by_key.get(neighbor).expect("task graph neighbor key must have a node id"); - out.push_str(" "); - out.push_str(node_id.as_str()); - out.push_str(" --> "); - out.push_str(target_id.as_str()); - out.push('\n'); - } - } - - out.push_str("```\n\n"); - - for entry in &entries { - // The heading identifies the task, and the Mermaid arrows capture - // neighbors, so the detail block only needs the task node payload. - let label = task_graph_label(entry.key, workspace_root); - let detail_json = redact_snapshot_pretty_json(entry.node, workspace_root); - out.push_str("## `"); - out.push_str(label.as_str()); - out.push_str("`\n\n```json\n"); - out.push_str(detail_json.as_str()); - out.push_str("\n```\n\n"); - } - - out -} diff --git a/crates/vite_task_server/Cargo.toml b/crates/vite_task_server/Cargo.toml deleted file mode 100644 index 94f1bad8d..000000000 --- a/crates/vite_task_server/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "vite_task_server" -version = "0.0.0" -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -futures = { workspace = true } -native_str = { workspace = true } -rustc-hash = { workspace = true } -tempfile = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true, features = ["io-util", "net", "rt", "macros"] } -tokio-util = { workspace = true } -tracing = { workspace = true } -vite_glob = { workspace = true } -vite_path = { workspace = true } -vite_task_ipc_shared = { workspace = true } -wincode = { workspace = true, features = ["derive"] } - -[target.'cfg(windows)'.dependencies] -uuid = { workspace = true, features = ["v4"] } - -[dev-dependencies] -tokio = { workspace = true, features = ["io-util", "net", "rt", "macros", "time"] } -vite_task_client = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false -test = false diff --git a/crates/vite_task_server/README.md b/crates/vite_task_server/README.md deleted file mode 100644 index cdcbdcddf..000000000 --- a/crates/vite_task_server/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# vite_task_server - -IPC server that runs per task execution, receiving messages from tools (runner-aware tools) and dispatching them to a user-provided handler. diff --git a/crates/vite_task_server/src/lib.rs b/crates/vite_task_server/src/lib.rs deleted file mode 100644 index b33ac877a..000000000 --- a/crates/vite_task_server/src/lib.rs +++ /dev/null @@ -1,503 +0,0 @@ -use std::{ - cell::RefCell, - ffi::{OsStr, OsString}, - io, - sync::Arc, -}; - -use futures::{FutureExt, StreamExt, future::LocalBoxFuture, stream::FuturesUnordered}; -use native_str::NativeStr; -use rustc_hash::{FxHashMap, FxHashSet}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio_util::sync::CancellationToken; -use vite_path::AbsolutePath; -use vite_task_ipc_shared::{ - EnvQuery as IpcEnvQuery, GetEnvResponse, GetEnvsResponse, IPC_ENV_NAME, Request, -}; -use wincode::{SchemaWrite, config::DefaultConfig}; - -pub trait Handler { - fn ignore_input(&mut self, path: &Arc); - fn ignore_output(&mut self, path: &Arc); - fn disable_cache(&mut self); - fn get_env(&mut self, name: &OsStr, tracked: bool) -> Option>; - /// Returns the subset of the env map whose names match `query`. - /// - /// # Errors - /// - /// Returns an error if a glob query fails to parse. - fn get_envs( - &mut self, - query: &IpcEnvQuery<'_>, - tracked: bool, - ) -> Result, Arc>, vite_glob::env::EnvGlobError>; -} - -/// A protocol-level failure observed while servicing a client. -/// -/// The driver retains only the first such error across all clients, then -/// completes gracefully (existing clients drain, new connections are rejected). -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("failed to read message from the task")] - ReadFrame(#[source] io::Error), - - #[error("invalid message from the task")] - InvalidRequest(#[source] wincode::ReadError), - - #[error("non-absolute path from the task: {path:?}")] - NonAbsolutePath { path: OsString }, - - #[error("invalid glob pattern from the task: {:?}", .0.pattern)] - InvalidGlob(Box), - - #[error("failed to send response to the task")] - WriteResponse(#[source] io::Error), -} - -/// Payload for [`Error::InvalidGlob`]. Boxed so the `Error` enum stays small. -#[derive(Debug)] -pub struct InvalidGlob { - pub pattern: Box, - pub source: vite_glob::env::EnvGlobError, -} - -/// A [`Handler`] that records cache-relevant reports and resolves env requests -/// against provided envs. -/// -/// Call [`Recorder::into_reports`] after the driver future completes to -/// recover the collected [`Reports`]. -pub struct Recorder { - ignored_inputs: FxHashSet>, - ignored_outputs: FxHashSet>, - cache_disabled: bool, - tracked_get_env: FxHashMap, Option>>, - tracked_get_envs: FxHashMap, - /// The envs `get_env` resolves against. The runner supplies these for the - /// spawned task; the server never re-reads the live process env. - envs: Arc, Arc>>, -} - -/// Owned env query key recorded for tracked `get_envs` calls. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum EnvQuery { - Glob(Arc), - Prefix(Arc), -} - -/// A record of a tracked env query made via `get_envs`. -/// -/// `matches` is captured on the first call and reused on repeat queries; the -/// server's env map is immutable for a task's lifetime. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EnvQueryRecord { - pub matches: FxHashMap, Arc>, -} - -/// The data collected by a [`Recorder`] over the server's lifetime. -#[derive(Debug, Default)] -pub struct Reports { - pub ignored_inputs: FxHashSet>, - pub ignored_outputs: FxHashSet>, - pub cache_disabled: bool, - pub tracked_get_env: FxHashMap, Option>>, - pub tracked_get_envs: FxHashMap, -} - -impl Recorder { - #[must_use] - pub fn new(envs: Arc, Arc>>) -> Self { - Self { - ignored_inputs: FxHashSet::default(), - ignored_outputs: FxHashSet::default(), - cache_disabled: false, - tracked_get_env: FxHashMap::default(), - tracked_get_envs: FxHashMap::default(), - envs, - } - } - - #[must_use] - pub fn into_reports(self) -> Reports { - Reports { - ignored_inputs: self.ignored_inputs, - ignored_outputs: self.ignored_outputs, - cache_disabled: self.cache_disabled, - tracked_get_env: self.tracked_get_env, - tracked_get_envs: self.tracked_get_envs, - } - } -} - -impl Handler for Recorder { - fn ignore_input(&mut self, path: &Arc) { - self.ignored_inputs.insert(Arc::clone(path)); - } - - fn ignore_output(&mut self, path: &Arc) { - self.ignored_outputs.insert(Arc::clone(path)); - } - - fn disable_cache(&mut self) { - self.cache_disabled = true; - } - - fn get_env(&mut self, name: &OsStr, tracked: bool) -> Option> { - let value = self.envs.get(name).cloned(); - if tracked { - self.tracked_get_env.entry(name.into()).or_insert_with(|| value.clone()); - } - value - } - - fn get_envs( - &mut self, - query: &IpcEnvQuery<'_>, - tracked: bool, - ) -> Result, Arc>, vite_glob::env::EnvGlobError> { - let key = match query { - IpcEnvQuery::Glob(pattern) => EnvQuery::Glob(Arc::from(*pattern)), - IpcEnvQuery::Prefix(prefix) => EnvQuery::Prefix(Arc::from(*prefix)), - }; - if let Some(existing) = self.tracked_get_envs.get(&key) { - return Ok(existing.matches.clone()); - } - let matches: FxHashMap, Arc> = match query { - IpcEnvQuery::Glob(pattern) => { - let glob = vite_glob::env::EnvGlob::new(pattern)?; - self.envs - .iter() - .filter_map(|(name, value)| { - let name_str = name.to_str()?; - if glob.is_match(name_str) { - Some((Arc::clone(name), Arc::clone(value))) - } else { - None - } - }) - .collect() - } - IpcEnvQuery::Prefix(prefix) => self - .envs - .iter() - .filter_map(|(name, value)| { - let name_str = name.to_str()?; - if env_name_starts_with(name_str, prefix) { - Some((Arc::clone(name), Arc::clone(value))) - } else { - None - } - }) - .collect(), - }; - if tracked { - self.tracked_get_envs.insert(key, EnvQueryRecord { matches: matches.clone() }); - } - Ok(matches) - } -} - -#[cfg(not(windows))] -fn env_name_starts_with(name: &str, prefix: &str) -> bool { - name.starts_with(prefix) -} - -#[cfg(windows)] -fn env_name_starts_with(name: &str, prefix: &str) -> bool { - let mut name_chars = name.chars(); - for prefix_char in prefix.chars() { - let Some(name_char) = name_chars.next() else { - return false; - }; - if !name_char.eq_ignore_ascii_case(&prefix_char) { - return false; - } - } - true -} - -/// Handle to a running IPC server. -/// -/// `driver` must be polled to accept clients and handle messages. It resolves -/// only after [`StopAccepting::signal`] has been called AND all in-flight -/// per-client tasks have drained, returning the owned handler. -/// -/// The driver resolves to `Err(Error)` if any client triggered a protocol -/// violation (see [`Error`]). The first such error is retained; subsequent -/// errors during drain are discarded. On `Err`, the handler is not returned. -/// -/// Dropping `driver` before it resolves tears everything down immediately — -/// listener closed, per-client tasks cancelled, handler discarded. -pub struct ServerHandle<'h, H> { - pub driver: LocalBoxFuture<'h, Result>, - pub stop_accepting: StopAccepting, -} - -/// Signal that tells the server to stop accepting new clients. Existing -/// clients continue until they naturally close the connection; the driver -/// future resolves once that drain completes. -/// -/// [`signal`](Self::signal) takes `&self` and the underlying cancellation -/// is idempotent, so calling it twice or from a shared borrow is safe. -pub struct StopAccepting { - token: CancellationToken, -} - -impl StopAccepting { - /// A no-op `StopAccepting` not bound to any running server. Signalling it - /// is a no-op. Useful for placeholder paths where the runner hasn't wired - /// the server in yet but still needs a value of this type. - #[must_use] - pub fn noop() -> Self { - Self { token: CancellationToken::new() } - } - - pub fn signal(&self) { - self.token.cancel(); - } -} - -/// Starts an IPC server. -/// -/// Returns the env entries that a child process must inherit to find and -/// connect to this server, plus a handle bundling the driver future and the -/// `StopAccepting` signal. See [`ServerHandle`] for driver semantics. -/// -/// # Errors -/// -/// Returns an error if creating the listener fails (on Unix, this includes -/// creating the temp socket path). -pub fn serve<'h, H: Handler + 'h>( - handler: H, -) -> io::Result<(impl Iterator, ServerHandle<'h, H>)> { - let stop_token = CancellationToken::new(); - let (name, bound) = bind_listener()?; - - let run_stop = stop_token.clone(); - let driver = async move { - // Multiple per-client futures coexist inside `FuturesUnordered` and each - // calls `&mut self` handler methods. `RefCell` provides the interior - // mutability that makes these shared-access method calls compile; at - // runtime the `borrow_mut()` never conflicts because we're on a - // single-threaded runtime and handler methods are synchronous (no - // awaits, so no borrow spans a yield point). - let handler = RefCell::new(handler); - let first_err = run(bound, &handler, run_stop).await; - first_err.map_or_else(|| Ok(handler.into_inner()), Err) - } - .boxed_local(); - - Ok(( - std::iter::once((OsStr::new(IPC_ENV_NAME), name)), - ServerHandle { driver, stop_accepting: StopAccepting { token: stop_token } }, - )) -} - -#[cfg(unix)] -type Stream = tokio::net::UnixStream; -#[cfg(windows)] -type Stream = tokio::net::windows::named_pipe::NamedPipeServer; - -/// The bound listener for the IPC server. -/// -/// Unix: a Tokio [`UnixListener`](tokio::net::UnixListener) bound inside a -/// [`NamedTempFile`](tempfile::NamedTempFile) so its socket file is unlinked -/// on `Drop`. Windows: a single named-pipe instance that is created up front -/// and replaced on each `accept` (a new pipe instance must be created before -/// the previous one is handed to the client, otherwise concurrent connect -/// attempts race for it). -#[cfg(unix)] -struct Bound { - file: tempfile::NamedTempFile, -} - -#[cfg(windows)] -struct Bound { - pipe_name: OsString, - pending: tokio::net::windows::named_pipe::NamedPipeServer, -} - -#[cfg(unix)] -fn bind_listener() -> io::Result<(OsString, Bound)> { - // `make` lets us bind the socket directly to the path tempfile picks; the - // closure is responsible for creating the file (`UnixListener::bind` does). - // The `NamedTempFile` wrapper unlinks the socket path on `Drop`. - let file = tempfile::Builder::new() - .prefix("vite_task_ipc_") - .make(|path| tokio::net::UnixListener::bind(path))?; - let name = file.path().as_os_str().to_owned(); - Ok((name, Bound { file })) -} - -#[cfg(windows)] -fn bind_listener() -> io::Result<(OsString, Bound)> { - use tokio::net::windows::named_pipe::ServerOptions; - - #[expect( - clippy::disallowed_macros, - reason = "pipe name always exceeds Str inline capacity; format! is the simplest construction" - )] - let pipe_name = OsString::from(format!(r"\\.\pipe\vite_task_ipc_{}", uuid::Uuid::new_v4())); - let pending = ServerOptions::new().first_pipe_instance(true).create(&pipe_name)?; - Ok((pipe_name.clone(), Bound { pipe_name, pending })) -} - -impl Bound { - #[cfg(unix)] - #[expect( - clippy::needless_pass_by_ref_mut, - reason = "Windows variant requires &mut self to swap pending instance; keep the signature uniform across cfgs so `run` can call it identically." - )] - async fn accept(&mut self) -> io::Result { - let (stream, _addr) = self.file.as_file().accept().await?; - Ok(stream) - } - - #[cfg(windows)] - async fn accept(&mut self) -> io::Result { - use tokio::net::windows::named_pipe::ServerOptions; - - // Wait for the next client to connect to the currently-pending - // instance, then immediately create a fresh instance to listen for the - // connection after that. Creating the next instance before yielding the - // accepted one ensures no client gets `ERROR_PIPE_BUSY` during the - // handoff. - self.pending.connect().await?; - let next = ServerOptions::new().create(&self.pipe_name)?; - Ok(std::mem::replace(&mut self.pending, next)) - } -} - -async fn run( - mut bound: Bound, - handler: &RefCell, - shutdown: CancellationToken, -) -> Option { - let mut clients = FuturesUnordered::new(); - let mut first_err: Option = None; - - // Accept phase: accept new clients until shutdown fires. - loop { - tokio::select! { - () = shutdown.cancelled() => break, - accept_result = bound.accept() => { - match accept_result { - Ok(stream) => { - clients.push(handle_client(stream, handler).boxed_local()); - } - Err(err) => { - tracing::warn!(?err, "vite_task_server: accept failed"); - } - } - } - Some(result) = clients.next(), if !clients.is_empty() => { - if let Err(err) = result - && first_err.is_none() - { - first_err = Some(err); - shutdown.cancel(); - } - } - } - } - - // Stop accepting: drop the listener (and on Unix unlink the socket file). - // Existing client streams continue to work. - drop(bound); - - // Drain phase: wait for all in-flight per-client tasks to finish. - while let Some(result) = clients.next().await { - if let Err(err) = result - && first_err.is_none() - { - first_err = Some(err); - } - } - - first_err -} - -async fn handle_client(mut stream: Stream, handler: &RefCell) -> Result<(), Error> { - let mut buf = Vec::new(); - loop { - match read_frame(&mut stream, &mut buf).await { - Ok(()) => {} - Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => return Ok(()), - Err(err) => return Err(Error::ReadFrame(err)), - } - - let request: Request<'_> = - wincode::deserialize_exact(&buf).map_err(Error::InvalidRequest)?; - - // Fire-and-forget branches (`IgnoreInput`, `IgnoreOutput`, `DisableCache`) - // intentionally write no response. Nothing in the runner observes - // individual IPC events live; the recorded set is collected after - // this driver drains. See `Request` in `vite_task_ipc_shared` for - // the rationale. - match request { - Request::IgnoreInput(ns) => { - let path = native_str_to_abs_path(ns)?; - handler.borrow_mut().ignore_input(&path); - } - Request::IgnoreOutput(ns) => { - let path = native_str_to_abs_path(ns)?; - handler.borrow_mut().ignore_output(&path); - } - Request::DisableCache => { - handler.borrow_mut().disable_cache(); - } - Request::GetEnv { name, tracked } => { - let value = handler.borrow_mut().get_env(name.to_cow_os_str().as_ref(), tracked); - let response = GetEnvResponse { env_value: value.as_deref().map(Into::into) }; - write_response(&mut stream, &response).await.map_err(Error::WriteResponse)?; - } - Request::GetEnvs { query, tracked } => { - let matches = handler.borrow_mut().get_envs(&query, tracked).map_err(|source| { - let pattern = match query { - IpcEnvQuery::Glob(pattern) => pattern, - IpcEnvQuery::Prefix(prefix) => prefix, - }; - Error::InvalidGlob(Box::new(InvalidGlob { - pattern: Box::::from(pattern), - source, - })) - })?; - let response = GetEnvsResponse { - entries: matches.iter().map(|(k, v)| ((&**k).into(), (&**v).into())).collect(), - }; - write_response(&mut stream, &response).await.map_err(Error::WriteResponse)?; - } - } - } -} - -fn native_str_to_abs_path(ns: &NativeStr) -> Result, Error> { - let os_str = ns.to_cow_os_str(); - AbsolutePath::new(&*os_str) - .map(Arc::from) - .ok_or_else(|| Error::NonAbsolutePath { path: os_str.into_owned() }) -} - -async fn read_frame(stream: &mut Stream, buf: &mut Vec) -> io::Result<()> { - let mut len_bytes = [0u8; 4]; - stream.read_exact(&mut len_bytes).await?; - let len = u32::from_le_bytes(len_bytes) as usize; - buf.clear(); - buf.resize(len, 0); - stream.read_exact(buf).await?; - Ok(()) -} - -async fn write_response(stream: &mut Stream, response: &T) -> io::Result<()> -where - T: SchemaWrite + ?Sized, -{ - let bytes = wincode::serialize(response) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; - let len = u32::try_from(bytes.len()) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "response too large"))?; - stream.write_all(&len.to_le_bytes()).await?; - stream.write_all(&bytes).await?; - stream.flush().await?; - Ok(()) -} diff --git a/crates/vite_task_server/tests/integration.rs b/crates/vite_task_server/tests/integration.rs deleted file mode 100644 index e22581812..000000000 --- a/crates/vite_task_server/tests/integration.rs +++ /dev/null @@ -1,349 +0,0 @@ -use std::{ - ffi::{OsStr, OsString}, - io::{self, Read, Write}, - sync::Arc, - thread, -}; - -use native_str::NativeStr; - -#[cfg(unix)] -type RawStream = std::os::unix::net::UnixStream; -#[cfg(windows)] -type RawStream = std::fs::File; -use rustc_hash::FxHashMap; -use tokio::runtime::Builder; -use vite_task_client::{Client, GetEnvsQuery}; -use vite_task_ipc_shared::{GetEnvResponse, Request}; -use vite_task_server::{EnvQuery, Error, Recorder, Reports, ServerHandle, serve}; - -fn env_map(pairs: &[(&str, &str)]) -> FxHashMap, Arc> { - pairs - .iter() - .map(|(k, v)| (Arc::::from(OsStr::new(k)), Arc::::from(OsStr::new(v)))) - .collect() -} - -fn run_with_server( - envs: FxHashMap, Arc>, - client_work: F, -) -> Result -where - F: FnOnce(Vec<(&'static OsStr, OsString)>) + Send + 'static, -{ - let recorder = Recorder::new(Arc::new(envs)); - - let rt = Builder::new_current_thread().enable_all().build().unwrap(); - rt.block_on(async move { - let (envs, ServerHandle { driver, stop_accepting }) = serve(recorder).expect("bind server"); - let envs: Vec<_> = envs.collect(); - - let client = async move { - tokio::task::spawn_blocking(move || client_work(envs)) - .await - .expect("client work panicked"); - stop_accepting.signal(); - }; - - let (result, ()) = tokio::join!(driver, client); - result.map(Recorder::into_reports) - }) -} - -fn connect(envs: &[(&'static OsStr, OsString)]) -> Client { - Client::from_envs(envs.iter().map(|(k, v)| (k, v))) - .expect("connect") - .expect("serve should yield an IPC env") -} - -/// Force a round-trip so the server has definitely processed every prior -/// fire-and-forget frame on this connection: frames on a single stream are -/// read sequentially, so once the server answers a `get_env` everything -/// before it must already have been dispatched to the handler. -fn flush(client: &Client) { - let _ = client.get_env(OsStr::new("__VP_TEST_FLUSH__"), false).unwrap(); -} - -#[cfg(unix)] -fn connect_raw(name: &OsStr) -> RawStream { - std::os::unix::net::UnixStream::connect(name).expect("connect raw") -} - -#[cfg(windows)] -fn connect_raw(name: &OsStr) -> RawStream { - std::fs::OpenOptions::new().read(true).write(true).open(name).expect("connect raw") -} - -fn send_frame(stream: &mut RawStream, request: &Request<'_>) { - let bytes = wincode::serialize(request).expect("serialize"); - let len = u32::try_from(bytes.len()).expect("frame length fits u32"); - stream.write_all(&len.to_le_bytes()).expect("write len"); - stream.write_all(&bytes).expect("write body"); - stream.flush().expect("flush"); -} - -fn recv_get_env_response(stream: &mut RawStream) -> GetEnvResponse { - let mut len_bytes = [0u8; 4]; - stream.read_exact(&mut len_bytes).expect("read len"); - let len = u32::from_le_bytes(len_bytes) as usize; - let mut buf = vec![0; len]; - stream.read_exact(&mut buf).expect("read body"); - wincode::deserialize_exact(&buf).expect("deserialize response") -} - -#[test] -fn single_client_fire_and_forget() { - #[cfg(unix)] - let (in_path, out_path) = ("/tmp/in.txt", "/tmp/out.txt"); - #[cfg(windows)] - let (in_path, out_path) = (r"C:\tmp\in.txt", r"C:\tmp\out.txt"); - - let reports = run_with_server(env_map(&[]), |envs| { - let client = connect(&envs); - client.ignore_input(OsStr::new(in_path)).unwrap(); - client.ignore_output(OsStr::new(out_path)).unwrap(); - // Temporary workaround: the client currently ignores disableCache so - // tools cannot opt out at configuration time before they perform the - // operation that actually makes a task uncacheable. - client.disable_cache().unwrap(); - flush(&client); - }) - .expect("driver returned error"); - - let inputs: Vec<_> = reports.ignored_inputs.iter().map(|p| p.as_path().as_os_str()).collect(); - let outputs: Vec<_> = reports.ignored_outputs.iter().map(|p| p.as_path().as_os_str()).collect(); - assert_eq!(inputs, vec![OsStr::new(in_path)]); - assert_eq!(outputs, vec![OsStr::new(out_path)]); - assert!(!reports.cache_disabled); -} - -#[test] -fn raw_disable_cache_request_disables_cache() { - let reports = run_with_server(env_map(&[]), |envs| { - let name = &envs[0].1; - let mut stream = connect_raw(name); - send_frame(&mut stream, &Request::DisableCache); - let flush_name: Box = OsStr::new("__VP_TEST_FLUSH__").into(); - send_frame(&mut stream, &Request::GetEnv { name: &flush_name, tracked: false }); - let _ = recv_get_env_response(&mut stream); - }) - .expect("driver returned error"); - - assert!(reports.cache_disabled); -} - -#[test] -fn get_env_found_and_not_found() { - let reports = run_with_server(env_map(&[("NODE_ENV", "production")]), |envs| { - let client = connect(&envs); - let present = client.get_env(OsStr::new("NODE_ENV"), true).unwrap(); - assert_eq!(present.as_deref(), Some(OsStr::new("production"))); - let missing = client.get_env(OsStr::new("MISSING"), false).unwrap(); - assert!(missing.is_none()); - }) - .expect("driver returned error"); - - assert!(!reports.cache_disabled); - let node = reports.tracked_get_env.get(OsStr::new("NODE_ENV")).expect("NODE_ENV recorded"); - assert_eq!(node.as_deref(), Some(OsStr::new("production"))); - - assert!( - !reports.tracked_get_env.contains_key(OsStr::new("MISSING")), - "untracked getEnv calls are not recorded" - ); -} - -#[test] -fn get_env_untracked_then_tracked_records_once() { - let reports = run_with_server(env_map(&[("NODE_ENV", "production")]), |envs| { - let client = connect(&envs); - let a = client.get_env(OsStr::new("NODE_ENV"), false).unwrap(); - let b = client.get_env(OsStr::new("NODE_ENV"), true).unwrap(); - let c = client.get_env(OsStr::new("NODE_ENV"), false).unwrap(); - for v in [a, b, c] { - assert_eq!(v.as_deref(), Some(OsStr::new("production"))); - } - }) - .expect("driver returned error"); - - let node = reports.tracked_get_env.get(OsStr::new("NODE_ENV")).expect("recorded"); - assert_eq!(node.as_deref(), Some(OsStr::new("production"))); -} - -#[test] -fn concurrent_clients() { - #[cfg(unix)] - let paths = ["/tmp/worker_0", "/tmp/worker_1", "/tmp/worker_2", "/tmp/worker_3"]; - #[cfg(windows)] - let paths = [r"C:\tmp\worker_0", r"C:\tmp\worker_1", r"C:\tmp\worker_2", r"C:\tmp\worker_3"]; - - let reports = run_with_server(env_map(&[("SHARED", "value")]), move |envs| { - let threads: Vec<_> = paths - .iter() - .map(|path| { - let envs = envs.clone(); - let path = *path; - thread::spawn(move || { - let client = connect(&envs); - client.ignore_input(OsStr::new(path)).unwrap(); - let value = client.get_env(OsStr::new("SHARED"), true).unwrap(); - assert_eq!(value.as_deref(), Some(OsStr::new("value"))); - }) - }) - .collect(); - for t in threads { - t.join().unwrap(); - } - }) - .expect("driver returned error"); - - assert!(!reports.cache_disabled); - assert_eq!(reports.ignored_inputs.len(), 4); - let shared = reports.tracked_get_env.get(OsStr::new("SHARED")).expect("recorded"); - assert_eq!(shared.as_deref(), Some(OsStr::new("value"))); -} - -#[test] -fn relative_input_joined_with_cwd() { - let cwd = vite_path::current_dir().expect("cwd"); - let expected = cwd.as_path().join("sub/file.txt"); - - let reports = run_with_server(env_map(&[]), |envs| { - let client = connect(&envs); - client.ignore_input(OsStr::new("sub/file.txt")).unwrap(); - flush(&client); - }) - .expect("driver returned error"); - - let inputs: Vec<_> = reports.ignored_inputs.iter().map(|p| p.as_path().as_os_str()).collect(); - assert_eq!(inputs, vec![expected.as_os_str()]); -} - -#[test] -fn server_returns_error_on_non_absolute_path() { - let err = run_with_server(env_map(&[]), |envs| { - let name = &envs[0].1; - let mut stream = connect_raw(name); - - let ns: Box = OsStr::new("relative/path").into(); - send_frame(&mut stream, &Request::IgnoreInput(&ns)); - - let mut buf = [0u8; 1]; - let read_err = stream.read_exact(&mut buf).expect_err("server should close connection"); - assert_eq!(read_err.kind(), io::ErrorKind::UnexpectedEof); - }) - .expect_err("driver should surface the protocol error"); - - match err { - Error::NonAbsolutePath { path } => { - assert_eq!(path, OsStr::new("relative/path")); - } - other => panic!("unexpected error variant: {other:?}"), - } -} - -#[test] -fn get_envs_returns_matching_entries() { - let reports = run_with_server( - env_map(&[("PROBE_A", "alpha"), ("PROBE_B", "beta"), ("UNRELATED", "noise")]), - |envs| { - let client = connect(&envs); - let matches = client.get_envs(GetEnvsQuery::Glob("PROBE_*"), true).unwrap(); - assert_eq!(matches.len(), 2); - assert_eq!( - matches.get(OsStr::new("PROBE_A")).map(AsRef::as_ref), - Some(OsStr::new("alpha")) - ); - assert_eq!( - matches.get(OsStr::new("PROBE_B")).map(AsRef::as_ref), - Some(OsStr::new("beta")) - ); - assert!(!matches.contains_key(OsStr::new("UNRELATED"))); - }, - ) - .expect("driver returned error"); - - assert!(!reports.cache_disabled); - let glob = - reports.tracked_get_envs.get(&EnvQuery::Glob(Arc::from("PROBE_*"))).expect("glob recorded"); - assert_eq!(glob.matches.len(), 2); -} - -#[test] -fn get_envs_prefix_treats_star_literally() { - let reports = run_with_server( - env_map(&[ - ("PROBE_*A", "literal"), - ("PROBE_XA", "wildcard if interpreted as glob"), - ("PROBE_A", "also wildcard if interpreted as glob"), - ]), - |envs| { - let client = connect(&envs); - let matches = client.get_envs(GetEnvsQuery::Prefix("PROBE_*"), true).unwrap(); - assert_eq!(matches.len(), 1); - assert_eq!( - matches.get(OsStr::new("PROBE_*A")).map(AsRef::as_ref), - Some(OsStr::new("literal")) - ); - }, - ) - .expect("driver returned error"); - - let prefix = reports - .tracked_get_envs - .get(&EnvQuery::Prefix(Arc::from("PROBE_*"))) - .expect("prefix query recorded"); - assert_eq!(prefix.matches.len(), 1); -} - -#[test] -fn get_envs_empty_match_set_is_returned() { - let reports = run_with_server(env_map(&[("FOO", "x"), ("BAR", "y")]), |envs| { - let client = connect(&envs); - let matches = client.get_envs(GetEnvsQuery::Glob("PROBE_*"), false).unwrap(); - assert!(matches.is_empty()); - }) - .expect("driver returned error"); - - assert!(!reports.cache_disabled); - assert!( - !reports.tracked_get_envs.contains_key(&EnvQuery::Glob(Arc::from("PROBE_*"))), - "untracked getEnvs calls are not recorded" - ); -} - -#[test] -fn get_envs_untracked_then_tracked_records_once() { - let reports = run_with_server(env_map(&[("PROBE_A", "alpha")]), |envs| { - let client = connect(&envs); - let first = client.get_envs(GetEnvsQuery::Glob("PROBE_*"), false).unwrap(); - let second = client.get_envs(GetEnvsQuery::Glob("PROBE_*"), true).unwrap(); - let third = client.get_envs(GetEnvsQuery::Glob("PROBE_*"), false).unwrap(); - assert_eq!(first, second); - assert_eq!(second, third); - }) - .expect("driver returned error"); - - let glob = - reports.tracked_get_envs.get(&EnvQuery::Glob(Arc::from("PROBE_*"))).expect("glob recorded"); - assert_eq!(glob.matches.len(), 1); -} - -#[test] -fn get_envs_invalid_pattern_surfaces_error() { - let err = run_with_server(env_map(&[]), |envs| { - let client = connect(&envs); - let send_err = client - .get_envs(GetEnvsQuery::Glob("{unclosed"), true) - .expect_err("server should reject"); - assert_eq!(send_err.kind(), io::ErrorKind::UnexpectedEof); - }) - .expect_err("driver should surface the protocol error"); - - match err { - Error::InvalidGlob(inner) => { - assert_eq!(inner.pattern.as_ref(), "{unclosed"); - } - other => panic!("unexpected error variant: {other:?}"), - } -} diff --git a/crates/vite_tui/Cargo.toml b/crates/vite_tui/Cargo.toml deleted file mode 100644 index 564c7eb90..000000000 --- a/crates/vite_tui/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "vite_tui" -version = "0.0.0" -edition.workspace = true -include = ["/src"] -license.workspace = true -publish = false -readme = "README.md" -rust-version.workspace = true - -[lints] -workspace = true - -[lib] -doctest = false -test = false - -[dependencies] -color-eyre = { workspace = true } -crossterm = { workspace = true } -directories = { workspace = true } -futures = { workspace = true } -portable-pty = { workspace = true } -ratatui = { workspace = true } -rustc-hash = { workspace = true } -tokio = { workspace = true, features = ["full"] } -tokio-util = { workspace = true } -tui-term = { workspace = true } - -tracing = { workspace = true } -tracing-error = { workspace = true } -tracing-subscriber = { workspace = true } diff --git a/crates/vite_tui/README.md b/crates/vite_tui/README.md deleted file mode 100644 index c070f5cd5..000000000 --- a/crates/vite_tui/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Vite TUI - -## Development - -```bash -RUST_LOG=DEBUG watchexec 'cargo run' -``` - -In another terminal: - -```bash -tail -f ~/Library/Application\ Support/dev.voidzero.vite_tui/vite_tui.log -``` diff --git a/crates/vite_tui/src/action.rs b/crates/vite_tui/src/action.rs deleted file mode 100644 index e331536ef..000000000 --- a/crates/vite_tui/src/action.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[expect(unused, reason = "TUI actions defined for future use")] -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Action { - Tick, - Render, - Resize(u16, u16), - Suspend, - Resume, - Quit, - ClearScreen, - Error(Box), - Task { task: Box, bytes: Box<[u8]> }, - Up, - Down, - SelectTask(usize), -} diff --git a/crates/vite_tui/src/app.rs b/crates/vite_tui/src/app.rs deleted file mode 100644 index 31b328208..000000000 --- a/crates/vite_tui/src/app.rs +++ /dev/null @@ -1,285 +0,0 @@ -use color_eyre::Result; -use crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind}; -use ratatui::{ - Frame, - layout::{Constraint, Layout}, - prelude::Rect, -}; -use rustc_hash::FxHashMap; -use tokio::sync::mpsc; - -use crate::{ - action::Action, - components::{Component, TasksList, TasksPane}, - tui::{Event, Tui}, -}; - -pub struct App { - should_quit: bool, - should_suspend: bool, - last_tick_key_events: Vec, - action_tx: mpsc::UnboundedSender, - action_rx: mpsc::UnboundedReceiver, - - tasks_list: TasksList, - #[expect( - clippy::disallowed_types, - reason = "vite_tui is a standalone TUI app, not using vite_str" - )] - tasks_pane: FxHashMap, - left_panel_area: Rect, -} - -impl Default for App { - fn default() -> Self { - Self::new() - } -} - -impl App { - #[must_use] - pub fn new() -> Self { - let tasks = vec!["top".to_string(), "df".to_string()]; - let (action_tx, action_rx) = mpsc::unbounded_channel(); - let tasks_pane = tasks.iter().map(|task| (task.clone(), TasksPane::new())).collect(); - Self { - should_quit: false, - should_suspend: false, - last_tick_key_events: Vec::new(), - action_tx, - action_rx, - tasks_list: TasksList::new(tasks), - tasks_pane, - left_panel_area: Rect::default(), - } - } - - /// # Errors - /// # Panics - pub async fn run(&mut self) -> Result<()> { - let mut tui = Tui::new()?.mouse(true).tick_rate(10.0).frame_rate(60.0); - tui.enter()?; - - // for component in &mut self.components { - // component.register_action_handler(self.action_tx.clone())?; - // } - // for component in self.components.iter_mut() { - // component.register_config_handler(self.config.clone())?; - // } - let size = tui.size()?; - for pane in self.tasks_pane.values_mut() { - pane.init(size)?; - } - self.tasks_list.init(size)?; - - for task in self.tasks_pane.keys() { - let pty_system = portable_pty::native_pty_system(); - let cmd = portable_pty::CommandBuilder::new(task); - let pair = pty_system - .openpty(portable_pty::PtySize { - rows: size.height, - cols: size.width, - pixel_width: 0, - pixel_height: 0, - }) - .unwrap(); - - // Wait for the child to complete - tokio::spawn(async move { - let mut child = pair.slave.spawn_command(cmd).unwrap(); - let _child_exit_status = child.wait().unwrap(); - drop(pair.slave); - }); - - let mut reader = pair.master.try_clone_reader().unwrap(); - - tokio::spawn({ - let action_tx = self.action_tx.clone(); - let task = task.clone(); - async move { - // Consume the output from the child - // Can't read the full buffer, since that would wait for EOF - let mut buf = [0u8; 8192]; - let mut processed_buf = Vec::new(); - loop { - let size = reader.read(&mut buf).unwrap(); - if size == 0 { - break; - } - if size > 0 { - processed_buf.extend_from_slice(&buf[..size]); - let bytes = processed_buf.iter().copied().collect(); - if action_tx - .send(Action::Task { task: task.clone().into_boxed_str(), bytes }) - .is_err() - { - break; - } - // Clear the processed portion of the buffer - processed_buf.clear(); - } - } - } - }); - - { - // Drop writer on purpose - let _writer = pair.master.take_writer().unwrap(); - } - drop(pair.master); - } - - let action_tx = self.action_tx.clone(); - loop { - self.handle_events(&mut tui).await?; - self.handle_actions(&mut tui)?; - if self.should_suspend { - tui.suspend()?; - action_tx.send(Action::Resume)?; - action_tx.send(Action::ClearScreen)?; - tui.enter()?; - } else if self.should_quit { - tui.stop(); - break; - } - } - - tui.exit()?; - Ok(()) - } - - async fn handle_events(&self, tui: &mut Tui) -> Result<()> { - let Some(event) = tui.next_event().await else { - return Ok(()); - }; - let action_tx = self.action_tx.clone(); - match event { - Event::Quit => action_tx.send(Action::Quit)?, - Event::Tick => action_tx.send(Action::Tick)?, - Event::Render => action_tx.send(Action::Render)?, - Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?, - Event::Key(key) => self.handle_key_event(key)?, - Event::Mouse(mouse) => self.handle_mouse_event(mouse)?, - _ => {} - } - Ok(()) - } - - fn handle_key_event(&self, key: KeyEvent) -> Result<()> { - let action_tx = self.action_tx.clone(); - match key.code { - KeyCode::Char('q') | KeyCode::Esc => { - action_tx.send(Action::Quit)?; - } - KeyCode::Char('k') | KeyCode::Up => { - action_tx.send(Action::Up)?; - } - KeyCode::Char('j') | KeyCode::Down => { - action_tx.send(Action::Down)?; - } - _ => { - // // If the key was not handled as a single key action, - // // then consider it for multi-key combinations. - // self.last_tick_key_events.push(key); - - // // Check for multi-key combinations - // if let Some(action) = keymap.get(&self.last_tick_key_events) { - // info!("Got action: {action:?}"); - // action_tx.send(action.clone())?; - } - } - Ok(()) - } - - fn handle_mouse_event(&self, mouse: MouseEvent) -> Result<()> { - let action_tx = self.action_tx.clone(); - - if mouse.kind == MouseEventKind::Down(MouseButton::Left) { - // Check if click is within the left panel area - if mouse.column >= self.left_panel_area.x - && mouse.column < self.left_panel_area.x + self.left_panel_area.width - && mouse.row >= self.left_panel_area.y - && mouse.row < self.left_panel_area.y + self.left_panel_area.height - { - // Calculate which task was clicked based on row position - // Account for header (1 row) and footer (1 row), so content starts at y+1 - if mouse.row > self.left_panel_area.y - && mouse.row < self.left_panel_area.y + self.left_panel_area.height - 1 - { - let clicked_row = mouse.row - self.left_panel_area.y - 1; // -1 for header - let task_count = self.tasks_list.task_count(); - - if (clicked_row as usize) < task_count { - // Send click action to select the task - action_tx.send(Action::SelectTask(clicked_row as usize))?; - } - } - } - } - - Ok(()) - } - - fn handle_actions(&mut self, tui: &mut Tui) -> Result<()> { - while let Ok(action) = self.action_rx.try_recv() { - if action != Action::Tick && action != Action::Render { - // debug!("{action:?}"); - } - match action { - Action::Tick => { - self.last_tick_key_events.drain(..); - } - Action::Quit => self.should_quit = true, - Action::Suspend => self.should_suspend = true, - Action::Resume => self.should_suspend = false, - Action::ClearScreen => tui.terminal.clear()?, - Action::Resize(w, h) => self.handle_resize(tui, w, h)?, - Action::Render => self.render(tui)?, - Action::Task { task, bytes } => { - if let Some(pane) = self.tasks_pane.get_mut(&*task) { - pane.process(&bytes); - } - } - Action::Up | Action::Down | Action::SelectTask(_) => { - self.tasks_list.update(action)?; - } - Action::Error(_) => {} - } - } - Ok(()) - } - - fn handle_resize(&mut self, tui: &mut Tui, w: u16, h: u16) -> Result<()> { - tui.resize(Rect::new(0, 0, w, h))?; - self.render(tui)?; - Ok(()) - } - - #[expect( - clippy::disallowed_macros, - reason = "vite_tui is a standalone TUI app, not using vite_str" - )] - fn render(&mut self, tui: &mut Tui) -> Result<()> { - tui.draw(|frame| { - if let Err(err) = self.draw(frame) { - let _ = - self.action_tx.send(Action::Error(format!("Failed to draw: {err:?}").into())); - } - })?; - Ok(()) - } - - fn draw(&mut self, frame: &mut Frame<'_>) -> Result<()> { - let [left, right] = - Layout::horizontal([Constraint::Max(20), Constraint::Fill(1)]).areas(frame.area()); - - // Store the left panel area for mouse event handling - self.left_panel_area = left; - - self.tasks_list.draw(frame, left)?; - if let Some(pane) = self.tasks_pane.get_mut(self.tasks_list.selected_task()) { - pane.draw(frame, right)?; - } - Ok(()) - } -} diff --git a/crates/vite_tui/src/components/mod.rs b/crates/vite_tui/src/components/mod.rs deleted file mode 100644 index 7de432c29..000000000 --- a/crates/vite_tui/src/components/mod.rs +++ /dev/null @@ -1,128 +0,0 @@ -mod tasks_list; -mod tasks_pane; - -use color_eyre::Result; -use crossterm::event::{KeyEvent, MouseEvent}; -use ratatui::{ - Frame, - layout::{Rect, Size}, -}; -pub use tasks_list::TasksList; -pub use tasks_pane::TasksPane; -use tokio::sync::mpsc::UnboundedSender; - -use crate::{action::Action, tui::Event}; - -/// `Component` is a trait that represents a visual and interactive element of the user interface. -/// -/// Implementors of this trait can be registered with the main application loop and will be able to -/// receive events, update state, and be rendered on the screen. -#[expect(unused, reason = "component trait methods defined for future use")] -pub trait Component: Send { - /// Register an action handler that can send actions for processing if necessary. - /// - /// # Arguments - /// - /// * `tx` - An unbounded sender that can send actions. - /// - /// # Returns - /// - /// * `Result<()>` - An Ok result or an error. - fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { - let _ = tx; // to appease clippy - Ok(()) - } - // /// Register a configuration handler that provides configuration settings if necessary. - // /// - // /// # Arguments - // /// - // /// * `config` - Configuration settings. - // /// - // /// # Returns - // /// - // /// * `Result<()>` - An Ok result or an error. - // fn register_config_handler(&mut self, config: Config) -> Result<()> { - // let _ = config; // to appease clippy - // Ok(()) - // } - /// Initialize the component with a specified area if necessary. - /// - /// # Arguments - /// - /// * `area` - Rectangular area to initialize the component within. - /// - /// # Returns - /// - /// * `Result<()>` - An Ok result or an error. - fn init(&mut self, area: Size) -> Result<()> { - let _ = area; // to appease clippy - Ok(()) - } - /// Handle incoming events and produce actions if necessary. - /// - /// # Arguments - /// - /// * `event` - An optional event to be processed. - /// - /// # Returns - /// - /// * `Result>` - An action to be processed or none. - fn handle_events(&mut self, event: Option) -> Result> { - let action = match event { - Some(Event::Key(key_event)) => self.handle_key_event(key_event)?, - Some(Event::Mouse(mouse_event)) => self.handle_mouse_event(mouse_event)?, - _ => None, - }; - Ok(action) - } - /// Handle key events and produce actions if necessary. - /// - /// # Arguments - /// - /// * `key` - A key event to be processed. - /// - /// # Returns - /// - /// * `Result>` - An action to be processed or none. - fn handle_key_event(&mut self, key: KeyEvent) -> Result> { - let _ = key; // to appease clippy - Ok(None) - } - /// Handle mouse events and produce actions if necessary. - /// - /// # Arguments - /// - /// * `mouse` - A mouse event to be processed. - /// - /// # Returns - /// - /// * `Result>` - An action to be processed or none. - fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Result> { - let _ = mouse; // to appease clippy - Ok(None) - } - /// Update the state of the component based on a received action. (REQUIRED) - /// - /// # Arguments - /// - /// * `action` - An action that may modify the state of the component. - /// - /// # Returns - /// - /// * `Result>` - An action to be processed or none. - fn update(&mut self, action: Action) -> Result> { - let _ = action; // to appease clippy - Ok(None) - } - /// Render the component on the screen. (REQUIRED) - /// - /// # Arguments - /// - /// * `f` - A frame used for rendering. - /// * `area` - The area in which the component should be drawn. - /// - /// # Returns - /// - /// * `Result<()>` - An Ok result or an error. - fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()>; -} diff --git a/crates/vite_tui/src/components/tasks_list.rs b/crates/vite_tui/src/components/tasks_list.rs deleted file mode 100644 index df2eef85f..000000000 --- a/crates/vite_tui/src/components/tasks_list.rs +++ /dev/null @@ -1,97 +0,0 @@ -use color_eyre::Result; -use ratatui::{ - Frame, - layout::{Constraint, Rect}, - prelude::Size, - style::{Color, Modifier, Style}, - text::Text, - widgets::{Block, Borders, Cell, Row, Table, TableState}, -}; - -use super::{Action, Component}; - -pub struct TasksList { - #[expect( - clippy::disallowed_types, - reason = "vite_tui is a standalone TUI app, not using vite_str" - )] - tasks: Vec, - // States - selection: usize, - state: TableState, -} - -impl TasksList { - #[expect( - clippy::disallowed_types, - reason = "vite_tui is a standalone TUI app, not using vite_str" - )] - pub const fn new(tasks: Vec) -> Self { - Self { state: TableState::new(), selection: 0, tasks } - } - - pub fn selected_task(&self) -> &str { - &self.tasks[self.selection] - } - - pub const fn task_count(&self) -> usize { - self.tasks.len() - } - - const fn select(&mut self, selection: usize) { - self.selection = selection; - self.state.select(Some(selection)); - } - - const fn up(&mut self) { - self.select(if self.selection == 0 { self.tasks.len() - 1 } else { self.selection - 1 }); - } - - const fn down(&mut self) { - self.select(if self.selection == self.tasks.len() - 1 { 0 } else { self.selection + 1 }); - } -} - -impl Component for TasksList { - fn init(&mut self, _area: Size) -> Result<()> { - self.select(0); - Ok(()) - } - - fn update(&mut self, action: Action) -> Result> { - match action { - Action::Up => self.up(), - Action::Down => self.down(), - Action::SelectTask(index) if index < self.tasks.len() => { - self.select(index); - } - _ => {} - } - Ok(None) - } - - fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { - let rows = self.tasks.iter().map(|task| Row::new([task.clone()])); - let widths = [Constraint::Min(15)]; - let table = Table::new(rows, widths) - .row_highlight_style(Style::default().fg(Color::Green)) - .column_spacing(0) - .block(Block::new().borders(Borders::RIGHT)) - .header( - Row::new([Cell::new(Text::styled( - "Tasks", - Style::default().add_modifier(Modifier::DIM), - ))]) - .height(1), - ) - .footer( - Row::new([Cell::new(Text::styled( - "↑ ↓ - Select", - Style::default().add_modifier(Modifier::DIM), - ))]) - .height(1), - ); - frame.render_stateful_widget(table, area, &mut self.state); - Ok(()) - } -} diff --git a/crates/vite_tui/src/components/tasks_pane.rs b/crates/vite_tui/src/components/tasks_pane.rs deleted file mode 100644 index a7ff5f069..000000000 --- a/crates/vite_tui/src/components/tasks_pane.rs +++ /dev/null @@ -1,42 +0,0 @@ -use color_eyre::Result; -use ratatui::{ - Frame, - layout::{Rect, Size}, -}; -use tui_term::{vt100::Parser, widget::PseudoTerminal}; - -use super::Component; - -pub struct TasksPane { - parser: Parser, - output: Vec, -} - -impl TasksPane { - pub fn new() -> Self { - Self { parser: Parser::default(), output: vec![] } - } - - pub fn resize(&mut self, rows: u16, cols: u16) { - self.parser = Parser::new(rows, cols, 0); - } - - pub fn process(&mut self, bytes: &[u8]) { - self.parser.process(bytes); - self.output.extend(bytes); - } -} - -impl Component for TasksPane { - fn init(&mut self, area: Size) -> Result<()> { - self.resize(area.height, area.width); - Ok(()) - } - - fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { - let screen = self.parser.screen(); - let pseudo_term = PseudoTerminal::new(screen); - frame.render_widget(pseudo_term, area); - Ok(()) - } -} diff --git a/crates/vite_tui/src/config.rs b/crates/vite_tui/src/config.rs deleted file mode 100644 index 373d10978..000000000 --- a/crates/vite_tui/src/config.rs +++ /dev/null @@ -1,575 +0,0 @@ -#[expect( - clippy::disallowed_types, - reason = "vite_tui is a standalone TUI app, not using vite_path/vite_str" -)] -use std::{env, path::PathBuf, sync::LazyLock}; - -// use derive_deref::{Deref, DerefMut}; -use directories::ProjectDirs; -// use serde::{Deserialize, de::Deserializer}; - -// const CONFIG: &str = include_str!("../.config/config.json5"); - -// #[derive(Clone, Debug, Deserialize, Default)] -// pub struct AppConfig { -// #[serde(default)] -// pub data_dir: PathBuf, -// #[serde(default)] -// pub config_dir: PathBuf, -// } - -// #[derive(Clone, Debug, Default, Deserialize)] -// pub struct Config { -// #[serde(default, flatten)] -// pub config: AppConfig, -// #[serde(default)] -// pub keybindings: KeyBindings, -// #[serde(default)] -// pub styles: Styles, -// } - -#[expect( - clippy::disallowed_types, - clippy::disallowed_methods, - reason = "vite_tui is a standalone TUI app, not using vite_str" -)] -pub static PROJECT_NAME: LazyLock = - LazyLock::new(|| env!("CARGO_CRATE_NAME").to_uppercase()); -// pub static DATA_FOLDER: LazyLock> = -// LazyLock::new(|| env::var(format!("{}_DATA", PROJECT_NAME.clone())).ok().map(PathBuf::from)); -// pub static CONFIG_FOLDER: LazyLock> = -// LazyLock::new(|| env::var(format!("{}_CONFIG", PROJECT_NAME.clone())).ok().map(PathBuf::from)); - -// impl Config { -// pub fn new() -> Result { -// let default_config: Config = json5::from_str(CONFIG).unwrap(); -// let data_dir = get_data_dir(); -// let config_dir = get_config_dir(); -// let mut builder = config::Config::builder() -// .set_default("data_dir", data_dir.to_str().unwrap())? -// .set_default("config_dir", config_dir.to_str().unwrap())?; - -// let config_files = [ -// ("config.json5", config::FileFormat::Json5), -// ("config.json", config::FileFormat::Json), -// ("config.yaml", config::FileFormat::Yaml), -// ("config.toml", config::FileFormat::Toml), -// ("config.ini", config::FileFormat::Ini), -// ]; -// let mut found_config = false; -// for (file, format) in &config_files { -// let source = config::File::from(config_dir.join(file)).format(*format).required(false); -// builder = builder.add_source(source); -// if config_dir.join(file).exists() { -// found_config = true -// } -// } -// if !found_config { -// error!("No configuration file found. Application may not behave as expected"); -// } - -// let mut cfg: Self = builder.build()?.try_deserialize()?; - -// for (mode, default_bindings) in default_config.keybindings.iter() { -// let user_bindings = cfg.keybindings.entry(*mode).or_default(); -// for (key, cmd) in default_bindings.iter() { -// user_bindings.entry(key.clone()).or_insert_with(|| cmd.clone()); -// } -// } -// for (mode, default_styles) in default_config.styles.iter() { -// let user_styles = cfg.styles.entry(*mode).or_default(); -// for (style_key, style) in default_styles.iter() { -// user_styles.entry(style_key.clone()).or_insert(*style); -// } -// } - -// Ok(cfg) -// } -// } - -#[expect( - clippy::disallowed_types, - reason = "vite_tui is a standalone TUI app, not using vite_path" -)] -pub fn get_data_dir() -> PathBuf { - project_directory().map_or_else( - || PathBuf::from(".").join(".data"), - |proj_dirs| proj_dirs.data_local_dir().to_path_buf(), - ) -} - -// pub fn get_config_dir() -> PathBuf { -// let directory = if let Some(s) = CONFIG_FOLDER.clone() { -// s -// } else if let Some(proj_dirs) = project_directory() { -// proj_dirs.config_local_dir().to_path_buf() -// } else { -// PathBuf::from(".").join(".config") -// }; -// directory -// } - -fn project_directory() -> Option { - ProjectDirs::from("dev", "voidzero", env!("CARGO_PKG_NAME")) -} - -// #[derive(Clone, Debug, Default, Deref, DerefMut)] -// pub struct KeyBindings(pub HashMap, Action>>); - -// impl<'de> Deserialize<'de> for KeyBindings { -// fn deserialize(deserializer: D) -> Result -// where -// D: Deserializer<'de>, -// { -// let parsed_map = HashMap::>::deserialize(deserializer)?; - -// let keybindings = parsed_map -// .into_iter() -// .map(|(mode, inner_map)| { -// let converted_inner_map = inner_map -// .into_iter() -// .map(|(key_str, cmd)| (parse_key_sequence(&key_str).unwrap(), cmd)) -// .collect(); -// (mode, converted_inner_map) -// }) -// .collect(); - -// Ok(KeyBindings(keybindings)) -// } -// } - -// fn parse_key_event(raw: &str) -> Result { -// let raw_lower = raw.to_ascii_lowercase(); -// let (remaining, modifiers) = extract_modifiers(&raw_lower); -// parse_key_code_with_modifiers(remaining, modifiers) -// } - -// fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) { -// let mut modifiers = KeyModifiers::empty(); -// let mut current = raw; - -// loop { -// match current { -// rest if rest.starts_with("ctrl-") => { -// modifiers.insert(KeyModifiers::CONTROL); -// current = &rest[5..]; -// } -// rest if rest.starts_with("alt-") => { -// modifiers.insert(KeyModifiers::ALT); -// current = &rest[4..]; -// } -// rest if rest.starts_with("shift-") => { -// modifiers.insert(KeyModifiers::SHIFT); -// current = &rest[6..]; -// } -// _ => break, // break out of the loop if no known prefix is detected -// }; -// } - -// (current, modifiers) -// } - -// fn parse_key_code_with_modifiers( -// raw: &str, -// mut modifiers: KeyModifiers, -// ) -> Result { -// let c = match raw { -// "esc" => KeyCode::Esc, -// "enter" => KeyCode::Enter, -// "left" => KeyCode::Left, -// "right" => KeyCode::Right, -// "up" => KeyCode::Up, -// "down" => KeyCode::Down, -// "home" => KeyCode::Home, -// "end" => KeyCode::End, -// "pageup" => KeyCode::PageUp, -// "pagedown" => KeyCode::PageDown, -// "backtab" => { -// modifiers.insert(KeyModifiers::SHIFT); -// KeyCode::BackTab -// } -// "backspace" => KeyCode::Backspace, -// "delete" => KeyCode::Delete, -// "insert" => KeyCode::Insert, -// "f1" => KeyCode::F(1), -// "f2" => KeyCode::F(2), -// "f3" => KeyCode::F(3), -// "f4" => KeyCode::F(4), -// "f5" => KeyCode::F(5), -// "f6" => KeyCode::F(6), -// "f7" => KeyCode::F(7), -// "f8" => KeyCode::F(8), -// "f9" => KeyCode::F(9), -// "f10" => KeyCode::F(10), -// "f11" => KeyCode::F(11), -// "f12" => KeyCode::F(12), -// "space" => KeyCode::Char(' '), -// "hyphen" => KeyCode::Char('-'), -// "minus" => KeyCode::Char('-'), -// "tab" => KeyCode::Tab, -// c if c.len() == 1 => { -// let mut c = c.chars().next().unwrap(); -// if modifiers.contains(KeyModifiers::SHIFT) { -// c = c.to_ascii_uppercase(); -// } -// KeyCode::Char(c) -// } -// _ => return Err(format!("Unable to parse {raw}")), -// }; -// Ok(KeyEvent::new(c, modifiers)) -// } - -// pub fn key_event_to_string(key_event: &KeyEvent) -> String { -// let char; -// let key_code = match key_event.code { -// KeyCode::Backspace => "backspace", -// KeyCode::Enter => "enter", -// KeyCode::Left => "left", -// KeyCode::Right => "right", -// KeyCode::Up => "up", -// KeyCode::Down => "down", -// KeyCode::Home => "home", -// KeyCode::End => "end", -// KeyCode::PageUp => "pageup", -// KeyCode::PageDown => "pagedown", -// KeyCode::Tab => "tab", -// KeyCode::BackTab => "backtab", -// KeyCode::Delete => "delete", -// KeyCode::Insert => "insert", -// KeyCode::F(c) => { -// char = format!("f({c})"); -// &char -// } -// KeyCode::Char(' ') => "space", -// KeyCode::Char(c) => { -// char = c.to_string(); -// &char -// } -// KeyCode::Esc => "esc", -// KeyCode::Null => "", -// KeyCode::CapsLock => "", -// KeyCode::Menu => "", -// KeyCode::ScrollLock => "", -// KeyCode::Media(_) => "", -// KeyCode::NumLock => "", -// KeyCode::PrintScreen => "", -// KeyCode::Pause => "", -// KeyCode::KeypadBegin => "", -// KeyCode::Modifier(_) => "", -// }; - -// let mut modifiers = Vec::with_capacity(3); - -// if key_event.modifiers.intersects(KeyModifiers::CONTROL) { -// modifiers.push("ctrl"); -// } - -// if key_event.modifiers.intersects(KeyModifiers::SHIFT) { -// modifiers.push("shift"); -// } - -// if key_event.modifiers.intersects(KeyModifiers::ALT) { -// modifiers.push("alt"); -// } - -// let mut key = modifiers.join("-"); - -// if !key.is_empty() { -// key.push('-'); -// } -// key.push_str(key_code); - -// key -// } - -// pub fn parse_key_sequence(raw: &str) -> Result, String> { -// if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() { -// return Err(format!("Unable to parse `{}`", raw)); -// } -// let raw = if !raw.contains("><") { -// let raw = raw.strip_prefix('<').unwrap_or(raw); -// let raw = raw.strip_prefix('>').unwrap_or(raw); -// raw -// } else { -// raw -// }; -// let sequences = raw -// .split("><") -// .map(|seq| { -// if let Some(s) = seq.strip_prefix('<') { -// s -// } else if let Some(s) = seq.strip_suffix('>') { -// s -// } else { -// seq -// } -// }) -// .collect::>(); - -// sequences.into_iter().map(parse_key_event).collect() -// } - -// #[derive(Clone, Debug, Default, Deref, DerefMut)] -// pub struct Styles(pub HashMap>); - -// impl<'de> Deserialize<'de> for Styles { -// fn deserialize(deserializer: D) -> Result -// where -// D: Deserializer<'de>, -// { -// let parsed_map = HashMap::>::deserialize(deserializer)?; - -// let styles = parsed_map -// .into_iter() -// .map(|(mode, inner_map)| { -// let converted_inner_map = -// inner_map.into_iter().map(|(str, style)| (str, parse_style(&style))).collect(); -// (mode, converted_inner_map) -// }) -// .collect(); - -// Ok(Styles(styles)) -// } -// } - -// pub fn parse_style(line: &str) -> Style { -// let (foreground, background) = -// line.split_at(line.to_lowercase().find("on ").unwrap_or(line.len())); -// let foreground = process_color_string(foreground); -// let background = process_color_string(&background.replace("on ", "")); - -// let mut style = Style::default(); -// if let Some(fg) = parse_color(&foreground.0) { -// style = style.fg(fg); -// } -// if let Some(bg) = parse_color(&background.0) { -// style = style.bg(bg); -// } -// style = style.add_modifier(foreground.1 | background.1); -// style -// } - -// fn process_color_string(color_str: &str) -> (String, Modifier) { -// let color = color_str -// .replace("grey", "gray") -// .replace("bright ", "") -// .replace("bold ", "") -// .replace("underline ", "") -// .replace("inverse ", ""); - -// let mut modifiers = Modifier::empty(); -// if color_str.contains("underline") { -// modifiers |= Modifier::UNDERLINED; -// } -// if color_str.contains("bold") { -// modifiers |= Modifier::BOLD; -// } -// if color_str.contains("inverse") { -// modifiers |= Modifier::REVERSED; -// } - -// (color, modifiers) -// } - -// fn parse_color(s: &str) -> Option { -// let s = s.trim_start(); -// let s = s.trim_end(); -// if s.contains("bright color") { -// let s = s.trim_start_matches("bright "); -// let c = s.trim_start_matches("color").parse::().unwrap_or_default(); -// Some(Color::Indexed(c.wrapping_shl(8))) -// } else if s.contains("color") { -// let c = s.trim_start_matches("color").parse::().unwrap_or_default(); -// Some(Color::Indexed(c)) -// } else if s.contains("gray") { -// let c = 232 + s.trim_start_matches("gray").parse::().unwrap_or_default(); -// Some(Color::Indexed(c)) -// } else if s.contains("rgb") { -// let red = (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8; -// let green = (s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8; -// let blue = (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8; -// let c = 16 + red * 36 + green * 6 + blue; -// Some(Color::Indexed(c)) -// } else if s == "bold black" { -// Some(Color::Indexed(8)) -// } else if s == "bold red" { -// Some(Color::Indexed(9)) -// } else if s == "bold green" { -// Some(Color::Indexed(10)) -// } else if s == "bold yellow" { -// Some(Color::Indexed(11)) -// } else if s == "bold blue" { -// Some(Color::Indexed(12)) -// } else if s == "bold magenta" { -// Some(Color::Indexed(13)) -// } else if s == "bold blue" { -// Some(Color::Indexed(14)) -// } else if s == "bold white" { -// Some(Color::Indexed(15)) -// } else if s == "black" { -// Some(Color::Indexed(0)) -// } else if s == "red" { -// Some(Color::Indexed(1)) -// } else if s == "green" { -// Some(Color::Indexed(2)) -// } else if s == "yellow" { -// Some(Color::Indexed(3)) -// } else if s == "blue" { -// Some(Color::Indexed(4)) -// } else if s == "magenta" { -// Some(Color::Indexed(5)) -// } else if s == "blue" { -// Some(Color::Indexed(6)) -// } else if s == "white" { -// Some(Color::Indexed(7)) -// } else { -// None -// } -// } - -// #[cfg(test)] -// mod tests { -// use pretty_assertions::assert_eq; - -// use super::*; - -// #[test] -// fn test_parse_style_default() { -// let style = parse_style(""); -// assert_eq!(style, Style::default()); -// } - -// #[test] -// fn test_parse_style_foreground() { -// let style = parse_style("red"); -// assert_eq!(style.fg, Some(Color::Indexed(1))); -// } - -// #[test] -// fn test_parse_style_background() { -// let style = parse_style("on blue"); -// assert_eq!(style.bg, Some(Color::Indexed(4))); -// } - -// #[test] -// fn test_parse_style_modifiers() { -// let style = parse_style("underline red on blue"); -// assert_eq!(style.fg, Some(Color::Indexed(1))); -// assert_eq!(style.bg, Some(Color::Indexed(4))); -// } - -// #[test] -// fn test_process_color_string() { -// let (color, modifiers) = process_color_string("underline bold inverse gray"); -// assert_eq!(color, "gray"); -// assert!(modifiers.contains(Modifier::UNDERLINED)); -// assert!(modifiers.contains(Modifier::BOLD)); -// assert!(modifiers.contains(Modifier::REVERSED)); -// } - -// #[test] -// fn test_parse_color_rgb() { -// let color = parse_color("rgb123"); -// let expected = 16 + 36 + 2 * 6 + 3; -// assert_eq!(color, Some(Color::Indexed(expected))); -// } - -// #[test] -// fn test_parse_color_unknown() { -// let color = parse_color("unknown"); -// assert_eq!(color, None); -// } - -// #[test] -// fn test_config() -> Result<()> { -// let c = Config::new()?; -// assert_eq!( -// c.keybindings -// .get(&Mode::Home) -// .unwrap() -// .get(&parse_key_sequence("").unwrap_or_default()) -// .unwrap(), -// &Action::Quit -// ); -// Ok(()) -// } - -// #[test] -// fn test_simple_keys() { -// assert_eq!( -// parse_key_event("a").unwrap(), -// KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty()) -// ); - -// assert_eq!( -// parse_key_event("enter").unwrap(), -// KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()) -// ); - -// assert_eq!( -// parse_key_event("esc").unwrap(), -// KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()) -// ); -// } - -// #[test] -// fn test_with_modifiers() { -// assert_eq!( -// parse_key_event("ctrl-a").unwrap(), -// KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL) -// ); - -// assert_eq!( -// parse_key_event("alt-enter").unwrap(), -// KeyEvent::new(KeyCode::Enter, KeyModifiers::ALT) -// ); - -// assert_eq!( -// parse_key_event("shift-esc").unwrap(), -// KeyEvent::new(KeyCode::Esc, KeyModifiers::SHIFT) -// ); -// } - -// #[test] -// fn test_multiple_modifiers() { -// assert_eq!( -// parse_key_event("ctrl-alt-a").unwrap(), -// KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL | KeyModifiers::ALT) -// ); - -// assert_eq!( -// parse_key_event("ctrl-shift-enter").unwrap(), -// KeyEvent::new(KeyCode::Enter, KeyModifiers::CONTROL | KeyModifiers::SHIFT) -// ); -// } - -// #[test] -// fn test_reverse_multiple_modifiers() { -// assert_eq!( -// key_event_to_string(&KeyEvent::new( -// KeyCode::Char('a'), -// KeyModifiers::CONTROL | KeyModifiers::ALT -// )), -// "ctrl-alt-a".to_string() -// ); -// } - -// #[test] -// fn test_invalid_keys() { -// assert!(parse_key_event("invalid-key").is_err()); -// assert!(parse_key_event("ctrl-invalid-key").is_err()); -// } - -// #[test] -// fn test_case_insensitivity() { -// assert_eq!( -// parse_key_event("CTRL-a").unwrap(), -// KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL) -// ); - -// assert_eq!( -// parse_key_event("AlT-eNtEr").unwrap(), -// KeyEvent::new(KeyCode::Enter, KeyModifiers::ALT) -// ); -// } -// } diff --git a/crates/vite_tui/src/lib.rs b/crates/vite_tui/src/lib.rs deleted file mode 100644 index 598439a8f..000000000 --- a/crates/vite_tui/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod action; -mod app; -mod components; -mod config; -mod tui; - -pub mod logging; - -pub use app::App; diff --git a/crates/vite_tui/src/logging.rs b/crates/vite_tui/src/logging.rs deleted file mode 100644 index cfb0d8530..000000000 --- a/crates/vite_tui/src/logging.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::sync::LazyLock; - -use color_eyre::Result; -use tracing_error::ErrorLayer; -use tracing_subscriber::{EnvFilter, fmt, prelude::*}; - -use crate::config; - -#[expect( - clippy::disallowed_types, - clippy::disallowed_macros, - reason = "vite_tui is a standalone TUI app, not using vite_str" -)] -pub static LOG_ENV: LazyLock = - LazyLock::new(|| format!("{}_LOG_LEVEL", config::PROJECT_NAME.clone())); -#[expect( - clippy::disallowed_types, - clippy::disallowed_macros, - reason = "vite_tui is a standalone TUI app, not using vite_str" -)] -pub static LOG_FILE: LazyLock = LazyLock::new(|| format!("{}.log", env!("CARGO_PKG_NAME"))); - -/// # Errors -pub fn init() -> Result<()> { - let directory = config::get_data_dir(); - std::fs::create_dir_all(directory.clone())?; - let log_path = directory.join(LOG_FILE.clone()); - let log_file = std::fs::File::create(log_path)?; - let env_filter = EnvFilter::builder().with_default_directive(tracing::Level::INFO.into()); - // If the `RUST_LOG` environment variable is set, use that as the default, otherwise use the - // value of the `LOG_ENV` environment variable. If the `LOG_ENV` environment variable contains - // errors, then this will return an error. - let env_filter = env_filter - .try_from_env() - .or_else(|_| env_filter.with_env_var(LOG_ENV.clone()).from_env())?; - let file_subscriber = fmt::layer() - .with_file(true) - .with_line_number(true) - .with_writer(log_file) - .with_target(false) - .with_ansi(false) - .with_filter(env_filter); - tracing_subscriber::registry().with(file_subscriber).with(ErrorLayer::default()).try_init()?; - Ok(()) -} diff --git a/crates/vite_tui/src/main.rs b/crates/vite_tui/src/main.rs deleted file mode 100644 index 368b6546d..000000000 --- a/crates/vite_tui/src/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -use color_eyre::Result; -use vite_tui::{App, logging}; - -#[tokio::main] -async fn main() -> Result<()> { - logging::init()?; - - let mut app = App::new(); - app.run().await?; - Ok(()) -} diff --git a/crates/vite_tui/src/tui.rs b/crates/vite_tui/src/tui.rs deleted file mode 100644 index d46cbd1bc..000000000 --- a/crates/vite_tui/src/tui.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::{ - io::{Stdout, stdout}, - ops::{Deref, DerefMut}, - time::Duration, -}; - -use color_eyre::Result; -use crossterm::{ - cursor, - event::{ - DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, - Event as CrosstermEvent, EventStream, KeyEvent, KeyEventKind, MouseEvent, - }, - terminal::{EnterAlternateScreen, LeaveAlternateScreen}, -}; -use futures::{FutureExt, StreamExt}; -use ratatui::{Terminal, backend::CrosstermBackend as Backend}; -use tokio::{ - sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, - task::JoinHandle, - time::interval, -}; -use tokio_util::sync::CancellationToken; -use tracing::error; - -#[expect(unused, reason = "TUI event variants defined for future use")] -#[derive(Clone, Debug)] -pub enum Event { - Init, - Quit, - Error, - // Closed, - Tick, - Render, - FocusGained, - FocusLost, - #[expect(clippy::disallowed_types, reason = "crossterm provides paste content as String")] - Paste(String), - Key(KeyEvent), - Mouse(MouseEvent), - Resize(u16, u16), -} - -pub struct Tui { - pub terminal: Terminal>, - pub task: JoinHandle<()>, - pub cancellation_token: CancellationToken, - pub event_rx: UnboundedReceiver, - pub event_tx: UnboundedSender, - pub frame_rate: f64, - pub tick_rate: f64, - pub mouse: bool, - pub paste: bool, -} - -impl Tui { - pub fn new() -> Result { - let (event_tx, event_rx) = mpsc::unbounded_channel(); - Ok(Self { - terminal: ratatui::Terminal::new(Backend::new(stdout()))?, - task: tokio::spawn(async {}), - cancellation_token: CancellationToken::new(), - event_rx, - event_tx, - frame_rate: 60.0, - tick_rate: 4.0, - mouse: false, - paste: false, - }) - } - - pub const fn tick_rate(mut self, tick_rate: f64) -> Self { - self.tick_rate = tick_rate; - self - } - - pub const fn frame_rate(mut self, frame_rate: f64) -> Self { - self.frame_rate = frame_rate; - self - } - - pub const fn mouse(mut self, mouse: bool) -> Self { - self.mouse = mouse; - self - } - - // pub const fn paste(mut self, paste: bool) -> Self { - // self.paste = paste; - // self - // } - - pub fn start(&mut self) { - self.cancel(); // Cancel any existing task - self.cancellation_token = CancellationToken::new(); - let event_loop = Self::event_loop( - self.event_tx.clone(), - self.cancellation_token.clone(), - self.tick_rate, - self.frame_rate, - ); - self.task = tokio::spawn(async { - event_loop.await; - }); - } - - async fn event_loop( - event_tx: UnboundedSender, - cancellation_token: CancellationToken, - tick_rate: f64, - frame_rate: f64, - ) { - let mut event_stream = EventStream::new(); - let mut tick_interval = interval(Duration::from_secs_f64(1.0 / tick_rate)); - let mut render_interval = interval(Duration::from_secs_f64(1.0 / frame_rate)); - - // if this fails, then it's likely a bug in the calling code - event_tx.send(Event::Init).expect("failed to send init event"); - loop { - let event = tokio::select! { - () = cancellation_token.cancelled() => { - break; - } - _ = tick_interval.tick() => Event::Tick, - _ = render_interval.tick() => Event::Render, - crossterm_event = event_stream.next().fuse() => match crossterm_event { - Some(Ok(event)) => match event { - CrosstermEvent::Key(key) if key.kind == KeyEventKind::Press => Event::Key(key), - CrosstermEvent::Mouse(mouse) => Event::Mouse(mouse), - CrosstermEvent::Resize(x, y) => Event::Resize(x, y), - CrosstermEvent::FocusLost => Event::FocusLost, - CrosstermEvent::FocusGained => Event::FocusGained, - CrosstermEvent::Paste(s) => Event::Paste(s), - CrosstermEvent::Key(_) => continue, // ignore other events - } - Some(Err(_)) => Event::Error, - None => break, // the event stream has stopped and will not produce any more events - }, - }; - if event_tx.send(event).is_err() { - // the receiver has been dropped, so there's no point in continuing the loop - break; - } - } - cancellation_token.cancel(); - } - - #[expect( - clippy::disallowed_methods, - reason = "polling with short sleep is acceptable for TUI task shutdown" - )] - pub fn stop(&self) { - self.cancel(); - let mut counter = 0; - while !self.task.is_finished() { - std::thread::sleep(Duration::from_millis(1)); - counter += 1; - if counter > 50 { - self.task.abort(); - } - if counter > 100 { - error!("Failed to abort task in 100 milliseconds for unknown reason"); - break; - } - } - } - - pub fn enter(&mut self) -> Result<()> { - crossterm::terminal::enable_raw_mode()?; - crossterm::execute!(stdout(), EnterAlternateScreen, cursor::Hide)?; - if self.mouse { - crossterm::execute!(stdout(), EnableMouseCapture)?; - } - if self.paste { - crossterm::execute!(stdout(), EnableBracketedPaste)?; - } - self.start(); - Ok(()) - } - - pub fn exit(&mut self) -> Result<()> { - self.stop(); - if crossterm::terminal::is_raw_mode_enabled()? { - self.flush()?; - if self.paste { - crossterm::execute!(stdout(), DisableBracketedPaste)?; - } - if self.mouse { - crossterm::execute!(stdout(), DisableMouseCapture)?; - } - crossterm::execute!(stdout(), LeaveAlternateScreen, cursor::Show)?; - crossterm::terminal::disable_raw_mode()?; - } - Ok(()) - } - - pub fn cancel(&self) { - self.cancellation_token.cancel(); - } - - pub fn suspend(&mut self) -> Result<()> { - self.exit()?; - // #[cfg(not(windows))] - // signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?; - Ok(()) - } - - // pub fn resume(&mut self) -> Result<()> { - // self.enter()?; - // Ok(()) - // } - - pub async fn next_event(&mut self) -> Option { - self.event_rx.recv().await - } -} - -impl Deref for Tui { - type Target = ratatui::Terminal>; - - fn deref(&self) -> &Self::Target { - &self.terminal - } -} - -impl DerefMut for Tui { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.terminal - } -} - -impl Drop for Tui { - fn drop(&mut self) { - self.exit().unwrap(); - } -} diff --git a/crates/vite_workspace/Cargo.toml b/crates/vite_workspace/Cargo.toml deleted file mode 100644 index 6a0c4a947..000000000 --- a/crates/vite_workspace/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "vite_workspace" -version = "0.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -publish = false -rust-version.workspace = true - -[dependencies] -clap = { workspace = true, features = ["derive"] } -petgraph = { workspace = true, features = ["serde-1"] } -rustc-hash = { workspace = true } -serde = { workspace = true, features = ["derive"] } -# use `preserve_order` feature to preserve the order of the fields in `package.json` -serde_json = { workspace = true, features = ["preserve_order"] } -serde_norway = { workspace = true } -thiserror = { workspace = true } -tracing = { workspace = true } -vec1 = { workspace = true, features = ["smallvec-v1"] } -vite_glob = { workspace = true } -vite_path = { workspace = true } -vite_str = { workspace = true } -wax = { workspace = true } - -[dev-dependencies] -tempfile = { workspace = true } - -[lints] -workspace = true - -[lib] -doctest = false diff --git a/crates/vite_workspace/README.md b/crates/vite_workspace/README.md deleted file mode 100644 index 885e965a7..000000000 --- a/crates/vite_workspace/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# vite_workspace - -- Finds workspace roots (pnpm-workspace.yaml, package.json with workspaces field), loads all packages -- Builds a dependency graph with workspace protocol support for pnpm/npm/yarn. diff --git a/crates/vite_workspace/src/error.rs b/crates/vite_workspace/src/error.rs deleted file mode 100644 index bd0b6f766..000000000 --- a/crates/vite_workspace/src/error.rs +++ /dev/null @@ -1,69 +0,0 @@ -#[expect( - clippy::disallowed_types, - reason = "StripPrefixError carries a raw &Path that may not be valid UTF-8, so it can't use vite_path types" -)] -use std::path::Path; -use std::{io, sync::Arc}; - -use vite_path::{ - AbsolutePath, AbsolutePathBuf, RelativePathBuf, absolute::StripPrefixError, - relative::InvalidPathDataError, -}; -use vite_str::Str; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("Duplicate package name `{name}` found at `{path1}` and `{path2}`")] - DuplicatedPackageName { name: Str, path1: RelativePathBuf, path2: RelativePathBuf }, - - #[error("Package not found in workspace: `{0:?}`")] - PackageJsonNotFound(AbsolutePathBuf), - - #[error("Package at `{package_path:?}` is outside workspace root `{workspace_root:?}`")] - PackageOutsideWorkspace { package_path: Arc, workspace_root: Arc }, - - #[error( - "The stripped path ({stripped_path:?}) is not a valid relative path because: {invalid_path_data_error}" - )] - #[expect( - clippy::disallowed_types, - reason = "stripped path may not be valid UTF-8, so it can't use vite_path types" - )] - StripPath { stripped_path: Box, invalid_path_data_error: InvalidPathDataError }, - - // External library errors - #[error(transparent)] - Io(#[from] io::Error), - - #[error("Failed to parse JSON file at {file_path:?}")] - SerdeJson { - file_path: Arc, - #[source] - serde_json_error: serde_json::Error, - }, - - #[error("Failed to parse YAML file at {file_path:?}")] - SerdeYaml { - file_path: Arc, - #[source] - serde_yaml_error: serde_norway::Error, - }, - - #[error(transparent)] - WaxBuild(#[from] wax::BuildError), - - #[error(transparent)] - WaxWalk(#[from] wax::walk::WalkError), - - #[error(transparent)] - Glob(#[from] vite_glob::path::PathGlobError), -} - -impl From> for Error { - fn from(value: StripPrefixError<'_>) -> Self { - Self::StripPath { - stripped_path: Box::from(value.stripped_path), - invalid_path_data_error: value.invalid_path_data_error, - } - } -} diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs deleted file mode 100644 index ba8e98236..000000000 --- a/crates/vite_workspace/src/lib.rs +++ /dev/null @@ -1,1413 +0,0 @@ -mod error; -pub mod package; -pub mod package_filter; -pub mod package_graph; -mod package_manager; - -use std::{collections::hash_map::Entry, fs, io, sync::Arc}; - -use petgraph::graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}; -use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; -use serde::Deserialize; -use vec1::smallvec_v1::SmallVec1; -use vite_glob::path::PathGlobSet; -use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePathBuf}; -use vite_str::Str; -use wax::{Glob, walk::Entry as _}; - -pub use crate::{ - error::Error, - package::{DependencyType, PackageJson}, - package_manager::{ - FileWithPath, WorkspaceFile, WorkspaceRoot, find_package_root, find_workspace_root, - }, -}; - -/// Strip a leading UTF-8 byte order mark (BOM) from `bytes`, if present. -/// -/// Some editors and tools (notably on Windows, e.g. Notepad or PowerShell's -/// `>` redirection) write `package.json` with a UTF-8 BOM (`EF BB BF`). -/// `serde_json` does not accept a leading BOM and fails with a parse error, so -/// we trim it before parsing. -pub(crate) fn strip_bom(bytes: &[u8]) -> &[u8] { - bytes.strip_prefix(b"\xEF\xBB\xBF").unwrap_or(bytes) -} - -/// The workspace configuration for pnpm. -#[derive(Debug, Deserialize)] -struct PnpmWorkspace { - /// The packages to include in the workspace. - /// - /// - #[serde(default)] - packages: Vec, -} - -/// The `workspaces` field in package.json can be either an array of glob patterns -/// or an object with a `packages` field (used by Bun catalogs and Yarn classic nohoist). -/// -/// Array form: `"workspaces": ["packages/*", "apps/*"]` -/// Object form: `"workspaces": {"packages": ["packages/*", "apps/*"], "catalog": {...}}` -/// -/// Bun: -/// Yarn classic: -#[derive(Debug, Deserialize)] -#[serde(untagged)] -enum NpmWorkspaces { - /// Array of glob patterns (npm, yarn, bun). - Array(Vec), - /// Object form with a `packages` field (Bun catalogs, Yarn classic nohoist). - Object { packages: Vec }, -} - -impl NpmWorkspaces { - fn into_packages(self) -> Vec { - match self { - Self::Array(packages) | Self::Object { packages } => packages, - } - } -} - -/// The workspace configuration for npm/yarn/bun. -/// -/// npm: -/// yarn: -/// bun: -#[derive(Debug, Deserialize)] -struct NpmWorkspace { - /// Glob patterns referencing the workspaces of the project. - /// Accepts both array form and object form (with `packages` key). - workspaces: NpmWorkspaces, -} - -#[derive(Debug)] -struct WorkspaceMemberGlobs { - workspaces: Vec, -} -impl WorkspaceMemberGlobs { - const fn new(workspaces: Vec) -> Self { - Self { workspaces } - } - - fn get_package_json_paths( - self, - workspace_root: impl AsRef, - ) -> Result, Error> { - let workspace_root = workspace_root.as_ref(); - let mut package_json_paths = HashSet::::default(); - let mut has_negated = false; - let mut inclusions = Vec::::new(); - let mut all = Vec::::new(); - for pattern in self.workspaces { - // Ported from npm's workspace pattern normalization: - // https://github.com/npm/map-workspaces/blob/76ea3c852171790e245052e006329de15b09e60e/lib/index.js#L17 - let exclusion_count = pattern.bytes().take_while(|byte| *byte == b'!').count(); - let path = &pattern[exclusion_count..]; - let path_without_optional_dot = path.strip_prefix('.').unwrap_or(path); - let path = if path_without_optional_dot.starts_with('/') { - path_without_optional_dot.trim_start_matches('/') - } else { - path - }; - let is_negated = exclusion_count % 2 == 1; - - let mut pattern = Str::with_capacity(pattern.len() + "/package.json".len()); - if is_negated { - pattern.push('!'); - } - pattern.push_str(path); - pattern.push_str(if path.is_empty() || path.ends_with('/') { - "package.json" - } else { - "/package.json" - }); - - if is_negated { - has_negated = true; - } else { - inclusions.push(pattern.clone()); - } - all.push(pattern); - } - let glob_patterns = if has_negated { Some(PathGlobSet::new(&all)?) } else { None }; - - // TODO: parallelize this - for inclusion in inclusions { - let glob = Glob::new(&inclusion)?; - for entry in glob.walk(workspace_root.as_path().to_path_buf()) { - let Ok(entry) = entry else { - continue; - }; - - if !entry.file_type().is_file() { - continue; - } - - if let Some(glob_patterns) = glob_patterns.as_ref() - && !glob_patterns.is_match(entry.to_candidate_path().as_ref()) - { - continue; - } - package_json_paths.insert(AbsolutePathBuf::new(entry.into_path()).unwrap()); - } - } - let mut package_json_paths = package_json_paths.into_iter().collect::>(); - package_json_paths.sort_unstable(); - Ok(package_json_paths) - } -} - -#[derive(Debug, Clone)] -pub struct PackageInfo { - pub package_json: PackageJson, - pub path: RelativePathBuf, - pub absolute_path: Arc, -} - -#[derive(Default)] -struct PackageGraphBuilder { - id_and_deps_by_path: HashMap)>, - name_to_path: HashMap>, - graph: DiGraph, -} - -impl PackageGraphBuilder { - fn add_package( - &mut self, - package_path: RelativePathBuf, - absolute_path: Arc, - package_json: PackageJson, - ) { - let deps = package_json.get_workspace_dependencies().collect::>(); - let package_name = package_json.name.clone(); - let id = self.graph.add_node(PackageInfo { - package_json, - path: package_path.clone(), - absolute_path, - }); - - // Always store by path - self.id_and_deps_by_path.insert(package_path.clone(), (id, deps)); - - // Also maintain name to path mapping for dependency resolution - match self.name_to_path.entry(package_name) { - Entry::Vacant(entry) => { - entry.insert(SmallVec1::new(package_path)); - } - Entry::Occupied(mut entry) => { - entry.get_mut().push(package_path); - } - } - } - - fn build(mut self) -> Result, Error> { - for (id, deps) in self.id_and_deps_by_path.values() { - for (dep_name, dep_type) in deps { - // Skip dependencies on nameless packages (empty string) - // These can't be referenced anyway - if dep_name.is_empty() { - continue; - } - - // Resolve dependency name to path, then find the node - if let Some(dep_paths) = self.name_to_path.get(dep_name) - && let Some((dep_id, _)) = self.id_and_deps_by_path.get(dep_paths.first()) - { - if let [dep_path1, dep_path2, ..] = dep_paths.as_slice() { - return Err(Error::DuplicatedPackageName { - name: dep_name.clone(), - path1: dep_path1.clone(), - path2: dep_path2.clone(), - }); - } - // Skip self-referential edges: a package listing itself as a dependency - // (e.g. for testing purposes) must not create a cycle in the task graph. - if *id != *dep_id { - self.graph.add_edge(*id, *dep_id, *dep_type); - } - } - // Silently skip if dependency not found - it might be an external package - } - } - Ok(self.graph) - } -} - -/// newtype of `DefaultIx` for indices in package graphs -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PackageIx(DefaultIx); -// SAFETY: PackageIx is a newtype over DefaultIx which already implements IndexType correctly -unsafe impl petgraph::graph::IndexType for PackageIx { - fn new(x: usize) -> Self { - Self(DefaultIx::new(x)) - } - - fn index(&self) -> usize { - self.0.index() - } - - fn max() -> Self { - Self(::max()) - } -} - -pub type PackageNodeIndex = NodeIndex; -pub type PackageEdgeIndex = EdgeIndex; - -/// Discover the workspace from cwd and load the package graph. -/// -/// # Errors -/// Returns an error if the workspace cannot be found or the package graph cannot be loaded. -#[tracing::instrument(level = "debug", skip_all)] -pub fn discover_package_graph( - cwd: impl AsRef, -) -> Result, Error> { - let (workspace_root, _cwd) = find_workspace_root(cwd.as_ref())?; - load_package_graph(&workspace_root) -} - -/// Load the package graph from a discovered workspace. -/// -/// # Errors -/// Returns an error if workspace files cannot be read/parsed, or if packages are outside the workspace root. -/// -/// # Panics -/// Panics if a `package.json` path has no parent directory (should not happen for valid paths). -#[tracing::instrument(level = "debug", skip_all)] -pub fn load_package_graph( - workspace_root: &WorkspaceRoot, -) -> Result, Error> { - let mut graph_builder = PackageGraphBuilder::default(); - let workspaces = match &workspace_root.workspace_file { - WorkspaceFile::PnpmWorkspaceYaml(file_with_path) => { - let workspace: PnpmWorkspace = - serde_norway::from_slice(strip_bom(file_with_path.content())).map_err(|e| { - Error::SerdeYaml { - file_path: Arc::clone(file_with_path.path()), - serde_yaml_error: e, - } - })?; - workspace.packages - } - WorkspaceFile::NpmWorkspaceJson(file_with_path) => { - let workspace: NpmWorkspace = - serde_json::from_slice(strip_bom(file_with_path.content())).map_err(|e| { - Error::SerdeJson { - file_path: Arc::clone(file_with_path.path()), - serde_json_error: e, - } - })?; - workspace.workspaces.into_packages() - } - WorkspaceFile::NonWorkspacePackage(file_with_path) => { - // For non-workspace packages, add the package.json to the graph as a root package - let package_json: PackageJson = - serde_json::from_slice(strip_bom(file_with_path.content())).map_err(|e| { - Error::SerdeJson { - file_path: Arc::clone(file_with_path.path()), - serde_json_error: e, - } - })?; - graph_builder.add_package( - RelativePathBuf::default(), - Arc::clone(&workspace_root.path), - package_json, - ); - - return graph_builder.build(); - } - }; - - let member_globs = WorkspaceMemberGlobs::new(workspaces); - let mut has_root_package = false; - for package_json_path in member_globs.get_package_json_paths(&*workspace_root.path)? { - let package_json_path: Arc = package_json_path.clone().into(); - let package_json: PackageJson = - serde_json::from_slice(strip_bom(&fs::read(&*package_json_path)?)).map_err(|e| { - Error::SerdeJson { file_path: Arc::clone(&package_json_path), serde_json_error: e } - })?; - let absolute_path = package_json_path.parent().unwrap(); - let Some(package_path) = absolute_path.strip_prefix(&*workspace_root.path)? else { - return Err(Error::PackageOutsideWorkspace { - package_path: package_json_path, - workspace_root: Arc::clone(&workspace_root.path), - }); - }; - - has_root_package = has_root_package || package_path.as_str().is_empty(); - graph_builder.add_package(package_path, absolute_path.into(), package_json); - } - // Always add the root package if member globs do not include it. - if !has_root_package { - let package_json_path = workspace_root.path.join("package.json"); - let package_json = match fs::read(&package_json_path) { - Ok(content) => { - let package_json_path: Arc = package_json_path.into(); - serde_json::from_slice(strip_bom(&content)).map_err(|e| Error::SerdeJson { - file_path: package_json_path, - serde_json_error: e, - })? - } - Err(err) if err.kind() == io::ErrorKind::NotFound => { - // No package.json at root - use empty default - PackageJson::default() - } - Err(err) => return Err(err.into()), - }; - graph_builder.add_package( - RelativePathBuf::default(), - Arc::clone(&workspace_root.path), - package_json, - ); - } - graph_builder.build() -} - -#[cfg(test)] -mod tests { - use std::fs; - - use petgraph::visit::EdgeRef; - use rustc_hash::FxHashSet; - use tempfile::TempDir; - - use super::*; - - #[test] - fn test_get_package_graph_single_package() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create a single package.json without workspaces - let package_json = serde_json::json!({ - "name": "my-app", - "dependencies": { - "react": "^18.0.0" - }, - "devDependencies": { - "typescript": "^5.0.0" - } - }); - fs::write(temp_dir_path.join("package.json"), package_json.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have exactly 1 node (the single package) - assert_eq!(graph.node_count(), 1); - assert_eq!(graph.edge_count(), 0); - - let node = graph.node_weight(NodeIndex::new(0)).unwrap(); - assert_eq!(node.package_json.name, "my-app"); - assert_eq!(node.path.as_str(), ""); - } - - #[test] - fn test_strip_bom() { - // Leading UTF-8 BOM is stripped. - assert_eq!(strip_bom(b"\xEF\xBB\xBF{}"), b"{}"); - // Content without a BOM is returned unchanged. - assert_eq!(strip_bom(b"{}"), b"{}"); - // Only a leading BOM is stripped, not occurrences elsewhere. - assert_eq!(strip_bom(b"{}\xEF\xBB\xBF"), b"{}\xEF\xBB\xBF"); - // Empty input is handled. - assert_eq!(strip_bom(b""), b""); - } - - /// Regression test for - /// follow-up: a `package.json` written with a UTF-8 BOM (e.g. by some - /// editors on Windows) must still parse instead of failing the whole graph. - #[test] - fn test_get_package_graph_package_json_with_bom() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // pnpm workspace so package.json files are read via `fs::read` + parse. - let workspace_yaml = "packages:\n - \"packages/*\"\n"; - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), workspace_yaml).unwrap(); - - // Root package.json with a BOM. - let root_package = serde_json::json!({ "name": "monorepo-root", "private": true }); - let mut root_bytes = b"\xEF\xBB\xBF".to_vec(); - root_bytes.extend_from_slice(root_package.to_string().as_bytes()); - fs::write(temp_dir_path.join("package.json"), root_bytes).unwrap(); - - // Member package.json with a BOM. - fs::create_dir_all(temp_dir_path.join("packages/pkg-a")).unwrap(); - let pkg_a = serde_json::json!({ "name": "pkg-a" }); - let mut pkg_a_bytes = b"\xEF\xBB\xBF".to_vec(); - pkg_a_bytes.extend_from_slice(pkg_a.to_string().as_bytes()); - fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a_bytes).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Both the root and the member package should be present. - assert_eq!(graph.node_count(), 2); - let names: FxHashSet<_> = - graph.node_weights().map(|n| n.package_json.name.as_str()).collect(); - assert!(names.contains("monorepo-root")); - assert!(names.contains("pkg-a")); - } - - #[test] - fn test_get_package_graph_pnpm_workspace_yaml_with_bom() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // pnpm-workspace.yaml with a leading BOM. - let mut yaml_bytes = b"\xEF\xBB\xBF".to_vec(); - yaml_bytes.extend_from_slice(b"packages:\n - \"packages/*\"\n"); - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), yaml_bytes).unwrap(); - - let root_package = serde_json::json!({ "name": "monorepo-root", "private": true }); - fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); - - fs::create_dir_all(temp_dir_path.join("packages/pkg-a")).unwrap(); - let pkg_a = serde_json::json!({ "name": "pkg-a" }); - fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - assert_eq!(graph.node_count(), 2); - let names: FxHashSet<_> = - graph.node_weights().map(|n| n.package_json.name.as_str()).collect(); - assert!(names.contains("monorepo-root")); - assert!(names.contains("pkg-a")); - } - - #[test] - fn test_get_package_graph_single_package_with_bom() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Single non-workspace package.json with a BOM (NonWorkspacePackage path). - let package_json = serde_json::json!({ "name": "my-app" }); - let mut bytes = b"\xEF\xBB\xBF".to_vec(); - bytes.extend_from_slice(package_json.to_string().as_bytes()); - fs::write(temp_dir_path.join("package.json"), bytes).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - assert_eq!(graph.node_count(), 1); - assert_eq!(graph.node_weight(NodeIndex::new(0)).unwrap().package_json.name, "my-app"); - } - - #[test] - fn test_get_package_graph_pnpm_workspace() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create pnpm-workspace.yaml - let workspace_yaml = r#"packages: - - "packages/*" -"#; - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), workspace_yaml).unwrap(); - - // Create root package.json - let root_package = serde_json::json!({ - "name": "monorepo-root", - "private": true - }); - fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create package A - fs::create_dir_all(temp_dir_path.join("packages/pkg-a")).unwrap(); - let pkg_a = serde_json::json!({ - "name": "pkg-a", - "dependencies": {} - }); - fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a.to_string()).unwrap(); - - // Create package B that depends on A - fs::create_dir_all(temp_dir_path.join("packages/pkg-b")).unwrap(); - let pkg_b = serde_json::json!({ - "name": "pkg-b", - "dependencies": { - "pkg-a": "workspace:*" - } - }); - fs::write(temp_dir_path.join("packages/pkg-b/package.json"), pkg_b.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have 3 nodes: root + pkg-a + pkg-b - assert_eq!(graph.node_count(), 3); - // Should have 1 edge: pkg-b -> pkg-a - assert_eq!(graph.edge_count(), 1); - - // Verify the dependency edge exists - let mut found_edge = false; - for edge_ref in graph.edge_references() { - let source = &graph[edge_ref.source()]; - let target = &graph[edge_ref.target()]; - if source.package_json.name == "pkg-b" && target.package_json.name == "pkg-a" { - found_edge = true; - assert_eq!(*edge_ref.weight(), DependencyType::Normal); - } - } - assert!(found_edge, "Should have found edge from pkg-b to pkg-a"); - } - - #[test] - fn test_get_package_graph_workspace_exclusions() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - let workspace_yaml = r#"packages: - - "packages/*" - - "!packages/excluded*" -"#; - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), workspace_yaml).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create included package - fs::create_dir_all(temp_dir_path.join("packages/included")).unwrap(); - let included = serde_json::json!({ - "name": "included-pkg" - }); - fs::write(temp_dir_path.join("packages/included/package.json"), included.to_string()) - .unwrap(); - - // Create excluded package - fs::create_dir_all(temp_dir_path.join("packages/excluded-test")).unwrap(); - let excluded = serde_json::json!({ - "name": "excluded-pkg" - }); - fs::write(temp_dir_path.join("packages/excluded-test/package.json"), excluded.to_string()) - .unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have the included package - let mut found_included = false; - let mut found_excluded = false; - for node in graph.node_weights() { - if node.package_json.name == "included-pkg" { - found_included = true; - } - if node.package_json.name == "excluded-pkg" { - found_excluded = true; - } - } - assert!(found_included, "Should have found included package"); - assert!(!found_excluded, "Should not have found excluded package"); - } - - #[test] - fn test_get_package_graph_workspace_work_with_last_match_wins() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - let workspace_yaml = r#"packages: - - "packages/**" - - "!packages/excluded/**" - - "packages/excluded/a" -"#; - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), workspace_yaml).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create included package - fs::create_dir_all(temp_dir_path.join("packages/included")).unwrap(); - let included = serde_json::json!({ - "name": "included-pkg" - }); - fs::write(temp_dir_path.join("packages/included/package.json"), included.to_string()) - .unwrap(); - - // Create excluded package b - fs::create_dir_all(temp_dir_path.join("packages/excluded/b")).unwrap(); - let excluded = serde_json::json!({ - "name": "excluded-b" - }); - fs::write(temp_dir_path.join("packages/excluded/b/package.json"), excluded.to_string()) - .unwrap(); - - // Create included package a - fs::create_dir_all(temp_dir_path.join("packages/excluded/a")).unwrap(); - let excluded = serde_json::json!({ - "name": "excluded-a" - }); - fs::write(temp_dir_path.join("packages/excluded/a/package.json"), excluded.to_string()) - .unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have the included package - let mut found_included = false; - let mut found_b = false; - let mut found_a = false; - for node in graph.node_weights() { - if node.package_json.name == "included-pkg" { - found_included = true; - } - if node.package_json.name == "excluded-b" { - found_b = true; - } - if node.package_json.name == "excluded-a" { - found_a = true; - } - } - assert!(found_included, "Should have found included package"); - assert!(!found_b, "Should not have found excluded package b"); - assert!(found_a, "Should have found included package a"); - } - - #[test] - fn test_get_package_graph_dev_and_peer_deps() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create pnpm-workspace.yaml - let workspace_yaml = r#"packages: - - "packages/*" -"#; - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), workspace_yaml).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create package A - fs::create_dir_all(temp_dir_path.join("packages/pkg-a")).unwrap(); - let pkg_a = serde_json::json!({ - "name": "pkg-a" - }); - fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a.to_string()).unwrap(); - - // Create package B - fs::create_dir_all(temp_dir_path.join("packages/pkg-b")).unwrap(); - let pkg_b = serde_json::json!({ - "name": "pkg-b" - }); - fs::write(temp_dir_path.join("packages/pkg-b/package.json"), pkg_b.to_string()).unwrap(); - - // Create package C that depends on A and B with different types - fs::create_dir_all(temp_dir_path.join("packages/pkg-c")).unwrap(); - let pkg_c = serde_json::json!({ - "name": "pkg-c", - "dependencies": { - "pkg-a": "workspace:*" - }, - "devDependencies": { - "pkg-b": "workspace:^1.0.0" - } - }); - fs::write(temp_dir_path.join("packages/pkg-c/package.json"), pkg_c.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have correct edge types - let mut found_normal_dep = false; - let mut found_dev_dep = false; - for edge_ref in graph.edge_references() { - let source = &graph[edge_ref.source()]; - let target = &graph[edge_ref.target()]; - - if source.package_json.name == "pkg-c" && target.package_json.name == "pkg-a" { - assert_eq!(*edge_ref.weight(), DependencyType::Normal); - found_normal_dep = true; - } - if source.package_json.name == "pkg-c" && target.package_json.name == "pkg-b" { - assert_eq!(*edge_ref.weight(), DependencyType::Dev); - found_dev_dep = true; - } - } - assert!(found_normal_dep, "Should have found normal dependency"); - assert!(found_dev_dep, "Should have found dev dependency"); - } - - #[test] - fn test_get_package_graph_duplicate_names() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create pnpm-workspace.yaml - let workspace_yaml = r#"packages: - - "packages/*" -"#; - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), workspace_yaml).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create first package with name "duplicate" - fs::create_dir_all(temp_dir_path.join("packages/pkg-1")).unwrap(); - let pkg_1 = serde_json::json!({ - "name": "duplicate" - }); - fs::write(temp_dir_path.join("packages/pkg-1/package.json"), pkg_1.to_string()).unwrap(); - - // Create second package with same name "duplicate" - fs::create_dir_all(temp_dir_path.join("packages/pkg-2")).unwrap(); - let pkg_2 = serde_json::json!({ - "name": "duplicate" - }); - fs::write(temp_dir_path.join("packages/pkg-2/package.json"), pkg_2.to_string()).unwrap(); - - // duplicate package names is allowed. - let result = discover_package_graph(temp_dir_path); - assert!(result.is_ok()); - } - - #[test] - fn test_get_package_graph_nameless_packages() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create pnpm-workspace.yaml - let workspace_yaml = r#"packages: - - "packages/*" -"#; - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), workspace_yaml).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create package without name - fs::create_dir_all(temp_dir_path.join("packages/nameless")).unwrap(); - let nameless = serde_json::json!({ - "dependencies": { - "some-lib": "^1.0.0" - } - }); - fs::write(temp_dir_path.join("packages/nameless/package.json"), nameless.to_string()) - .unwrap(); - - // Create package that tries to depend on nameless package - fs::create_dir_all(temp_dir_path.join("packages/pkg-a")).unwrap(); - let pkg_a = serde_json::json!({ - "name": "pkg-a", - "dependencies": { - "": "workspace:*" // Trying to depend on nameless package - } - }); - fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have 3 nodes (nameless, pkg-a, root) but no edges (nameless package can't be referenced) - assert_eq!(graph.node_count(), 3); - assert_eq!(graph.edge_count(), 0); - } - - #[test] - fn test_get_package_graph_workspace_protocol_with_version() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create pnpm-workspace.yaml - let workspace_yaml = r#"packages: - - "packages/*" -"#; - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), workspace_yaml).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create package A - fs::create_dir_all(temp_dir_path.join("packages/pkg-a")).unwrap(); - let pkg_a = serde_json::json!({ - "name": "@scope/pkg-a", - "version": "1.0.0" - }); - fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a.to_string()).unwrap(); - - // Create package B that depends on A with specific workspace version - fs::create_dir_all(temp_dir_path.join("packages/pkg-b")).unwrap(); - let pkg_b = serde_json::json!({ - "name": "pkg-b", - "dependencies": { - "@scope/pkg-a": "workspace:@scope/pkg-a@^1.0.0" - } - }); - fs::write(temp_dir_path.join("packages/pkg-b/package.json"), pkg_b.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should correctly parse workspace protocol with version - let mut found_edge = false; - for edge_ref in graph.edge_references() { - let source = &graph[edge_ref.source()]; - let target = &graph[edge_ref.target()]; - if source.package_json.name == "pkg-b" && target.package_json.name == "@scope/pkg-a" { - found_edge = true; - } - } - assert!(found_edge, "Should have found edge from pkg-b to @scope/pkg-a"); - } - - #[test] - fn test_get_package_graph_circular_dependencies() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create pnpm-workspace.yaml - let workspace_yaml = r#"packages: - - "packages/*" -"#; - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), workspace_yaml).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create package A that depends on B - fs::create_dir_all(temp_dir_path.join("packages/pkg-a")).unwrap(); - let pkg_a = serde_json::json!({ - "name": "pkg-a", - "dependencies": { - "pkg-b": "workspace:*" - } - }); - fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a.to_string()).unwrap(); - - // Create package B that depends on A (circular) - fs::create_dir_all(temp_dir_path.join("packages/pkg-b")).unwrap(); - let pkg_b = serde_json::json!({ - "name": "pkg-b", - "dependencies": { - "pkg-a": "workspace:*" - } - }); - fs::write(temp_dir_path.join("packages/pkg-b/package.json"), pkg_b.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have 3 nodes (pkg-a, pkg-b, root) and 2 edges (circular between a and b) - assert_eq!(graph.node_count(), 3); - assert_eq!(graph.edge_count(), 2); - - // Verify both edges exist - let mut found_a_to_b = false; - let mut found_b_to_a = false; - for edge_ref in graph.edge_references() { - let source = &graph[edge_ref.source()]; - let target = &graph[edge_ref.target()]; - - if source.package_json.name == "pkg-a" && target.package_json.name == "pkg-b" { - found_a_to_b = true; - } - if source.package_json.name == "pkg-b" && target.package_json.name == "pkg-a" { - found_b_to_a = true; - } - } - assert!(found_a_to_b && found_b_to_a, "Should have found circular dependencies"); - } - - #[test] - fn test_get_package_graph_missing_root_package_with_globs() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create pnpm-workspace.yaml that doesn't include root - let workspace_yaml = r#"packages: - - "packages/*" -"#; - fs::write(temp_dir_path.join("pnpm-workspace.yaml"), workspace_yaml).unwrap(); - - // Create root package.json that won't be included by glob - let root_package = serde_json::json!({ - "name": "root", - "private": true - }); - fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); - - // Create packages directory with one package - fs::create_dir_all(temp_dir_path.join("packages/pkg-a")).unwrap(); - let pkg_a = serde_json::json!({ - "name": "pkg-a" - }); - fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have both root and pkg-a (root added automatically) - assert_eq!(graph.node_count(), 2); - - let mut found_root = false; - let mut found_pkg_a = false; - for node in graph.node_weights() { - if node.package_json.name == "root" && node.path.as_str() == "" { - found_root = true; - } - if node.package_json.name == "pkg-a" { - found_pkg_a = true; - } - } - assert!(found_root, "Should have found root package"); - assert!(found_pkg_a, "Should have found pkg-a"); - } - - #[test] - fn test_get_package_graph_npm_workspace() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create package.json with workspaces field (npm workspace) - let root_package = serde_json::json!({ - "name": "npm-monorepo", - "private": true, - "workspaces": ["./packages/*", "./apps/*"] - }); - fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); - - // Create packages directory structure - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - fs::create_dir_all(temp_dir_path.join("apps")).unwrap(); - - // Create shared library package - fs::create_dir_all(temp_dir_path.join("packages/shared")).unwrap(); - let shared_pkg = serde_json::json!({ - "name": "@myorg/shared", - "version": "1.0.0" - }); - fs::write(temp_dir_path.join("packages/shared/package.json"), shared_pkg.to_string()) - .unwrap(); - - // Create UI library that depends on shared - fs::create_dir_all(temp_dir_path.join("packages/ui")).unwrap(); - let ui_pkg = serde_json::json!({ - "name": "@myorg/ui", - "version": "1.0.0", - "dependencies": { - "@myorg/shared": "workspace:*" - } - }); - fs::write(temp_dir_path.join("packages/ui/package.json"), ui_pkg.to_string()).unwrap(); - - // Create app that depends on both packages - fs::create_dir_all(temp_dir_path.join("apps/web")).unwrap(); - let web_app = serde_json::json!({ - "name": "web-app", - "version": "0.1.0", - "dependencies": { - "@myorg/shared": "workspace:*", - "@myorg/ui": "workspace:^1.0.0" - } - }); - fs::write(temp_dir_path.join("apps/web/package.json"), web_app.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have 4 nodes: root + shared + ui + web-app - assert_eq!(graph.node_count(), 4); - - // Verify packages were found - let mut packages_found = FxHashSet::::default(); - for node in graph.node_weights() { - packages_found.insert(node.package_json.name.clone()); - } - assert!(packages_found.contains("npm-monorepo")); - assert!(packages_found.contains("@myorg/shared")); - assert!(packages_found.contains("@myorg/ui")); - assert!(packages_found.contains("web-app")); - - // Verify dependency edges - let mut found_ui_to_shared = false; - let mut found_web_to_shared = false; - let mut found_web_to_ui = false; - - for edge_ref in graph.edge_references() { - let source = &graph[edge_ref.source()]; - let target = &graph[edge_ref.target()]; - - if source.package_json.name == "@myorg/ui" - && target.package_json.name == "@myorg/shared" - { - found_ui_to_shared = true; - } - if source.package_json.name == "web-app" && target.package_json.name == "@myorg/shared" - { - found_web_to_shared = true; - } - if source.package_json.name == "web-app" && target.package_json.name == "@myorg/ui" { - found_web_to_ui = true; - } - } - - assert!(found_ui_to_shared, "UI should depend on shared"); - assert!(found_web_to_shared, "Web app should depend on shared"); - assert!(found_web_to_ui, "Web app should depend on UI"); - } - - #[test] - fn test_get_package_graph_npm_workspace_with_root_relative_prefixes() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - let root_package = serde_json::json!({ - "name": "npm-monorepo", - "private": true, - "workspaces": [ - "./packages/single-slash", - ".//packages/double-slash", - ".///packages/triple-slash", - "/packages/rooted", - "//packages/double-rooted", - "!.///packages/excluded", - "!!./packages/even-negations", - "./packages/odd-negations", - "!!!.//packages/odd-negations" - ] - }); - fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); - - let package_names = [ - "single-slash", - "double-slash", - "triple-slash", - "rooted", - "double-rooted", - "even-negations", - ]; - for package_name in package_names.iter().copied().chain(["excluded", "odd-negations"]) { - let package_path = temp_dir_path.join("packages").join(package_name); - fs::create_dir_all(&package_path).unwrap(); - fs::write( - package_path.join("package.json"), - serde_json::json!({ "name": package_name }).to_string(), - ) - .unwrap(); - } - - let graph = discover_package_graph(temp_dir_path).unwrap(); - let packages_found: FxHashSet<_> = - graph.node_weights().map(|node| node.package_json.name.as_str()).collect(); - - assert_eq!(graph.node_count(), package_names.len() + 1); - assert!(packages_found.contains("npm-monorepo")); - for package_name in package_names { - assert!(packages_found.contains(package_name), "Should have found {package_name}"); - } - assert!(!packages_found.contains("excluded")); - assert!(!packages_found.contains("odd-negations")); - } - - #[test] - fn test_get_package_graph_yarn_workspace() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create package.json with workspaces field (yarn workspace) - // Using the simple array format which is compatible with both yarn and npm - let root_package = serde_json::json!({ - "name": "yarn-monorepo", - "private": true, - "workspaces": ["packages/*"] - }); - fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create core package - fs::create_dir_all(temp_dir_path.join("packages/core")).unwrap(); - let core_pkg = serde_json::json!({ - "name": "core", - "version": "2.0.0" - }); - fs::write(temp_dir_path.join("packages/core/package.json"), core_pkg.to_string()).unwrap(); - - // Create utils package that has peer dependency on core - fs::create_dir_all(temp_dir_path.join("packages/utils")).unwrap(); - let utils_pkg = serde_json::json!({ - "name": "utils", - "version": "1.5.0", - "peerDependencies": { - "core": "workspace:^2.0.0" - } - }); - fs::write(temp_dir_path.join("packages/utils/package.json"), utils_pkg.to_string()) - .unwrap(); - - // Create cli package that depends on both - fs::create_dir_all(temp_dir_path.join("packages/cli")).unwrap(); - let cli_pkg = serde_json::json!({ - "name": "cli-tool", - "version": "0.5.0", - "dependencies": { - "core": "workspace:*" - }, - "devDependencies": { - "utils": "workspace:*" - } - }); - fs::write(temp_dir_path.join("packages/cli/package.json"), cli_pkg.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have 4 nodes: root + core + utils + cli-tool - assert_eq!(graph.node_count(), 4); - - // Verify all packages were found - let mut packages_found = FxHashSet::::default(); - for node in graph.node_weights() { - packages_found.insert(node.package_json.name.clone()); - } - assert!(packages_found.contains("yarn-monorepo")); - assert!(packages_found.contains("core")); - assert!(packages_found.contains("utils")); - assert!(packages_found.contains("cli-tool")); - - // Verify dependency edges and their types - let mut found_utils_peer_core = false; - let mut found_cli_dep_core = false; - let mut found_cli_dev_utils = false; - - for edge_ref in graph.edge_references() { - let source = &graph[edge_ref.source()]; - let target = &graph[edge_ref.target()]; - - if source.package_json.name == "utils" && target.package_json.name == "core" { - assert_eq!(*edge_ref.weight(), DependencyType::Peer); - found_utils_peer_core = true; - } - if source.package_json.name == "cli-tool" && target.package_json.name == "core" { - assert_eq!(*edge_ref.weight(), DependencyType::Normal); - found_cli_dep_core = true; - } - if source.package_json.name == "cli-tool" && target.package_json.name == "utils" { - assert_eq!(*edge_ref.weight(), DependencyType::Dev); - found_cli_dev_utils = true; - } - } - - assert!(found_utils_peer_core, "Utils should have peer dependency on core"); - assert!(found_cli_dep_core, "CLI should depend on core"); - assert!(found_cli_dev_utils, "CLI should have dev dependency on utils"); - } - - #[test] - fn test_get_package_graph_npm_workspace_with_exclusions() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create package.json with workspaces field including exclusions - let root_package = serde_json::json!({ - "name": "npm-workspace-exclusions", - "private": true, - "workspaces": [ - "packages/*", - "!packages/experimental", - "!packages/*.backup" - ] - }); - fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create normal package - fs::create_dir_all(temp_dir_path.join("packages/normal")).unwrap(); - let normal_pkg = serde_json::json!({ - "name": "normal-package" - }); - fs::write(temp_dir_path.join("packages/normal/package.json"), normal_pkg.to_string()) - .unwrap(); - - // Create experimental package (should be excluded) - fs::create_dir_all(temp_dir_path.join("packages/experimental")).unwrap(); - let experimental_pkg = serde_json::json!({ - "name": "experimental-package" - }); - fs::write( - temp_dir_path.join("packages/experimental/package.json"), - experimental_pkg.to_string(), - ) - .unwrap(); - - // Create backup package (should be excluded by pattern) - fs::create_dir_all(temp_dir_path.join("packages/old.backup")).unwrap(); - let backup_pkg = serde_json::json!({ - "name": "backup-package" - }); - fs::write(temp_dir_path.join("packages/old.backup/package.json"), backup_pkg.to_string()) - .unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Check which packages were included - let mut packages_found = FxHashSet::::default(); - for node in graph.node_weights() { - packages_found.insert(node.package_json.name.clone()); - } - - assert!(packages_found.contains("npm-workspace-exclusions"), "Root should be included"); - assert!(packages_found.contains("normal-package"), "Normal package should be included"); - - // Note: As identified in the pnpm exclusion test, exclusions might not work correctly - // with the current implementation. This test documents the expected behavior. - } - - #[test] - fn test_get_package_graph_mixed_workspace_protocols() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create package.json with workspaces (npm/yarn style) - let root_package = serde_json::json!({ - "name": "mixed-protocols", - "private": true, - "workspaces": ["libs/*", "services/*"] - }); - fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); - - // Create directory structure - fs::create_dir_all(temp_dir_path.join("libs")).unwrap(); - fs::create_dir_all(temp_dir_path.join("services")).unwrap(); - - // Create a library with specific version - fs::create_dir_all(temp_dir_path.join("libs/database")).unwrap(); - let db_pkg = serde_json::json!({ - "name": "@company/database", - "version": "3.2.1" - }); - fs::write(temp_dir_path.join("libs/database/package.json"), db_pkg.to_string()).unwrap(); - - // Create service with various workspace protocol formats - fs::create_dir_all(temp_dir_path.join("services/api")).unwrap(); - let api_pkg = serde_json::json!({ - "name": "api-service", - "dependencies": { - // Different workspace protocol formats - "@company/database": "workspace:*", - "external-lib": "^1.0.0" // External dependency (not workspace) - } - }); - fs::write(temp_dir_path.join("services/api/package.json"), api_pkg.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Verify packages - assert_eq!(graph.node_count(), 3); // root + database + api - - // Verify workspace dependency exists but not external dependency - let mut found_workspace_dep = false; - for edge_ref in graph.edge_references() { - let source = &graph[edge_ref.source()]; - let target = &graph[edge_ref.target()]; - - if source.package_json.name == "api-service" - && target.package_json.name == "@company/database" - { - found_workspace_dep = true; - } - } - - assert!(found_workspace_dep, "Should have found workspace dependency"); - - // External dependencies should not create edges - assert_eq!(graph.edge_count(), 1, "Should only have one edge for workspace dependency"); - } - - #[test] - fn test_get_package_graph_npm_workspace_object_form() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create package.json with object-form workspaces (Bun/Yarn classic style) - let root_package = serde_json::json!({ - "name": "bun-monorepo", - "private": true, - "workspaces": { - "packages": ["packages/*", "apps/*"] - } - }); - fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); - - // Create packages directory structure - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - fs::create_dir_all(temp_dir_path.join("apps")).unwrap(); - - // Create shared library package - fs::create_dir_all(temp_dir_path.join("packages/shared")).unwrap(); - let shared_pkg = serde_json::json!({ - "name": "@myorg/shared", - "version": "1.0.0" - }); - fs::write(temp_dir_path.join("packages/shared/package.json"), shared_pkg.to_string()) - .unwrap(); - - // Create app that depends on shared - fs::create_dir_all(temp_dir_path.join("apps/web")).unwrap(); - let web_app = serde_json::json!({ - "name": "web-app", - "version": "0.1.0", - "dependencies": { - "@myorg/shared": "workspace:*" - } - }); - fs::write(temp_dir_path.join("apps/web/package.json"), web_app.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have 3 nodes: root + shared + web-app - assert_eq!(graph.node_count(), 3); - - // Verify packages were found - let mut packages_found = FxHashSet::::default(); - for node in graph.node_weights() { - packages_found.insert(node.package_json.name.clone()); - } - assert!(packages_found.contains("bun-monorepo")); - assert!(packages_found.contains("@myorg/shared")); - assert!(packages_found.contains("web-app")); - - // Verify dependency edge - let mut found_web_to_shared = false; - for edge_ref in graph.edge_references() { - let source = &graph[edge_ref.source()]; - let target = &graph[edge_ref.target()]; - if source.package_json.name == "web-app" && target.package_json.name == "@myorg/shared" - { - found_web_to_shared = true; - } - } - assert!(found_web_to_shared, "Web app should depend on shared"); - } - - #[test] - fn test_get_package_graph_bun_workspace_with_catalog() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - - // Create package.json with Bun catalog in object-form workspaces - let root_package = serde_json::json!({ - "name": "bun-catalog-monorepo", - "private": true, - "workspaces": { - "packages": ["packages/*"], - "catalog": { - "react": "^19.0.0", - "vite": "npm:@voidzero-dev/vite-plus-core@latest" - } - } - }); - fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); - - // Create packages directory - fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); - - // Create a package - fs::create_dir_all(temp_dir_path.join("packages/app")).unwrap(); - let app_pkg = serde_json::json!({ - "name": "my-app", - "dependencies": { - "react": "catalog:" - } - }); - fs::write(temp_dir_path.join("packages/app/package.json"), app_pkg.to_string()).unwrap(); - - let graph = discover_package_graph(temp_dir_path).unwrap(); - - // Should have 2 nodes: root + app (catalog field is silently ignored) - assert_eq!(graph.node_count(), 2); - - let mut packages_found = FxHashSet::::default(); - for node in graph.node_weights() { - packages_found.insert(node.package_json.name.clone()); - } - assert!(packages_found.contains("bun-catalog-monorepo")); - assert!(packages_found.contains("my-app")); - } -} diff --git a/crates/vite_workspace/src/package.rs b/crates/vite_workspace/src/package.rs deleted file mode 100644 index 25cc65089..000000000 --- a/crates/vite_workspace/src/package.rs +++ /dev/null @@ -1,71 +0,0 @@ -use rustc_hash::FxHashMap; -use serde::{Deserialize, Serialize}; -use vite_str::Str; - -#[derive(Copy, Clone, Debug, Serialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub enum DependencyType { - Normal, - Dev, - Peer, -} - -#[derive(Serialize, Deserialize, Clone, Default)] -#[serde(rename_all = "camelCase")] -pub struct PackageJson { - #[serde(default)] - pub name: Str, - #[serde(default)] - pub scripts: FxHashMap, - #[serde(default)] - pub dependencies: FxHashMap, - #[serde(default)] - pub dev_dependencies: FxHashMap, - #[serde(default)] - pub peer_dependencies: FxHashMap, -} - -impl std::fmt::Debug for PackageJson { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if std::env::var("VITE_DEBUG_VERBOSE").is_ok_and(|v| v != "0" && v != "false") { - write!( - f, - "PackageJson {{ name: {:?}, scripts: {:?}, dependencies: {:?}, dev_dependencies: {:?}, peer_dependencies: {:?} }}", - self.name, - self.scripts, - self.dependencies, - self.dev_dependencies, - self.peer_dependencies - ) - } else { - write!(f, "PackageJson {{ name: {:?}, scripts: {:?} }}", self.name, self.scripts) - } - } -} - -impl PackageJson { - pub fn get_workspace_dependencies( - &self, - ) -> impl Iterator + use<'_> { - self.dependencies - .iter() - .map(|entry| (entry, DependencyType::Normal)) - .chain(self.dev_dependencies.iter().map(|entry| (entry, DependencyType::Dev))) - .chain(self.peer_dependencies.iter().map(|entry| (entry, DependencyType::Peer))) - .filter_map(|((key, value), dep_type)| { - let Some(workspace_version) = value.strip_prefix("workspace:") else { - // TODO: support link-workspace-packages: https://pnpm.io/workspaces#workspace-protocol-workspace) - return None; - }; - // TODO: support paths: https://github.com/pnpm/pnpm/pull/2972 - Some(( - if let Some((name, _)) = workspace_version.rsplit_once('@') { - name.into() - } else { - key.clone() - }, - dep_type, - )) - }) - } -} diff --git a/crates/vite_workspace/src/package_filter.rs b/crates/vite_workspace/src/package_filter.rs deleted file mode 100644 index d3daa5f8e..000000000 --- a/crates/vite_workspace/src/package_filter.rs +++ /dev/null @@ -1,1460 +0,0 @@ -//! Package filter types and parsing for pnpm-style `--filter` selectors. -//! -//! # Design -//! -//! Package selection is deliberately separated from task matching (two-stage model). -//! This module handles only Stage 1: which packages to include/exclude. -//! Stage 2 (which tasks to run in those packages) lives in `vite_task_graph`. -//! -//! # Filter syntax -//! -//! Follows pnpm's `--filter` specification: -//! -//! - `foo` → exact package name -//! - `@scope/*` → glob pattern -//! - `./path` → packages whose root is at or under this directory (one-way) -//! - `{./path}` → same, brace syntax -//! - `name{./dir}` → name AND directory (intersection) -//! - `foo...` → foo + its transitive dependencies -//! - `...foo` → foo + its transitive dependents -//! - `foo^...` → foo's dependencies only (exclude foo itself) -//! - `...^foo` → foo's dependents only (exclude foo itself) -//! - `...foo...` → foo + dependencies + dependents -//! - `!foo` → exclude foo from results -//! -//! The `ContainingPackage` selector variant is NOT produced by `parse_filter`. -//! It is synthesized internally for `vp run build` (implicit cwd) and `vp run -t build` -//! to walk up the directory tree and find the package that contains the given path. -//! This mirrors pnpm's `findPrefix` behaviour (not [`parsePackageSelector`] behaviour). -//! -//! [`parsePackageSelector`]: https://github.com/pnpm/pnpm/blob/05dd45ea82fff9c0b687cdc8f478a1027077d343/workspace/filter-workspace-packages/src/parsePackageSelector.ts#L14-L61 - -use std::{ops::Deref, sync::Arc}; - -use vec1::Vec1; -use vite_path::AbsolutePath; -use vite_str::Str; - -use crate::package_graph::PackageQuery; - -/// Compiled glob pattern with its source string preserved for equality comparison. -/// -/// `wax::Glob` does not implement `PartialEq`, so this wrapper compares -/// patterns by their source string representation. -#[derive(Debug, Clone)] -pub(crate) struct GlobPattern { - glob: wax::Glob<'static>, - source: Str, -} - -impl GlobPattern { - pub(crate) const fn new(glob: wax::Glob<'static>, source: Str) -> Self { - Self { glob, source } - } -} - -impl PartialEq for GlobPattern { - fn eq(&self, other: &Self) -> bool { - self.source == other.source - } -} - -impl Deref for GlobPattern { - type Target = wax::Glob<'static>; - - fn deref(&self) -> &Self::Target { - &self.glob - } -} - -// ──────────────────────────────────────────────────────────────────────────── -// Types -// ──────────────────────────────────────────────────────────────────────────── - -/// Exact name or glob pattern for matching package names. -#[derive(Debug, Clone, PartialEq)] -pub(crate) enum PackageNamePattern { - /// Exact name (e.g. `foo`, `@scope/pkg`). O(1) hash lookup. - /// - /// Scoped auto-completion applies during resolution: if `"bar"` has no exact match - /// but exactly one `@*/bar` package exists, that package is matched. - /// pnpm ref: - /// - /// When `unique` is true, resolution errors if multiple packages share the - /// name. Set for `pkg#task` CLI specifiers; false for `--filter`. - Exact { name: Str, unique: bool }, - - /// Glob pattern (e.g. `@scope/*`, `*-utils`). Iterates all packages. - /// - /// Only `*` and `?` wildcards are supported (pnpm semantics). - /// Stored as an owned `Glob<'static>` so the filter can outlive the input string. - Glob(Box), -} - -/// Directory matching pattern for `--filter` selectors. -/// -/// Follows pnpm v7+ glob-dir semantics: plain paths are exact-only, -/// `*` / `**` opt in to descendant matching. -/// -/// pnpm ref: -#[derive(Debug, Clone, PartialEq)] -pub(crate) enum DirectoryPattern { - /// Exact path match (no glob metacharacters in selector). - Exact(Arc), - - /// Glob: resolved base directory (non-glob prefix) + relative glob pattern. - /// - /// Matching strips `base` from a candidate path, then tests the remainder - /// against `pattern`. For example, `./packages/*` with cwd `/ws` produces - /// `base = /ws/packages`, `pattern = *`, which matches `/ws/packages/app` - /// (remainder `app` matches `*`). - Glob { base: Arc, pattern: Box }, -} - -/// What packages to initially match. -/// -/// The enum prevents the all-`None` invalid state that would arise from a struct -/// with all optional fields (as in pnpm's independent optional fields). -#[derive(Debug, Clone, PartialEq)] -pub(crate) enum PackageSelector { - /// Match by name only. Produced by `--filter foo` or `--filter "@scope/*"`. - Name(PackageNamePattern), - - /// Match by directory. Produced by `--filter .`, `--filter ./path`, `--filter {dir}`. - /// - /// Uses pnpm v7+ glob-dir semantics: plain paths are exact-match only, - /// `*` / `**` globs opt in to descendant matching. - Directory(DirectoryPattern), - - /// Find the package that **contains** this path (walks up the directory tree). - /// - /// Produced internally for `vp run build` (implicit cwd) and `vp run -t build`. - /// Uses `IndexedPackageGraph::get_package_index_from_cwd` semantics. - /// Never produced by `parse_filter`. - ContainingPackage(Arc), - - /// Match by name AND directory (intersection). - /// Produced by `--filter "pattern{./dir}"`. - NameAndDirectory { name: PackageNamePattern, directory: DirectoryPattern }, - - /// Select the workspace root package (the package with empty relative path). - /// Produced by `-w` / `--workspace-root`. - WorkspaceRoot, -} - -/// Direction to traverse the package dependency graph from the initially matched packages. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum TraversalDirection { - /// Transitive dependencies (outgoing edges). Produced by `foo...`. - Dependencies, - - /// Transitive dependents (incoming edges). Produced by `...foo`. - Dependents, - - /// Both: walk dependents first, then walk all dependencies of every found dependent. - /// Produced by `...foo...`. - /// pnpm ref: - Both, -} - -/// Graph traversal specification: how to expand from the initially matched packages. -/// -/// Only present when `...` appears in the filter. The absence of this struct prevents -/// the invalid state of `exclude_self = true` without any expansion. -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct GraphTraversal { - pub(crate) direction: TraversalDirection, - - /// Exclude the initially matched packages from the result. - /// - /// Produced by `^` in `foo^...` (keep dependencies, drop foo) - /// or `...^foo` (keep dependents, drop foo). - pub(crate) exclude_self: bool, -} - -/// A single package filter, corresponding to one `--filter` argument. -/// -/// Multiple filters are composed at the `PackageQuery` level: -/// inclusions are unioned, then exclusions are subtracted. -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct PackageFilter { - /// When `true`, packages matching this filter are **excluded** from the result. - /// Produced by a leading `!` in the filter string. - pub(crate) exclude: bool, - - /// Which packages to initially match. - pub(crate) selector: PackageSelector, - - /// Optional graph expansion from the initial match. - /// `None` = exact match only (no traversal). - pub(crate) traversal: Option, - - /// Original `--filter` token that produced this filter. - /// `None` for synthetic filters (implicit cwd, package name, `-w`). - pub(crate) source: Option, -} - -// ──────────────────────────────────────────────────────────────────────────── -// Error -// ──────────────────────────────────────────────────────────────────────────── - -/// Errors that can occur when parsing a `--filter` string. -#[derive(Debug, thiserror::Error)] -pub enum PackageFilterParseError { - #[error("Empty filter selector")] - EmptySelector, - - #[error("Invalid glob pattern: {0}")] - InvalidGlob(#[from] wax::BuildError), -} - -// ──────────────────────────────────────────────────────────────────────────── -// CLI package query -// ──────────────────────────────────────────────────────────────────────────── - -/// Errors that can occur when converting [`PackageQueryArgs`] into a [`PackageQuery`]. -#[derive(Debug, thiserror::Error)] -pub enum PackageQueryError { - #[error("--recursive and --transitive cannot be used together")] - RecursiveTransitiveConflict, - - #[error("--filter and --transitive cannot be used together")] - FilterWithTransitive, - - #[error("--filter and --recursive cannot be used together")] - FilterWithRecursive, - - #[error("cannot specify package name with --recursive")] - PackageNameWithRecursive { package_name: Str }, - - #[error("cannot specify package name with --filter")] - PackageNameWithFilter { package_name: Str }, - - #[error("cannot specify package name with --workspace-root")] - PackageNameWithWorkspaceRoot { package_name: Str }, - - #[error("--filter value is empty")] - EmptyFilter, - - #[error("invalid --filter expression")] - InvalidFilter(#[from] PackageFilterParseError), -} - -/// CLI arguments for selecting which packages a command applies to. -/// -/// Use `#[clap(flatten)]` to embed these in a parent clap struct. -/// Call [`into_package_query`](Self::into_package_query) to convert into an opaque [`PackageQuery`]. -#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] -#[expect(clippy::struct_excessive_bools, reason = "CLI flags are naturally boolean")] -pub struct PackageQueryArgs { - /// Select all packages in the workspace. - #[clap(default_value = "false", short, long)] - recursive: bool, - - /// Select the current package and its transitive dependencies. - #[clap(default_value = "false", short, long)] - transitive: bool, - - /// Select the workspace root package. - #[clap(default_value = "false", short = 'w', long = "workspace-root")] - workspace_root: bool, - - /// Match packages by name, directory, or glob pattern. - #[clap( - short = 'F', - long = "filter", - num_args = 1, - long_help = "\ -Match packages by name, directory, or glob pattern. - - --filter Select by package name (e.g. foo, @scope/*) - --filter ./ Select packages under a directory - --filter {} Same as ./, but allows traversal suffixes - --filter ... Select package and its dependencies - --filter ... Select package and its dependents - --filter ^... Select only the dependencies (exclude the package itself) - --filter ! Exclude packages matching the pattern" - )] - filters: Vec, - - /// Exit with a non-zero status if a `--filter` expression matches no packages. - /// - /// Without this flag, an unmatched filter (a typo, an empty glob, or a - /// traversal like `{.}^...` that collapses to zero on a leaf package) only - /// produces a warning and the command exits successfully. - #[clap(long = "fail-if-no-match", default_value = "false")] - pub fail_if_no_match: bool, -} - -impl PackageQueryArgs { - /// Convert CLI arguments into an opaque [`PackageQuery`]. - /// - /// `package_name` is the optional package name from a `package#task` specifier. - /// `cwd` is the working directory (used as fallback when no package name or filter is given). - /// - /// Returns `(query, is_cwd_only)` where `is_cwd_only` is `true` when the query - /// falls through to the implicit-cwd path (no `-r`, `-t`, `-w`, `--filter`, - /// or explicit package name). - /// - /// # Errors - /// - /// Returns [`PackageQueryError`] if conflicting flags are set, a package name - /// is specified with `--recursive` or `--filter`, or a filter expression is invalid. - #[expect(clippy::too_many_lines, reason = "single exhaustive match")] - pub fn into_package_query( - self, - package_name: Option, - cwd: &Arc, - ) -> Result<(PackageQuery, bool), PackageQueryError> { - // `fail_if_no_match` is read directly from the field before this - // method consumes `self`; it does not affect query construction. - let Self { recursive, transitive, workspace_root, filters, fail_if_no_match: _ } = self; - - // Collect filter tokens from all `--filter` arguments, splitting on whitespace. - let mut filter_tokens = Vec::::with_capacity(filters.len()); - for filter in filters { - let mut is_empty = true; - for filter_token in filter.split_ascii_whitespace() { - is_empty = false; - filter_tokens.push(filter_token.into()); - } - // Error if any --filter value is empty or whitespace-only. - if is_empty { - return Err(PackageQueryError::EmptyFilter); - } - } - // We have checked that filter_tokens is non-empty if any filters were provided, - // If no tokens are collected, it means no filters were provided. - let filter_tokens: Option> = Vec1::try_from_vec(filter_tokens).ok(); - - // Error arms only match the conflicting fields (wildcards for the rest). - // Success arms explicitly match every field — no wildcards. - match (recursive, transitive, workspace_root, filter_tokens, package_name) { - // ------------------------- error cases -------------------------------- - - // --recursive --transitive - (true, true, _, _, _) => Err(PackageQueryError::RecursiveTransitiveConflict), - // --recursive --filter - (true, _, _, Some(_), _) => Err(PackageQueryError::FilterWithRecursive), - // --recursive # - (true, false, _, _, Some(package_name)) => { - Err(PackageQueryError::PackageNameWithRecursive { package_name }) - } - // --transitive --filter - (false, true, _, Some(_), _) => Err(PackageQueryError::FilterWithTransitive), - // --filter # - (_, _, _, Some(_), Some(package_name)) => { - Err(PackageQueryError::PackageNameWithFilter { package_name }) - } - // --workspace-root # - (_, _, true, _, Some(package_name)) => { - Err(PackageQueryError::PackageNameWithWorkspaceRoot { package_name }) - } - - // ------------------------ success cases ------------------------------- - - // --recursive (--workspace-root is redundant) - (true, false, true | false, None, None) => Ok((PackageQuery::all(), false)), - // --filter [--workspace-root] - (false, false, workspace_root, Some(filter_tokens), None) => { - let mut parsed: Vec1 = - filter_tokens.try_mapped(|f| parse_filter(&f, cwd))?; - if workspace_root { - parsed.push(PackageFilter { - exclude: false, - selector: PackageSelector::WorkspaceRoot, - traversal: None, - source: None, - }); - } - Ok((PackageQuery::filters(parsed), false)) - } - // --workspace-root [--transitive] - (false, transitive, true, None, None) => { - let traversal = if transitive { - Some(GraphTraversal { - direction: TraversalDirection::Dependencies, - exclude_self: false, - }) - } else { - None - }; - Ok(( - PackageQuery::filters(Vec1::new(PackageFilter { - exclude: false, - selector: PackageSelector::WorkspaceRoot, - traversal, - source: None, - })), - false, - )) - } - // [--transitive] # - (false, transitive, false, None, Some(name)) => { - let traversal = if transitive { - Some(GraphTraversal { - direction: TraversalDirection::Dependencies, - exclude_self: false, - }) - } else { - None - }; - Ok(( - PackageQuery::filters(Vec1::new(PackageFilter { - exclude: false, - selector: PackageSelector::Name(PackageNamePattern::Exact { - name, - unique: true, - }), - traversal, - source: None, - })), - false, - )) - } - // --transitive - (false, true, false, None, None) => Ok(( - PackageQuery::filters(Vec1::new(PackageFilter { - exclude: false, - selector: PackageSelector::ContainingPackage(Arc::clone(cwd)), - traversal: Some(GraphTraversal { - direction: TraversalDirection::Dependencies, - exclude_self: false, - }), - source: None, - })), - false, - )), - // (no flags, implicit cwd) - (false, false, false, None, None) => Ok(( - PackageQuery::filters(Vec1::new(PackageFilter { - exclude: false, - selector: PackageSelector::ContainingPackage(Arc::clone(cwd)), - traversal: None, - source: None, - })), - true, - )), - } - } -} - -// ──────────────────────────────────────────────────────────────────────────── -// Parsing -// ──────────────────────────────────────────────────────────────────────────── - -/// Parse a `--filter` string into a [`PackageFilter`]. -/// -/// `cwd` is used to resolve relative paths (`.`, `./path`, `{./path}`). -/// -/// # Errors -/// -/// Returns [`PackageFilterParseError::EmptySelector`] if the core selector is empty, -/// or [`PackageFilterParseError::InvalidGlob`] if the pattern contains an invalid glob. -/// -/// # Syntax -/// -/// Follows pnpm's [`parsePackageSelector`] algorithm. See module-level docs for examples. -/// -/// [`parsePackageSelector`]: https://github.com/pnpm/pnpm/blob/05dd45ea82fff9c0b687cdc8f478a1027077d343/workspace/filter-workspace-packages/src/parsePackageSelector.ts#L14-L61 -pub(crate) fn parse_filter( - input: &str, - cwd: &AbsolutePath, -) -> Result { - // Step 1: strip leading `!` → exclusion filter. - let (exclude, rest) = - input.strip_prefix('!').map_or((false, input), |without_bang| (true, without_bang)); - - // Step 2: strip trailing `...` → transitive dependencies. - // Check for `^` immediately before `...` → exclude the seed packages themselves. - let (include_dependencies, deps_exclude_self, rest) = - rest.strip_suffix("...").map_or((false, false, rest), |before_dots| { - before_dots - .strip_suffix('^') - .map_or((true, false, before_dots), |before_caret| (true, true, before_caret)) - }); - - // Step 3: strip leading `...` → transitive dependents. - // Check for `^` immediately after `...` → exclude the seed packages themselves. - let (include_dependents, dependents_exclude_self, core) = - rest.strip_prefix("...").map_or((false, false, rest), |after_dots| { - after_dots - .strip_prefix('^') - .map_or((true, false, after_dots), |after_caret| (true, true, after_caret)) - }); - - // exclude_self is true if either direction had `^`. - let exclude_self = deps_exclude_self || dependents_exclude_self; - - // Step 4–5: build the traversal descriptor. - let traversal = match (include_dependencies, include_dependents) { - (false, false) => None, - (true, false) => { - Some(GraphTraversal { direction: TraversalDirection::Dependencies, exclude_self }) - } - (false, true) => { - Some(GraphTraversal { direction: TraversalDirection::Dependents, exclude_self }) - } - (true, true) => Some(GraphTraversal { direction: TraversalDirection::Both, exclude_self }), - }; - - // Step 6–9: parse the remaining core selector. - let (selector, supports_traversal) = parse_core_selector(core, cwd)?; - - // pnpm discards traversal on unbraced path selectors — `..` (parent dir) - // and `...` (traversal) are ambiguous. Braces disambiguate: `{./path}...`. - // Ref: https://github.com/pnpm/pnpm/issues/1651 - let traversal = if supports_traversal { traversal } else { None }; - - Ok(PackageFilter { exclude, selector, traversal, source: Some(Str::from(input)) }) -} - -/// Parse the core selector string (after stripping `!` and `...` markers). -/// -/// Implements pnpm's [`SELECTOR_REGEX`] logic: `^([^.][^{}[\]]*)?(\{[^}]+\})?$` -/// -/// [`SELECTOR_REGEX`]: https://github.com/pnpm/pnpm/blob/05dd45ea82fff9c0b687cdc8f478a1027077d343/workspace/filter-workspace-packages/src/parsePackageSelector.ts#L37 -/// -/// Decision tree: -/// 1. If the string ends with `}` and contains a `{`, split name and brace-directory. -/// The name part must not start with `.` for the brace split to be valid -/// (per the regex rule that Group 1 must not start with `.`). -/// 2. If the string starts with `.`, treat the whole thing as a relative path. -/// 3. Otherwise treat as a name pattern (exact or glob). -/// -/// Returns `(selector, supports_traversal)`. Unbraced `.`-prefix path selectors -/// return `false` because pnpm discards `...` traversal on them (ambiguity with `..`). -fn parse_core_selector( - core: &str, - cwd: &AbsolutePath, -) -> Result<(PackageSelector, bool), PackageFilterParseError> { - // Try to extract a brace-enclosed directory suffix: `{...}`. - // The name part before the brace must not start with `.` (pnpm regex Group 1 constraint). - if let Some(without_closing) = core.strip_suffix('}') - && let Some(brace_pos) = without_closing.rfind('{') - { - let name_part = &without_closing[..brace_pos]; - let dir_inner = &without_closing[brace_pos + 1..]; - - // Per pnpm's regex: Group 1 (`[^.][^{}[\]]*`) must NOT start with `.`. - // If name_part starts with `.`, fall through to the `.`-prefix check. - if !name_part.starts_with('.') { - let directory = resolve_directory_pattern(dir_inner, cwd)?; - - return if name_part.is_empty() { - // Only a directory selector: `{./foo}` or `{packages/app}`. - Ok((PackageSelector::Directory(directory), true)) - } else { - // Name and directory combined: `foo{./bar}`. - let name = build_name_pattern(name_part)?; - Ok((PackageSelector::NameAndDirectory { name, directory }, true)) - }; - } - // name_part starts with `.`: fall through — treat entire core as a relative path. - } - - // If the core starts with `.`, it's a relative path to a directory. - // This handles `.`, `..`, `./foo`, `../foo`, `./foo/*`, `./foo/**`. - // Traversal is NOT supported — pnpm discards `...` on unbraced path selectors. - if core.starts_with('.') { - let directory = resolve_directory_pattern(core, cwd)?; - return Ok((PackageSelector::Directory(directory), false)); - } - - // Guard against an empty selector reaching here. - if core.is_empty() { - return Err(PackageFilterParseError::EmptySelector); - } - - // Plain name or glob pattern. - Ok((PackageSelector::Name(build_name_pattern(core)?), true)) -} - -/// Resolve a directory selector string into a [`DirectoryPattern`]. -/// -/// Uses [`wax::Glob::partition`] to split into an invariant base path and an -/// optional glob pattern. If `partition` yields no pattern, the result is an -/// exact path match; otherwise it is a glob match. -fn resolve_directory_pattern( - path_str: &str, - cwd: &AbsolutePath, -) -> Result { - let glob = wax::Glob::new(path_str)?.into_owned(); - let (base_pathbuf, pattern) = glob.partition(); - let base_str = base_pathbuf.to_str().expect("filter paths are always valid UTF-8"); - let base = resolve_filter_path(if base_str.is_empty() { "." } else { base_str }, cwd); - - match pattern { - Some(pattern) => Ok(DirectoryPattern::Glob { - base, - pattern: Box::new(GlobPattern::new(pattern, path_str.into())), - }), - None => Ok(DirectoryPattern::Exact(base)), - } -} - -/// Resolve a path string relative to `cwd`, normalising away `.` and `..`. -/// -/// `path_str` may be `"."`, `".."`, `"./foo"`, `"../foo"`, or a bare name like `"packages/app"`. -/// -/// Uses lexical normalization (no filesystem access), which can produce incorrect -/// results when symlinks are involved (e.g. `/a/symlink/../b` → `/a/b`). This -/// matches pnpm's behaviour. -fn resolve_filter_path(path_str: &str, cwd: &AbsolutePath) -> Arc { - cwd.join(path_str).clean().into() -} - -/// Build a [`PackageNamePattern`] from a name or glob string. -/// -/// Uses [`wax::Glob::partition`] to determine if the pattern contains variant -/// (non-literal) components. If it does, the pattern is a glob; otherwise exact. -fn build_name_pattern(name: &str) -> Result { - let glob = wax::Glob::new(name)?.into_owned(); - if glob.clone().partition().1.is_some() { - Ok(PackageNamePattern::Glob(Box::new(GlobPattern::new(glob, name.into())))) - } else { - Ok(PackageNamePattern::Exact { name: name.into(), unique: false }) - } -} - -// ──────────────────────────────────────────────────────────────────────────── -// Unit tests -// ──────────────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - /// Construct an [`AbsolutePath`] from a Unix-style literal (test helper). - /// - /// On Windows, a `C:` prefix is prepended so `/workspace` becomes `C:/workspace`. - #[cfg_attr( - windows, - expect( - clippy::disallowed_macros, - reason = "test helper constructs Windows paths from Unix-style literals" - ) - )] - fn abs(path: &'static str) -> &'static AbsolutePath { - #[cfg(unix)] - { - AbsolutePath::new(path).expect("test path must be absolute") - } - #[cfg(windows)] - { - let leaked = Box::leak(std::format!("C:{path}").into_boxed_str()); - AbsolutePath::new(leaked).expect("test path must be absolute") - } - } - - // ── Helpers to assert selector shapes ─────────────────────────────────── - - fn assert_exact_name(filter: &PackageFilter, expected: &str) { - match &filter.selector { - PackageSelector::Name(PackageNamePattern::Exact { name: n, .. }) => { - assert_eq!(n.as_str(), expected, "exact name mismatch"); - } - other => panic!("expected Name(Exact({expected:?})), got {other:?}"), - } - } - - fn assert_glob_name(filter: &PackageFilter, expected_pattern: &str) { - match &filter.selector { - PackageSelector::Name(PackageNamePattern::Glob(g)) => { - assert_eq!(g.to_string(), expected_pattern, "glob pattern mismatch"); - } - other => panic!("expected Name(Glob({expected_pattern:?})), got {other:?}"), - } - } - - fn assert_directory(filter: &PackageFilter, expected_path: &AbsolutePath) { - match &filter.selector { - PackageSelector::Directory(DirectoryPattern::Exact(dir)) => { - assert_eq!(dir.as_ref(), expected_path, "directory mismatch"); - } - other => panic!("expected Directory(Exact({expected_path:?})), got {other:?}"), - } - } - - fn assert_directory_glob( - filter: &PackageFilter, - expected_base: &AbsolutePath, - expected_pattern: &str, - ) { - match &filter.selector { - PackageSelector::Directory(DirectoryPattern::Glob { base, pattern }) => { - assert_eq!(base.as_ref(), expected_base, "base mismatch"); - assert_eq!(pattern.to_string(), expected_pattern, "pattern mismatch"); - } - other => panic!( - "expected Directory(Glob {{ base: {expected_base:?}, pattern: {expected_pattern:?} }}), got {other:?}" - ), - } - } - - fn assert_name_and_directory( - filter: &PackageFilter, - expected_name: &str, - expected_dir: &AbsolutePath, - ) { - match &filter.selector { - PackageSelector::NameAndDirectory { - name: PackageNamePattern::Exact { name: n, .. }, - directory: DirectoryPattern::Exact(dir), - } => { - assert_eq!(n.as_str(), expected_name, "name mismatch"); - assert_eq!(dir.as_ref(), expected_dir, "directory mismatch"); - } - other => panic!( - "expected NameAndDirectory(Exact({expected_name:?}), Exact({expected_dir:?})), got {other:?}" - ), - } - } - - fn assert_name_and_directory_glob( - filter: &PackageFilter, - expected_name: &str, - expected_base: &AbsolutePath, - expected_pattern: &str, - ) { - match &filter.selector { - PackageSelector::NameAndDirectory { - name: PackageNamePattern::Exact { name: n, .. }, - directory: DirectoryPattern::Glob { base, pattern }, - } => { - assert_eq!(n.as_str(), expected_name, "name mismatch"); - assert_eq!(base.as_ref(), expected_base, "base mismatch"); - assert_eq!(pattern.to_string(), expected_pattern, "pattern mismatch"); - } - other => panic!( - "expected NameAndDirectory(Exact({expected_name:?}), Glob {{ base: {expected_base:?}, pattern: {expected_pattern:?} }}), got {other:?}" - ), - } - } - - fn assert_no_traversal(filter: &PackageFilter) { - assert!(filter.traversal.is_none(), "expected no traversal, got {:?}", filter.traversal); - } - - fn assert_traversal(filter: &PackageFilter, direction: TraversalDirection, exclude_self: bool) { - match &filter.traversal { - Some(t) => { - assert_eq!(t.direction, direction, "direction mismatch"); - assert_eq!(t.exclude_self, exclude_self, "exclude_self mismatch"); - } - None => panic!("expected traversal {direction:?}/{exclude_self}, got None"), - } - } - - // ── Tests ported from pnpm parsePackageSelector.ts ────────────────────── - - #[test] - fn exact_name() { - let cwd = abs("/workspace"); - let f = parse_filter("foo", cwd).unwrap(); - assert!(!f.exclude); - assert_exact_name(&f, "foo"); - assert_no_traversal(&f); - } - - #[test] - fn name_with_dependencies() { - let cwd = abs("/workspace"); - let f = parse_filter("foo...", cwd).unwrap(); - assert!(!f.exclude); - assert_exact_name(&f, "foo"); - assert_traversal(&f, TraversalDirection::Dependencies, false); - } - - #[test] - fn name_with_dependents() { - let cwd = abs("/workspace"); - let f = parse_filter("...foo", cwd).unwrap(); - assert!(!f.exclude); - assert_exact_name(&f, "foo"); - assert_traversal(&f, TraversalDirection::Dependents, false); - } - - #[test] - fn name_with_both_directions() { - let cwd = abs("/workspace"); - let f = parse_filter("...foo...", cwd).unwrap(); - assert!(!f.exclude); - assert_exact_name(&f, "foo"); - assert_traversal(&f, TraversalDirection::Both, false); - } - - #[test] - fn name_with_dependencies_exclude_self() { - let cwd = abs("/workspace"); - let f = parse_filter("foo^...", cwd).unwrap(); - assert!(!f.exclude); - assert_exact_name(&f, "foo"); - assert_traversal(&f, TraversalDirection::Dependencies, true); - } - - #[test] - fn name_with_dependents_exclude_self() { - let cwd = abs("/workspace"); - let f = parse_filter("...^foo", cwd).unwrap(); - assert!(!f.exclude); - assert_exact_name(&f, "foo"); - assert_traversal(&f, TraversalDirection::Dependents, true); - } - - #[test] - fn relative_path_dot_slash_foo() { - let cwd = abs("/workspace"); - let f = parse_filter("./foo", cwd).unwrap(); - assert!(!f.exclude); - assert_directory(&f, abs("/workspace/foo")); - assert_no_traversal(&f); - } - - #[test] - fn relative_path_dot() { - let cwd = abs("/workspace/packages/app"); - let f = parse_filter(".", cwd).unwrap(); - assert!(!f.exclude); - assert_directory(&f, abs("/workspace/packages/app")); - assert_no_traversal(&f); - } - - #[test] - fn relative_path_dotdot() { - let cwd = abs("/workspace/packages/app"); - let f = parse_filter("..", cwd).unwrap(); - assert!(!f.exclude); - assert_directory(&f, abs("/workspace/packages")); - assert_no_traversal(&f); - } - - #[test] - fn exclusion_prefix() { - let cwd = abs("/workspace"); - let f = parse_filter("!foo", cwd).unwrap(); - assert!(f.exclude); - assert_exact_name(&f, "foo"); - assert_no_traversal(&f); - } - - #[test] - fn brace_directory_relative_path() { - let cwd = abs("/workspace"); - let f = parse_filter("{./foo}", cwd).unwrap(); - assert!(!f.exclude); - assert_directory(&f, abs("/workspace/foo")); - assert_no_traversal(&f); - } - - #[test] - fn brace_directory_with_dependents() { - let cwd = abs("/workspace"); - let f = parse_filter("...{./foo}", cwd).unwrap(); - assert!(!f.exclude); - assert_directory(&f, abs("/workspace/foo")); - assert_traversal(&f, TraversalDirection::Dependents, false); - } - - #[test] - fn name_and_directory_combined() { - let cwd = abs("/workspace"); - let f = parse_filter("pattern{./dir}", cwd).unwrap(); - assert!(!f.exclude); - assert_name_and_directory(&f, "pattern", abs("/workspace/dir")); - assert_no_traversal(&f); - } - - #[test] - fn glob_pattern() { - let cwd = abs("/workspace"); - let f = parse_filter("@scope/*", cwd).unwrap(); - assert!(!f.exclude); - assert_glob_name(&f, "@scope/*"); - assert_no_traversal(&f); - } - - #[test] - fn empty_selector_error() { - let cwd = abs("/workspace"); - let err = parse_filter("", cwd).unwrap_err(); - assert!(matches!(err, PackageFilterParseError::EmptySelector)); - } - - /// A filter with only `!` (exclusion of empty selector) should also error. - #[test] - fn exclusion_with_empty_selector_error() { - let cwd = abs("/workspace"); - let err = parse_filter("!", cwd).unwrap_err(); - assert!(matches!(err, PackageFilterParseError::EmptySelector)); - } - - #[test] - fn scoped_package_name() { - let cwd = abs("/workspace"); - let f = parse_filter("@test/app", cwd).unwrap(); - assert_exact_name(&f, "@test/app"); - assert_no_traversal(&f); - } - - #[test] - fn path_normalisation_dotdot_in_middle() { - // `./foo/../bar` should normalise to `cwd/bar` - let cwd = abs("/workspace"); - let f = parse_filter("{./foo/../bar}", cwd).unwrap(); - assert_directory(&f, abs("/workspace/bar")); - } - - #[test] - fn path_normalisation_trailing_dot() { - // `./foo/.` should normalise to `cwd/foo` - let cwd = abs("/workspace"); - let f = parse_filter("{./foo/.}", cwd).unwrap(); - assert_directory(&f, abs("/workspace/foo")); - } - - // ── Directory glob tests ───────────────────────────────────────────────── - - #[test] - fn directory_glob_star() { - let cwd = abs("/workspace"); - let f = parse_filter("./packages/*", cwd).unwrap(); - assert!(!f.exclude); - assert_directory_glob(&f, abs("/workspace/packages"), "*"); - assert_no_traversal(&f); - } - - #[test] - fn directory_glob_double_star() { - let cwd = abs("/workspace"); - let f = parse_filter("./packages/**", cwd).unwrap(); - assert!(!f.exclude); - assert_directory_glob(&f, abs("/workspace/packages"), "**"); - assert_no_traversal(&f); - } - - #[test] - fn brace_directory_glob() { - let cwd = abs("/workspace"); - let f = parse_filter("{./packages/*}", cwd).unwrap(); - assert!(!f.exclude); - assert_directory_glob(&f, abs("/workspace/packages"), "*"); - assert_no_traversal(&f); - } - - #[test] - fn name_and_directory_glob_combined() { - let cwd = abs("/workspace"); - let f = parse_filter("app{./packages/*}", cwd).unwrap(); - assert!(!f.exclude); - assert_name_and_directory_glob(&f, "app", abs("/workspace/packages"), "*"); - assert_no_traversal(&f); - } - - #[test] - fn directory_glob_with_traversal() { - let cwd = abs("/workspace"); - let f = parse_filter("...{./packages/*}", cwd).unwrap(); - assert!(!f.exclude); - assert_directory_glob(&f, abs("/workspace/packages"), "*"); - assert_traversal(&f, TraversalDirection::Dependents, false); - } - - #[test] - fn directory_glob_parent_prefix() { - // `../*` from a subdirectory should resolve base to parent - let cwd = abs("/workspace/packages/app"); - let f = parse_filter("../*", cwd).unwrap(); - assert_directory_glob(&f, abs("/workspace/packages"), "*"); - } - - #[test] - fn directory_glob_dotdot_in_base() { - // `../foo/*` — `..` in the non-glob base is normalised before glob matching. - // Matches Node's path.join('/ws/packages/app', '../foo/*') → '/ws/packages/foo/*'. - let cwd = abs("/workspace/packages/app"); - let f = parse_filter("../foo/*", cwd).unwrap(); - assert_directory_glob(&f, abs("/workspace/packages/foo"), "*"); - } - - // ── Direct resolve_directory_pattern tests ────────────────────────────── - - #[test] - fn dir_pattern_plain_path() { - let cwd = abs("/workspace"); - let dp = resolve_directory_pattern("./packages/app", cwd).unwrap(); - assert!( - matches!(&dp, DirectoryPattern::Exact(p) if p.as_ref() == abs("/workspace/packages/app")) - ); - } - - #[test] - fn dir_pattern_dot() { - let cwd = abs("/workspace/packages/app"); - let dp = resolve_directory_pattern(".", cwd).unwrap(); - assert!( - matches!(&dp, DirectoryPattern::Exact(p) if p.as_ref() == abs("/workspace/packages/app")) - ); - } - - #[test] - fn dir_pattern_dotdot() { - let cwd = abs("/workspace/packages/app"); - let dp = resolve_directory_pattern("..", cwd).unwrap(); - assert!( - matches!(&dp, DirectoryPattern::Exact(p) if p.as_ref() == abs("/workspace/packages")) - ); - } - - #[test] - fn dir_pattern_normalises_dotdot_in_middle() { - let cwd = abs("/workspace"); - let dp = resolve_directory_pattern("./foo/../bar", cwd).unwrap(); - assert!(matches!(&dp, DirectoryPattern::Exact(p) if p.as_ref() == abs("/workspace/bar"))); - } - - #[test] - fn dir_pattern_glob_star() { - let cwd = abs("/workspace"); - let dp = resolve_directory_pattern("./packages/*", cwd).unwrap(); - match &dp { - DirectoryPattern::Glob { base, pattern } => { - assert_eq!(base.as_ref(), abs("/workspace/packages")); - assert_eq!(pattern.to_string(), "*"); - } - DirectoryPattern::Exact(p) => panic!("expected Glob, got Exact({p:?})"), - } - } - - #[test] - fn dir_pattern_glob_double_star() { - let cwd = abs("/workspace"); - let dp = resolve_directory_pattern("./packages/**", cwd).unwrap(); - match &dp { - DirectoryPattern::Glob { base, pattern } => { - assert_eq!(base.as_ref(), abs("/workspace/packages")); - assert_eq!(pattern.to_string(), "**"); - } - DirectoryPattern::Exact(p) => panic!("expected Glob, got Exact({p:?})"), - } - } - - #[test] - fn dir_pattern_bare_glob_star() { - // `*` with no path prefix — base should resolve to cwd - let cwd = abs("/workspace"); - let dp = resolve_directory_pattern("*", cwd).unwrap(); - match &dp { - DirectoryPattern::Glob { base, pattern } => { - assert_eq!(base.as_ref(), abs("/workspace")); - assert_eq!(pattern.to_string(), "*"); - } - DirectoryPattern::Exact(p) => panic!("expected Glob, got Exact({p:?})"), - } - } - - #[test] - fn dir_pattern_dotdot_before_glob() { - let cwd = abs("/workspace/packages/app"); - let dp = resolve_directory_pattern("../*", cwd).unwrap(); - match &dp { - DirectoryPattern::Glob { base, pattern } => { - assert_eq!(base.as_ref(), abs("/workspace/packages")); - assert_eq!(pattern.to_string(), "*"); - } - DirectoryPattern::Exact(p) => panic!("expected Glob, got Exact({p:?})"), - } - } - - #[test] - fn dir_pattern_nested_glob() { - let cwd = abs("/workspace"); - let dp = resolve_directory_pattern("./packages/*/src", cwd).unwrap(); - match &dp { - DirectoryPattern::Glob { base, pattern } => { - assert_eq!(base.as_ref(), abs("/workspace/packages")); - assert_eq!(pattern.to_string(), "*/src"); - } - DirectoryPattern::Exact(p) => panic!("expected Glob, got Exact({p:?})"), - } - } - - // ── Unbraced path selectors discard traversal (pnpm compat) ───────── - - #[test] - fn unbraced_path_discards_trailing_dots() { - // `./foo...` — `...` is stripped but traversal is discarded for unbraced paths. - let cwd = abs("/workspace"); - let f = parse_filter("./foo...", cwd).unwrap(); - assert_directory(&f, abs("/workspace/foo")); - assert_no_traversal(&f); - } - - #[test] - fn unbraced_dot_discards_trailing_dots() { - // `....` = `.` (cwd) + `...` — traversal discarded. - let cwd = abs("/workspace/packages/app"); - let f = parse_filter("....", cwd).unwrap(); - assert_directory(&f, abs("/workspace/packages/app")); - assert_no_traversal(&f); - } - - #[test] - fn unbraced_dotdot_discards_leading_dots() { - // `......` = `...` (dependents) + `...` (remaining = `...`) - // After stripping both `...` markers, core = empty → error? No: - // `...../foo` = `...` (dependents) + `../foo` — traversal discarded. - let cwd = abs("/workspace/packages/app"); - let f = parse_filter("...../foo", cwd).unwrap(); - assert_directory(&f, abs("/workspace/packages/foo")); - assert_no_traversal(&f); - } - - #[test] - fn braced_path_preserves_traversal() { - // `{./foo}...` — braces make traversal work. - let cwd = abs("/workspace"); - let f = parse_filter("{./foo}...", cwd).unwrap(); - assert_directory(&f, abs("/workspace/foo")); - assert_traversal(&f, TraversalDirection::Dependencies, false); - } - - // ── -w / --workspace-root flag ────────────────────────────────────────── - - #[test] - fn workspace_root_produces_selector() { - let cwd: Arc = Arc::from(abs("/workspace/packages/app")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: true, - filters: Vec::new(), - fail_if_no_match: false, - }; - let (query, _) = args.into_package_query(None, &cwd).unwrap(); - match &query.0 { - crate::package_graph::PackageQueryKind::Filters(filters) => { - assert_eq!(filters.len(), 1); - assert!(!filters[0].exclude); - assert!( - matches!(&filters[0].selector, PackageSelector::WorkspaceRoot), - "expected WorkspaceRoot, got {:?}", - filters[0].selector - ); - assert_no_traversal(&filters[0]); - } - crate::package_graph::PackageQueryKind::All => panic!("expected Filters, got All"), - } - } - - #[test] - fn workspace_root_with_recursive_returns_all() { - // -w is redundant with -r (all packages already includes root). - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: true, - transitive: false, - workspace_root: true, - filters: Vec::new(), - fail_if_no_match: false, - }; - let (query, _) = args.into_package_query(None, &cwd).unwrap(); - assert!( - matches!(&query.0, crate::package_graph::PackageQueryKind::All), - "expected All, got {:?}", - query.0 - ); - } - - #[test] - fn workspace_root_with_transitive() { - // -w -t: workspace root with transitive dependencies. - let cwd: Arc = Arc::from(abs("/workspace/packages/app")); - let args = PackageQueryArgs { - recursive: false, - transitive: true, - workspace_root: true, - filters: Vec::new(), - fail_if_no_match: false, - }; - let (query, _) = args.into_package_query(None, &cwd).unwrap(); - match &query.0 { - crate::package_graph::PackageQueryKind::Filters(filters) => { - assert_eq!(filters.len(), 1); - assert!( - matches!(&filters[0].selector, PackageSelector::WorkspaceRoot), - "expected WorkspaceRoot, got {:?}", - filters[0].selector - ); - assert_traversal(&filters[0], TraversalDirection::Dependencies, false); - } - crate::package_graph::PackageQueryKind::All => panic!("expected Filters, got All"), - } - } - - #[test] - fn workspace_root_with_filter_unions() { - // -w --filter foo: workspace root + parsed filter. - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: true, - filters: vec![Str::from("foo")], - fail_if_no_match: false, - }; - let (query, _) = args.into_package_query(None, &cwd).unwrap(); - match &query.0 { - crate::package_graph::PackageQueryKind::Filters(filters) => { - assert_eq!(filters.len(), 2); - assert_exact_name(&filters[0], "foo"); - assert!( - matches!(&filters[1].selector, PackageSelector::WorkspaceRoot), - "expected WorkspaceRoot, got {:?}", - filters[1].selector - ); - } - crate::package_graph::PackageQueryKind::All => panic!("expected Filters, got All"), - } - } - - #[test] - fn workspace_root_conflicts_with_package_name() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: true, - filters: Vec::new(), - fail_if_no_match: false, - }; - assert!(matches!( - args.into_package_query(Some(Str::from("app")), &cwd), - Err(PackageQueryError::PackageNameWithWorkspaceRoot { .. }) - )); - } - - // ── source field ─────────────────────────────────────────────────────── - - #[test] - fn parse_filter_sets_source() { - let cwd = abs("/workspace"); - let f = parse_filter("@test/app...", cwd).unwrap(); - assert_eq!(f.source.as_deref(), Some("@test/app...")); - } - - #[test] - fn filter_source_preserved_after_whitespace_split() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: false, - filters: vec![Str::from("a b")], - fail_if_no_match: false, - }; - let (query, _) = args.into_package_query(None, &cwd).unwrap(); - match &query.0 { - crate::package_graph::PackageQueryKind::Filters(filters) => { - assert_eq!(filters.len(), 2); - assert_eq!(filters[0].source.as_deref(), Some("a")); - assert_eq!(filters[1].source.as_deref(), Some("b")); - } - crate::package_graph::PackageQueryKind::All => panic!("expected Filters, got All"), - } - } - - #[test] - fn synthetic_workspace_root_filter_has_no_source() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: true, - filters: vec![Str::from("foo")], - fail_if_no_match: false, - }; - let (query, _) = args.into_package_query(None, &cwd).unwrap(); - match &query.0 { - crate::package_graph::PackageQueryKind::Filters(filters) => { - assert_eq!(filters.len(), 2); - assert_eq!(filters[0].source.as_deref(), Some("foo")); - assert!(filters[1].source.is_none()); - } - crate::package_graph::PackageQueryKind::All => panic!("expected Filters, got All"), - } - } - - #[test] - fn implicit_cwd_filter_has_no_source() { - let cwd: Arc = Arc::from(abs("/workspace/packages/app")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: false, - filters: Vec::new(), - fail_if_no_match: false, - }; - let (query, _) = args.into_package_query(None, &cwd).unwrap(); - match &query.0 { - crate::package_graph::PackageQueryKind::Filters(filters) => { - assert_eq!(filters.len(), 1); - assert!(filters[0].source.is_none()); - } - crate::package_graph::PackageQueryKind::All => panic!("expected Filters, got All"), - } - } - - // ── empty filter validation ───────────────────────────────────────────── - - #[test] - fn empty_filter_string_errors() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: false, - filters: vec![Str::from("")], - fail_if_no_match: false, - }; - assert!(matches!(args.into_package_query(None, &cwd), Err(PackageQueryError::EmptyFilter))); - } - - #[test] - fn whitespace_only_filter_errors() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: false, - filters: vec![Str::from(" ")], - fail_if_no_match: false, - }; - assert!(matches!(args.into_package_query(None, &cwd), Err(PackageQueryError::EmptyFilter))); - } - - #[test] - fn second_filter_empty_errors() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: false, - filters: vec![Str::from("foo"), Str::from("")], - fail_if_no_match: false, - }; - assert!(matches!(args.into_package_query(None, &cwd), Err(PackageQueryError::EmptyFilter))); - } - - #[test] - fn first_filter_empty_with_valid_second_errors() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: false, - filters: vec![Str::from(""), Str::from("foo")], - fail_if_no_match: false, - }; - assert!(matches!(args.into_package_query(None, &cwd), Err(PackageQueryError::EmptyFilter))); - } - - #[test] - fn valid_filter_with_whitespace_only_second_errors() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: false, - filters: vec![Str::from("foo"), Str::from(" \t ")], - fail_if_no_match: false, - }; - assert!(matches!(args.into_package_query(None, &cwd), Err(PackageQueryError::EmptyFilter))); - } - - // ── is_cwd_only flag ───────────────────────────────────────────────────── - - #[test] - fn is_cwd_only_true_for_bare_invocation() { - let cwd: Arc = Arc::from(abs("/workspace/packages/app")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: false, - filters: Vec::new(), - fail_if_no_match: false, - }; - let (_, is_cwd_only) = args.into_package_query(None, &cwd).unwrap(); - assert!(is_cwd_only); - } - - #[test] - fn is_cwd_only_false_with_package_name() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: false, - filters: Vec::new(), - fail_if_no_match: false, - }; - let (_, is_cwd_only) = args.into_package_query(Some(Str::from("app")), &cwd).unwrap(); - assert!(!is_cwd_only); - } - - #[test] - fn is_cwd_only_false_with_transitive() { - let cwd: Arc = Arc::from(abs("/workspace/packages/app")); - let args = PackageQueryArgs { - recursive: false, - transitive: true, - workspace_root: false, - filters: Vec::new(), - fail_if_no_match: false, - }; - let (_, is_cwd_only) = args.into_package_query(None, &cwd).unwrap(); - assert!(!is_cwd_only); - } - - #[test] - fn is_cwd_only_false_with_recursive() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: true, - transitive: false, - workspace_root: false, - filters: Vec::new(), - fail_if_no_match: false, - }; - let (_, is_cwd_only) = args.into_package_query(None, &cwd).unwrap(); - assert!(!is_cwd_only); - } - - #[test] - fn is_cwd_only_false_with_filter() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: false, - filters: vec![Str::from("foo")], - fail_if_no_match: false, - }; - let (_, is_cwd_only) = args.into_package_query(None, &cwd).unwrap(); - assert!(!is_cwd_only); - } - - #[test] - fn is_cwd_only_false_with_workspace_root() { - let cwd: Arc = Arc::from(abs("/workspace")); - let args = PackageQueryArgs { - recursive: false, - transitive: false, - workspace_root: true, - filters: Vec::new(), - fail_if_no_match: false, - }; - let (_, is_cwd_only) = args.into_package_query(None, &cwd).unwrap(); - assert!(!is_cwd_only); - } -} diff --git a/crates/vite_workspace/src/package_graph.rs b/crates/vite_workspace/src/package_graph.rs deleted file mode 100644 index 45eb88f78..000000000 --- a/crates/vite_workspace/src/package_graph.rs +++ /dev/null @@ -1,530 +0,0 @@ -//! Package graph with indexed lookups and filter resolution. -//! -//! This module owns the `IndexedPackageGraph` (the read-only package dependency -//! graph enriched with hash-map indices for O(1) lookups) and the `PackageQuery` -//! type that expresses which packages a task query applies to. -//! -//! # Two-stage model -//! -//! Package selection (this module) is deliberately decoupled from task matching -//! (the task-query layer in `vite_task_graph`). `resolve_query` returns a *package -//! subgraph* — a `DiGraphMap` containing only the selected -//! packages and the original dependency edges between them. The task-query layer -//! then maps each selected package to its task node, reconnecting across -//! task-lacking nodes. - -use std::sync::Arc; - -use petgraph::{Direction, graph::DiGraph, prelude::DiGraphMap, visit::EdgeRef}; -use rustc_hash::{FxHashMap, FxHashSet}; -use vec1::{Vec1, smallvec_v1::SmallVec1}; -use vite_path::AbsolutePath; -use vite_str::Str; - -use crate::{ - DependencyType, PackageInfo, PackageIx, PackageNodeIndex, - package_filter::{ - DirectoryPattern, GraphTraversal, PackageFilter, PackageNamePattern, PackageSelector, - TraversalDirection, - }, -}; - -// ──────────────────────────────────────────────────────────────────────────── -// PackageQuery -// ──────────────────────────────────────────────────────────────────────────── - -/// Specifies which packages a task query applies to. -/// -/// This type is opaque — construct it via [`PackageQueryArgs::into_package_query`] -/// (from `package_filter`). -/// -/// [`PackageQueryArgs::into_package_query`]: crate::package_filter::PackageQueryArgs::into_package_query -#[derive(Debug, PartialEq)] -pub struct PackageQuery(pub(crate) PackageQueryKind); - -#[derive(Debug, PartialEq)] -pub(crate) enum PackageQueryKind { - /// One or more `--filter` expressions. - /// - /// Inclusions are unioned; exclusions are subtracted from the union. - /// If all filters are exclusions, the starting set is the full workspace - /// pnpm ref: - Filters(Vec1), - - /// All packages in the workspace, in topological dependency order. - /// - /// Produced by `--recursive` / `-r`. - All, -} - -impl PackageQuery { - /// All packages in the workspace. - pub(crate) const fn all() -> Self { - Self(PackageQueryKind::All) - } - - /// One or more filter expressions. - pub(crate) const fn filters(filters: Vec1) -> Self { - Self(PackageQueryKind::Filters(filters)) - } - - /// Select the single package whose root is `path`. - /// - /// Used by the interactive task selector to match by filesystem path - /// instead of package name. - #[must_use] - pub fn containing_package(path: Arc) -> Self { - Self(PackageQueryKind::Filters(Vec1::new(PackageFilter { - exclude: false, - selector: PackageSelector::ContainingPackage(path), - traversal: None, - source: None, - }))) - } -} - -// ──────────────────────────────────────────────────────────────────────────── -// FilterResolution -// ──────────────────────────────────────────────────────────────────────────── - -/// The result of resolving a [`PackageQuery`] against the workspace. -pub struct FilterResolution { - /// Induced package subgraph: nodes = selected packages, edges = original dependency - /// ordering edges between them. - /// - /// All original edges between selected packages are preserved regardless of how each - /// package was selected (traversal or cherry-pick). A cherry-picked package still - /// respects its dependencies if they happen to be in the selected set. - /// - /// Future `--filter-prod` support would skip `DependencyType::Dev` edges at this - /// stage (construction time), keeping all downstream code edge-type-agnostic. - pub package_subgraph: DiGraphMap, - - /// Original `--filter` strings for inclusion filters that contributed no packages - /// to the final selected set (either the core selector matched nothing, or the - /// selector matched but the graph traversal — e.g. `^...` — collapsed it to empty). - /// - /// Omits synthetic filters (implicit cwd, `-w`) since the user didn't type them. - /// Empty when `PackageQuery::All` is used. - pub unmatched_selectors: Vec, -} - -// ──────────────────────────────────────────────────────────────────────────── -// PackageQueryResolveError -// ──────────────────────────────────────────────────────────────────────────── - -/// Errors that can occur when resolving a [`PackageQuery`] against the workspace. -#[derive(Debug, thiserror::Error)] -pub enum PackageQueryResolveError { - #[error( - "Package name '{package_name}' is ambiguous; found in multiple locations: {}", - package_paths.iter().map(|p| p.as_path().display().to_string()).collect::>().join(", ") - )] - AmbiguousPackageName { package_name: Str, package_paths: Box<[Arc]> }, -} - -// ──────────────────────────────────────────────────────────────────────────── -// IndexedPackageGraph -// ──────────────────────────────────────────────────────────────────────────── - -/// Package graph with additional hash maps for quick task lookup. -#[derive(Debug)] -pub struct IndexedPackageGraph { - graph: DiGraph, - - /// Package indices grouped by name. - /// - /// `SmallVec1` avoids a heap allocation in the common case (one package per name) - /// while still supporting the rare monorepo name-collision scenario. - indices_by_name: FxHashMap>, - - /// Package indices by absolute path, for O(1) lookup given a cwd. - indices_by_path: FxHashMap, PackageNodeIndex>, -} - -impl IndexedPackageGraph { - /// Build the index from a raw package graph. - #[must_use] - pub fn index(package_graph: DiGraph) -> Self { - let indices_by_path: FxHashMap, PackageNodeIndex> = package_graph - .node_indices() - .map(|idx| (Arc::clone(&package_graph[idx].absolute_path), idx)) - .collect(); - - let mut indices_by_name: FxHashMap> = - FxHashMap::default(); - for idx in package_graph.node_indices() { - let name = package_graph[idx].package_json.name.clone(); - indices_by_name - .entry(name) - .and_modify(|v| v.push(idx)) - .or_insert_with(|| SmallVec1::new(idx)); - } - - Self { graph: package_graph, indices_by_name, indices_by_path } - } - - // ── Public accessors ───────────────────────────────────────────────────── - - /// Reference to the underlying package graph. - #[must_use] - pub const fn package_graph(&self) -> &DiGraph { - &self.graph - } - - /// Walk up the directory tree from `cwd` to find the nearest enclosing package. - /// - /// Returns `None` if no package root is found anywhere above `cwd`. - #[must_use] - pub fn get_package_index_from_cwd(&self, cwd: &AbsolutePath) -> Option { - let mut cur = cwd; - loop { - if let Some(&idx) = self.indices_by_path.get(cur) { - return Some(idx); - } - cur = cur.parent()?; - } - } - - /// Look up all package indices with the given name (exact, case-sensitive). - #[must_use] - pub fn get_package_indices_by_name( - &self, - name: &Str, - ) -> Option<&SmallVec1<[PackageNodeIndex; 1]>> { - self.indices_by_name.get(name) - } - - // ── Query resolution ───────────────────────────────────────────────────── - - /// Resolve a [`PackageQuery`] into a [`FilterResolution`]. - /// - /// For `All`, returns the full induced subgraph (every package, every edge). - /// For `Filters`, applies the filter algorithm and returns the induced subgraph - /// of the matching packages. - /// - /// # Errors - /// - /// Returns [`PackageQueryResolveError::AmbiguousPackageName`] when an exact - /// package name (from a `pkg#task` specifier) matches multiple packages. - pub fn resolve_query( - &self, - query: &PackageQuery, - ) -> Result { - match &query.0 { - PackageQueryKind::All => Ok(FilterResolution { - package_subgraph: self.full_subgraph(), - unmatched_selectors: Vec::new(), - }), - PackageQueryKind::Filters(filters) => self.resolve_filters(filters.as_slice()), - } - } - - /// Build the full induced subgraph: every package node and every dependency edge. - /// - /// Used by `PackageQuery::All` (`--recursive`). - fn full_subgraph(&self) -> DiGraphMap { - let mut subgraph = DiGraphMap::new(); - for idx in self.graph.node_indices() { - subgraph.add_node(idx); - } - for edge in self.graph.edge_references() { - subgraph.add_edge(edge.source(), edge.target(), ()); - } - subgraph - } - - /// Resolve a slice of package filters into a `FilterResolution`. - /// - /// # Algorithm (follows pnpm's [`filterWorkspacePackages`]) - /// - /// [`filterWorkspacePackages`]: https://github.com/pnpm/pnpm/blob/491a84fb26fa716408bf6bd361680f6a450c61fc/workspace/filter-workspace-packages/src/index.ts#L149-L178 - /// - /// 1. Partition filters into inclusions (`!exclude = false`) and exclusions. - /// 2. If there are no inclusions, start from ALL packages (exclude-only mode). - /// 3. For each inclusion: resolve its selector to entry packages, expand via - /// graph traversal if requested, and union into `selected`. - /// 4. For each exclusion: resolve + expand, then subtract from `selected`. - /// 5. Build the induced subgraph: every original edge whose both endpoints are - /// in `selected` is preserved, regardless of how each endpoint was selected. - fn resolve_filters( - &self, - filters: &[PackageFilter], - ) -> Result { - let mut unmatched_selectors = Vec::new(); - - let (inclusions, exclusions): (Vec<_>, Vec<_>) = filters.iter().partition(|f| !f.exclude); - - // Start from all packages when there are no inclusions (exclude-only mode). - let mut selected: FxHashSet = if inclusions.is_empty() { - self.graph.node_indices().collect() - } else { - FxHashSet::default() - }; - - // Apply inclusions: union each filter's resolved set into `selected`. - for filter in &inclusions { - let matched = self.resolve_selector_entries(&filter.selector)?; - let expanded = self.expand_traversal(matched, filter.traversal.as_ref()); - // Track filters that contribute nothing (typo, no-match glob, or a - // traversal like `^...` that collapsed an otherwise-matching seed - // down to zero). Synthetic filters (implicit cwd, `-w`) have no - // user-typed source and are skipped — they aren't typos. - if expanded.is_empty() - && let Some(source) = &filter.source - { - unmatched_selectors.push(source.clone()); - } - selected.extend(expanded); - } - - // Apply exclusions: subtract each filter's resolved set from `selected`. - for filter in &exclusions { - let matched = self.resolve_selector_entries(&filter.selector)?; - let to_remove = self.expand_traversal(matched, filter.traversal.as_ref()); - for pkg in to_remove { - selected.remove(&pkg); - } - } - - let package_subgraph = self.build_induced_subgraph(&selected); - Ok(FilterResolution { package_subgraph, unmatched_selectors }) - } - - /// Resolve a `PackageSelector` to the set of directly matched packages - /// (before any graph traversal expansion). - fn resolve_selector_entries( - &self, - selector: &PackageSelector, - ) -> Result, PackageQueryResolveError> { - let mut matched = FxHashSet::default(); - - match selector { - PackageSelector::Name(pattern) => { - self.match_by_name_pattern(pattern, &mut matched)?; - } - - PackageSelector::Directory(dir_pattern) => { - self.match_by_directory_pattern(dir_pattern, &mut matched); - } - - PackageSelector::ContainingPackage(path) => { - // Walk up the directory tree to find the enclosing package. - if let Some(idx) = self.get_package_index_from_cwd(path) { - matched.insert(idx); - } - } - - PackageSelector::NameAndDirectory { name, directory } => { - // Intersection: packages satisfying both name AND directory. - let mut by_name = FxHashSet::default(); - self.match_by_name_pattern(name, &mut by_name)?; - let mut by_dir = FxHashSet::default(); - self.match_by_directory_pattern(directory, &mut by_dir); - matched.extend(by_name.intersection(&by_dir)); - } - - PackageSelector::WorkspaceRoot => { - // The workspace root package has an empty relative path. - for idx in self.graph.node_indices() { - if self.graph[idx].path.as_str().is_empty() { - matched.insert(idx); - break; - } - } - } - } - - Ok(matched) - } - - /// Match packages by a name pattern, inserting into `out`. - /// - /// For `Exact` names, scoped auto-completion applies - /// (pnpm ref: ): - /// if `"bar"` has no exact match but exactly one `@*/bar` package exists, - /// that package is used instead. - fn match_by_name_pattern( - &self, - pattern: &PackageNamePattern, - out: &mut FxHashSet, - ) -> Result<(), PackageQueryResolveError> { - match pattern { - PackageNamePattern::Exact { name, unique } => { - if let Some(indices) = self.get_package_indices_by_name(name) { - if *unique && indices.len() > 1 { - return Err(PackageQueryResolveError::AmbiguousPackageName { - package_name: name.clone(), - package_paths: indices - .iter() - .map(|i| Arc::clone(&self.graph[*i].absolute_path)) - .collect(), - }); - } - out.extend(indices.iter().copied()); - } else { - // Scoped auto-completion: `"bar"` → `"@scope/bar"` if exactly one match. - let scoped_suffix = vite_str::format!("/{}", name); - let scoped: Vec<_> = self - .indices_by_name - .iter() - .filter(|(pkg_name, _)| { - pkg_name.starts_with('@') && pkg_name.ends_with(scoped_suffix.as_str()) - }) - .flat_map(|(_, indices)| indices.iter().copied()) - .collect(); - if scoped.len() == 1 { - out.insert(scoped[0]); - } - // 0 matches: nothing to insert. - // >1 matches: ambiguous; skip rather than guessing wrong. - } - } - - PackageNamePattern::Glob(glob) => { - use wax::Program as _; - for (pkg_name, indices) in &self.indices_by_name { - if glob.is_match(pkg_name.as_str()) { - out.extend(indices.iter().copied()); - } - } - } - } - Ok(()) - } - - /// Match packages by a directory pattern, inserting into `out`. - /// - /// pnpm ref: - fn match_by_directory_pattern( - &self, - pattern: &DirectoryPattern, - out: &mut FxHashSet, - ) { - match pattern { - DirectoryPattern::Exact(dir) => { - // O(1) exact lookup by path. - if let Some(&idx) = self.indices_by_path.get(dir) { - out.insert(idx); - } - } - DirectoryPattern::Glob { base, pattern } => { - use wax::Program as _; - for idx in self.graph.node_indices() { - let pkg_path = &self.graph[idx].absolute_path; - if let Ok(remainder) = pkg_path.as_path().strip_prefix(base.as_path()) - && pattern.is_match(remainder) - { - out.insert(idx); - } - } - } - } - } - - /// Expand a seed set of packages according to a graph traversal specification. - /// - /// Returns the final set, including or excluding the original seeds depending on - /// `traversal.exclude_self`. `None` traversal → seeds returned unchanged. - fn expand_traversal( - &self, - seeds: FxHashSet, - traversal: Option<&GraphTraversal>, - ) -> FxHashSet { - let Some(traversal) = traversal else { - return seeds; - }; - - let mut reachable = FxHashSet::default(); - - match traversal.direction { - TraversalDirection::Dependencies => { - self.bfs_outgoing(&seeds, &mut reachable); - } - TraversalDirection::Dependents => { - self.bfs_incoming(&seeds, &mut reachable); - } - TraversalDirection::Both => { - // Walk dependents first, then walk dependencies of ALL dependents found - // (including the original seeds). - // pnpm ref: - let mut dependents = FxHashSet::default(); - self.bfs_incoming(&seeds, &mut dependents); - let all_dep_seeds: FxHashSet<_> = - seeds.iter().chain(dependents.iter()).copied().collect(); - self.bfs_outgoing(&all_dep_seeds, &mut reachable); - reachable.extend(dependents); - } - } - - if traversal.exclude_self { - for seed in &seeds { - reachable.remove(seed); - } - } else { - reachable.extend(seeds); - } - - reachable - } - - /// BFS along outgoing (dependency) edges from `seeds`, collecting all reachable nodes. - /// - /// Seeds are NOT added to `out`; the caller decides inclusion based on `exclude_self`. - fn bfs_outgoing( - &self, - seeds: &FxHashSet, - out: &mut FxHashSet, - ) { - let mut queue: Vec = seeds.iter().copied().collect(); - while let Some(node) = queue.pop() { - for edge in self.graph.edges(node) { - let dep = edge.target(); - if out.insert(dep) { - queue.push(dep); - } - } - } - } - - /// BFS along incoming (dependent) edges from `seeds`, collecting all reachable nodes. - /// - /// Seeds are NOT added to `out`. - fn bfs_incoming( - &self, - seeds: &FxHashSet, - out: &mut FxHashSet, - ) { - let mut queue: Vec = seeds.iter().copied().collect(); - while let Some(node) = queue.pop() { - for edge in self.graph.edges_directed(node, Direction::Incoming) { - let dependent = edge.source(); - if out.insert(dependent) { - queue.push(dependent); - } - } - } - } - - /// Build the induced subgraph of `selected` packages. - /// - /// Includes every node in `selected` and every original edge `(a, b)` where - /// both `a` and `b` are in `selected`. Isolated nodes are also included. - fn build_induced_subgraph( - &self, - selected: &FxHashSet, - ) -> DiGraphMap { - let mut subgraph = DiGraphMap::new(); - for &pkg in selected { - subgraph.add_node(pkg); - } - for edge in self.graph.edge_references() { - let src = edge.source(); - let dst = edge.target(); - if selected.contains(&src) && selected.contains(&dst) { - subgraph.add_edge(src, dst, ()); - } - } - subgraph - } -} diff --git a/crates/vite_workspace/src/package_manager.rs b/crates/vite_workspace/src/package_manager.rs deleted file mode 100644 index bbff33277..000000000 --- a/crates/vite_workspace/src/package_manager.rs +++ /dev/null @@ -1,270 +0,0 @@ -use std::{fs, sync::Arc}; - -use vite_path::{AbsolutePath, RelativePathBuf}; - -use crate::Error; - -/// The contents of a file bundled with its absolute path for error context. -/// -/// The file is read to memory on construction and its handle closed -/// immediately — the struct itself never holds a live OS file handle. This -/// keeps long-lived `WorkspaceRoot`s (held across an entire `vp run` session) -/// from pinning files like `pnpm-workspace.yaml`, which on Windows could -/// otherwise block pnpm's atomic write-and-rename and fail with EPERM -/// (). -#[derive(Debug)] -pub struct FileWithPath { - content: Vec, - path: Arc, -} - -impl FileWithPath { - /// Open a file at the given path and read its contents into memory. - /// - /// # Errors - /// Returns an error if the file cannot be read. - pub fn open(path: Arc) -> Result { - let content = fs::read(&*path)?; - Ok(Self { content, path }) - } - - /// Try to read a file, returning None if it doesn't exist. - /// - /// # Errors - /// Returns an error if the file exists but cannot be read. - pub fn open_if_exists(path: Arc) -> Result, Error> { - match fs::read(&*path) { - Ok(content) => Ok(Some(Self { content, path })), - Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), - Err(e) => Err(e.into()), - } - } - - /// Get the file contents as a byte slice. - #[must_use] - pub fn content(&self) -> &[u8] { - &self.content - } - - /// Get the file path. - #[must_use] - pub const fn path(&self) -> &Arc { - &self.path - } -} - -/// The package root directory and its package.json file. -#[derive(Debug)] -pub struct PackageRoot<'a> { - pub path: &'a AbsolutePath, - pub cwd: RelativePathBuf, - pub package_json: FileWithPath, -} - -/// Find the package root directory from the current working directory. `original_cwd` must be absolute. -/// -/// If the package.json file is not found, will return `PackageJsonNotFound` error. -/// -/// # Errors -/// Returns an error if no `package.json` is found in any ancestor directory, or if a path cannot be stripped. -/// -/// # Panics -/// Panics if `original_cwd` is not within the found package root (should not happen in practice). -pub fn find_package_root(original_cwd: &AbsolutePath) -> Result, Error> { - let mut cwd = original_cwd; - loop { - // Check for package.json - let package_json_path: Arc = cwd.join("package.json").into(); - if let Some(file_with_path) = FileWithPath::open_if_exists(package_json_path)? { - return Ok(PackageRoot { - path: cwd, - cwd: original_cwd.strip_prefix(cwd)?.expect("cwd must be within the package root"), - package_json: file_with_path, - }); - } - - if let Some(parent) = cwd.parent() { - // Move up one directory - cwd = parent; - } else { - // We've reached the root, return PackageJsonNotFound error. - return Err(Error::PackageJsonNotFound(original_cwd.to_absolute_path_buf())); - } - } -} - -/// The workspace file. -/// -/// - `PnpmWorkspaceYaml` is the pnpm workspace file. -/// - `NpmWorkspaceJson` is the package.json file of a yarn/npm workspace. -/// - `NonWorkspacePackage` is the package.json file of a non-workspace package. -#[derive(Debug)] -pub enum WorkspaceFile { - /// The pnpm-workspace.yaml file of a pnpm workspace. - PnpmWorkspaceYaml(FileWithPath), - /// The package.json file of a yarn/npm workspace. - NpmWorkspaceJson(FileWithPath), - /// The package.json file of a non-workspace package. - NonWorkspacePackage(FileWithPath), -} - -/// The workspace root directory and its workspace file. -/// -/// If the workspace file is not found, but a package is found, `workspace_file` will be `NonWorkspacePackage` with the `package.json` File. -#[derive(Debug)] -pub struct WorkspaceRoot { - /// The absolute path of the workspace root directory. - pub path: Arc, - /// The workspace file. - pub workspace_file: WorkspaceFile, -} - -/// Find the workspace root directory from the current working directory. `original_cwd` must be absolute. -/// -/// Returns the workspace root and the relative path from the workspace root to the original cwd. -/// -/// If the workspace file is not found, but a package is found, `workspace_file` will be `NonWorkspacePackage` with the `package.json` File. -/// -/// If neither workspace nor package is found, will return `PackageJsonNotFound` error. -/// -/// # Errors -/// Returns an error if no workspace or package is found, or if file I/O or JSON/YAML parsing fails. -/// -/// # Panics -/// Panics if `original_cwd` is not within the found workspace root (should not happen in practice). -pub fn find_workspace_root( - original_cwd: &AbsolutePath, -) -> Result<(WorkspaceRoot, RelativePathBuf), Error> { - let mut cwd = original_cwd; - - loop { - // Check for pnpm-workspace.yaml for pnpm workspace - let pnpm_workspace_path: Arc = cwd.join("pnpm-workspace.yaml").into(); - if let Some(file_with_path) = FileWithPath::open_if_exists(pnpm_workspace_path)? { - let relative_cwd = - original_cwd.strip_prefix(cwd)?.expect("cwd must be within the pnpm workspace"); - return Ok(( - WorkspaceRoot { - path: Arc::from(cwd), - workspace_file: WorkspaceFile::PnpmWorkspaceYaml(file_with_path), - }, - relative_cwd, - )); - } - - // Check for package.json with workspaces field for npm/yarn workspace - let package_json_path: Arc = cwd.join("package.json").into(); - if let Some(file_with_path) = FileWithPath::open_if_exists(package_json_path)? { - let package_json: serde_json::Value = serde_json::from_slice(crate::strip_bom( - file_with_path.content(), - )) - .map_err(|e| Error::SerdeJson { - file_path: Arc::clone(file_with_path.path()), - serde_json_error: e, - })?; - if package_json.get("workspaces").is_some() { - let relative_cwd = - original_cwd.strip_prefix(cwd)?.expect("cwd must be within the workspace"); - return Ok(( - WorkspaceRoot { - path: Arc::from(cwd), - workspace_file: WorkspaceFile::NpmWorkspaceJson(file_with_path), - }, - relative_cwd, - )); - } - } - - // TODO(@fengmk2): other package manager support - - // Move up one directory - if let Some(parent) = cwd.parent() { - cwd = parent; - } else { - // We've reached the root, try to find the package root and return the non-workspace package. - let package_root = find_package_root(original_cwd)?; - let workspace_file = WorkspaceFile::NonWorkspacePackage(package_root.package_json); - return Ok(( - WorkspaceRoot { path: Arc::from(package_root.path), workspace_file }, - package_root.cwd, - )); - } - } -} - -#[cfg(test)] -mod tests { - use tempfile::TempDir; - - use super::*; - - /// Regression test for : - /// on Windows, an open handle to `pnpm-workspace.yaml` without - /// `FILE_SHARE_DELETE` blocks pnpm's atomic write-tmp-then-rename. - #[test] - fn find_workspace_root_does_not_lock_pnpm_workspace_yaml() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - let ws_yaml = temp_dir_path.join("pnpm-workspace.yaml"); - let ws_yaml_tmp = temp_dir_path.join("pnpm-workspace.yaml.tmp"); - - fs::write(&ws_yaml, b"packages:\n - apps/*\n").unwrap(); - - let (workspace_root, _) = find_workspace_root(temp_dir_path).unwrap(); - - fs::write(&ws_yaml_tmp, b"packages:\n - apps/*\n - packages/*\n").unwrap(); - fs::rename(&ws_yaml_tmp, &ws_yaml) - .expect("rename over pnpm-workspace.yaml must succeed while WorkspaceRoot is alive"); - - drop(workspace_root); - } - - /// Linux-only: `/proc/self/fd` lets us verify no descriptor remains - /// pointing at `pnpm-workspace.yaml` regardless of Rust's default - /// share mode on the platform. - #[cfg(target_os = "linux")] - #[test] - fn find_workspace_root_releases_pnpm_workspace_yaml_fd_on_linux() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - let ws_yaml = temp_dir_path.join("pnpm-workspace.yaml"); - fs::write(&ws_yaml, b"packages:\n - apps/*\n").unwrap(); - - let (workspace_root, _) = find_workspace_root(temp_dir_path).unwrap(); - - let ws_yaml_canonical = fs::canonicalize(&ws_yaml).unwrap(); - let mut open_to_target = 0; - for entry in fs::read_dir("/proc/self/fd").unwrap().flatten() { - if let Ok(link) = fs::read_link(entry.path()) - && link == ws_yaml_canonical - { - open_to_target += 1; - } - } - assert_eq!( - open_to_target, 0, - "expected no open file descriptor for pnpm-workspace.yaml, got {open_to_target}", - ); - drop(workspace_root); - } - - #[test] - fn file_with_path_content_matches_file_on_disk() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - let path: Arc = temp_dir_path.join("pnpm-workspace.yaml").into(); - fs::write(&*path, b"packages:\n - apps/*\n").unwrap(); - - let file_with_path = FileWithPath::open(Arc::clone(&path)).unwrap(); - assert_eq!(file_with_path.content(), b"packages:\n - apps/*\n"); - assert_eq!(&**file_with_path.path(), &*path); - } - - #[test] - fn file_with_path_open_if_exists_returns_none_when_missing() { - let temp_dir = TempDir::new().unwrap(); - let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); - let path: Arc = temp_dir_path.join("missing.yaml").into(); - assert!(FileWithPath::open_if_exists(path).unwrap().is_none()); - } -} diff --git a/deny.toml b/deny.toml deleted file mode 100644 index 4644e0a61..000000000 --- a/deny.toml +++ /dev/null @@ -1,254 +0,0 @@ -# This template contains all of the possible sections and their default values - -# Note that all fields that take a lint level have these possible values: -# * deny - An error will be produced and the check will fail -# * warn - A warning will be produced, but the check will not fail -# * allow - No warning or error will be produced, though in some cases a note -# will be - -# The values provided in this template are the default values that will be used -# when any section or field is not specified in your own configuration - -# This section is considered when running `cargo deny check advisories` -# More documentation for the advisories section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html -[advisories] -# The path where the advisory database is cloned/fetched into -db-path = "~/.cargo/advisory-db" -# The url(s) of the advisory databases to use -db-urls = ["https://github.com/rustsec/advisory-db"] -# The lint level for crates that have been yanked from their source registry -yanked = "warn" -# A list of advisory IDs to ignore. Note that ignored advisories will still -# output a note when they are encountered. -ignore = [ - "RUSTSEC-2024-0399", - # "RUSTSEC-0000-0000", -] -# Threshold for security vulnerabilities, any vulnerability with a CVSS score -# lower than the range specified will be ignored. Note that ignored advisories -# will still output a note when they are encountered. -# * None - CVSS Score 0.0 -# * Low - CVSS Score 0.1 - 3.9 -# * Medium - CVSS Score 4.0 - 6.9 -# * High - CVSS Score 7.0 - 8.9 -# * Critical - CVSS Score 9.0 - 10.0 -# severity-threshold = - -# If this is true, then cargo deny will use the git executable to fetch advisory database. -# If this is false, then it uses a built-in git library. -# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. -# See Git Authentication for more information about setting up git authentication. -# git-fetch-with-cli = true - -# This section is considered when running `cargo deny check licenses` -# More documentation for the licenses section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html -[licenses] -# List of explicitly allowed licenses -# See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. -allow = [ - "Apache-2.0", - "BSD-3-Clause", - "ISC", - "MIT", - "MPL-2.0", - "OpenSSL", - "Unicode-DFS-2016", - "Unicode-3.0", -] -# The confidence threshold for detecting a license from license text. -# The higher the value, the more closely the license text must be to the -# canonical license text of a valid SPDX license file. -# [possible values: any between 0.0 and 1.0]. -confidence-threshold = 0.8 -# Allow 1 or more licenses on a per-crate basis, so that particular licenses -# aren't accepted for every possible crate as with the normal allow list -exceptions = [ - - # Each entry is the crate and version constraint, and its specific allow - # list - # { allow = ["Zlib"], name = "adler32", version = "*" }, -] - -# Some crates don't have (easily) machine readable licensing information, -# adding a clarification entry for it allows you to manually specify the -# licensing information -[[licenses.clarify]] -# The name of the crate the clarification applies to -name = "ring" -# The optional version constraint for the crate -version = "*" -# The SPDX expression for the license requirements of the crate -expression = "MIT AND ISC AND OpenSSL" -# One or more files in the crate's source used as the "source of truth" for -# the license expression. If the contents match, the clarification will be used -# when running the license check, otherwise the clarification will be ignored -# and the crate will be checked normally, which may produce warnings or errors -# depending on the rest of your configuration -license-files = [ - # Each entry is a crate relative path, and the (opaque) hash of its contents - { path = "LICENSE", hash = 0xbd0eed23 }, -] - -[licenses.private] -# If true, ignores workspace crates that aren't published, or are only -# published to private registries. -# To see how to mark a crate as unpublished (to the official registry), -# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. -ignore = false -# One or more private registries that you might publish crates to, if a crate -# is only published to private registries, and ignore is true, the crate will -# not have its license(s) checked -registries = [ - - # "https://sekretz.com/registry -] - -# This section is considered when running `cargo deny check bans`. -# More documentation about the 'bans' section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html -[bans] -# Lint level for when multiple versions of the same crate are detected -multiple-versions = "warn" -# Lint level for when a crate version requirement is `*` -wildcards = "allow" -# The graph highlighting used when creating dotgraphs for crates -# with multiple versions -# * lowest-version - The path to the lowest versioned duplicate is highlighted -# * simplest-path - The path to the version with the fewest edges is highlighted -# * all - Both lowest-version and simplest-path are used -highlight = "all" -# The default lint level for `default` features for crates that are members of -# the workspace that is being checked. This can be overridden by allowing/denying -# `default` on a crate-by-crate basis if desired. -workspace-default-features = "allow" -# The default lint level for `default` features for external crates that are not -# members of the workspace. This can be overridden by allowing/denying `default` -# on a crate-by-crate basis if desired. -external-default-features = "allow" -# List of crates that are allowed. Use with care! -allow = [ - - # { name = "ansi_term", version = "=0.11.0" }, -] -# List of crates to deny -deny = [ - - # Each entry the name of a crate and a version range. If version is - # not specified, all versions will be matched. - # { name = "ansi_term", version = "=0.11.0" }, - # - # Wrapper crates can optionally be specified to allow the crate when it - # is a direct dependency of the otherwise banned crate - # { name = "ansi_term", version = "=0.11.0", wrappers = [] }, -] - -# List of features to allow/deny -# Each entry the name of a crate and a version range. If version is -# not specified, all versions will be matched. -# [[bans.features]] -# name = "reqwest" -# Features to not allow -# deny = ["json"] -# Features to allow -# allow = [ -# "rustls", -# "__rustls", -# "__tls", -# "hyper-rustls", -# "rustls", -# "rustls-pemfile", -# "rustls-tls-webpki-roots", -# "tokio-rustls", -# "webpki-roots", -# ] -# If true, the allowed features must exactly match the enabled feature set. If -# this is set there is no point setting `deny` -# exact = true - -# Certain crates/versions that will be skipped when doing duplicate detection. -skip = [ - - # { name = "ansi_term", version = "=0.11.0" }, -] -# Similarly to `skip` allows you to skip certain crates during duplicate -# detection. Unlike skip, it also includes the entire tree of transitive -# dependencies starting at the specified crate, up to a certain depth, which is -# by default infinite. -skip-tree = [ - - # { name = "ansi_term", version = "=0.11.0", depth = 20 }, -] - -# This section is considered when running `cargo deny check sources`. -# More documentation about the 'sources' section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html -[sources] -# Lint level for what to happen when a crate from a crate registry that is not -# in the allow list is encountered -unknown-registry = "warn" -# Lint level for what to happen when a crate from a git repository that is not -# in the allow list is encountered -unknown-git = "warn" -# List of URLs for allowed crate registries. Defaults to the crates.io index -# if not specified. If it is specified but empty, no registries are allowed. -allow-registry = ["https://github.com/rust-lang/crates.io-index"] -# List of URLs for allowed Git repositories -allow-git = [] - -[sources.allow-org] -# 1 or more github.com organizations to allow git sources for -# github = [""] -# 1 or more gitlab.com organizations to allow git sources for -# gitlab = [""] -# 1 or more bitbucket.org organizations to allow git sources for -# bitbucket = [""] - -[graph] -# If 1 or more target triples (and optionally, target_features) are specified, -# only the specified targets will be checked when running `cargo deny check`. -# This means, if a particular package is only ever used as a target specific -# dependency, such as, for example, the `nix` crate only being used via the -# `target_family = "unix"` configuration, that only having windows targets in -# this list would mean the nix crate, as well as any of its exclusive -# dependencies not shared by any other crates, would be ignored, as the target -# list here is effectively saying which targets you are building for. -targets = [ - - # The triple can be any string, but only the target triples built in to - # rustc (as of 1.40) can be checked against actual config expressions - # { triple = "x86_64-unknown-linux-musl" }, - # You can also specify which target_features you promise are enabled for a - # particular target. target_features are currently not validated against - # the actual valid features supported by the target architecture. - # { triple = "wasm32-unknown-unknown", features = ["atomics"] }, -] -# When creating the dependency graph used as the source of truth when checks are -# executed, this field can be used to prune crates from the graph, removing them -# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate -# is pruned from the graph, all of its dependencies will also be pruned unless -# they are connected to another crate in the graph that hasn't been pruned, -# so it should be used with care. The identifiers are [Package ID Specifications] -# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) -# exclude = [] -# If true, metadata will be collected with `--all-features`. Note that this can't -# be toggled off if true, if you want to conditionally enable `--all-features` it -# is recommended to pass `--all-features` on the cmd line instead -all-features = false -# If true, metadata will be collected with `--no-default-features`. The same -# caveat with `all-features` applies -no-default-features = false - -# If set, these feature will be enabled when collecting metadata. If `--features` -# is specified on the cmd line they will take precedence over this option. -# features = [] - -[output] -# When outputting inclusion graphs in diagnostics that include features, this -# option can be used to specify the depth at which feature edges will be added. -# This option is included since the graphs can be quite large and the addition -# of features from the crate(s) to all of the graph roots can be far too verbose. -# This option can be overridden via `--feature-depth` on the cmd line -feature-depth = 1 diff --git a/docs/cancellation.md b/docs/cancellation.md deleted file mode 100644 index 30d4b104f..000000000 --- a/docs/cancellation.md +++ /dev/null @@ -1,23 +0,0 @@ -# Cancellation - -`vp run` handles two kinds of cancellation: **Ctrl-C** (user interrupt) and **fast-fail** (a task exits with non-zero status). Both prevent new tasks from being scheduled and prevent caching of in-flight results, but they differ in how they treat running processes. - -## Ctrl-C - -When the user presses Ctrl-C: - -1. The OS delivers SIGINT (Unix) or CTRL_C_EVENT (Windows) directly to all processes in the terminal's foreground process group — both the runner and child tasks. This is standard OS behavior, not something `vp run` implements. -2. No new tasks are scheduled after the signal. -3. Results of in-flight tasks are **not cached**, even if a task handles the signal gracefully and exits 0. The output may be incomplete, so caching it would risk false cache hits on subsequent runs. - -## Fast-fail - -When any task exits with non-zero status: - -1. All other running child processes are killed immediately (`SIGKILL` on Unix, `TerminateJobObject` on Windows). -2. No new tasks are scheduled. -3. Results of other in-flight tasks are **not cached** (they were killed mid-execution). - -## Why interrupted tasks are not cached - -A task that receives Ctrl-C might exit 0 after partial work (e.g., a build tool that flushes what it has so far). Caching this result would mean the next `vp run` replays incomplete output and skips the real execution. By never caching interrupted results, `vp run` guarantees that the next run starts fresh. diff --git a/docs/concurrency.md b/docs/concurrency.md deleted file mode 100644 index 57b021cdc..000000000 --- a/docs/concurrency.md +++ /dev/null @@ -1,59 +0,0 @@ -# Concurrency - -`vp run` runs up to 4 tasks at once by default, respecting dependency order. - -## `--concurrency-limit`/`VP_RUN_CONCURRENCY_LIMIT` - -```sh -vp run -t --concurrency-limit 1 build # sequential -vp run -t --concurrency-limit 16 build # up to 16 at once -``` - -Also settable via the `VP_RUN_CONCURRENCY_LIMIT` env var. The CLI flag wins when both are present. - -**Default is 4** (same as pnpm) — enough to keep the machine busy while leaving room for tasks that already use all cores (bundlers, `tsc`, etc.). - -**Why the name `--concurrency-limit`**: The name makes clear this is an upper bound, not a target. We plan to add per-task concurrency control in `vite.config.*` (e.g. marking a build task as CPU-heavy), so the actual concurrency may end up lower than the limit. - -**Why not support CPU percentage** (like Turborepo's `--concurrency 50%`): This setting is meant to be a simple upper bound. CPU-core-aware concurrency control belongs at the per-task level, which we plan to add in the future. - -**Why support `VP_RUN_CONCURRENCY_LIMIT` env**: To allow people to apply it to every `vp run` without repeating the flag, especially in CI. - -**Why not support `concurrencyLimit` in `vite.config.*`**: The right limit usually depends on the machine, not the project. We reserve config-file concurrency settings for per-task hints (see above). - -Equivalent flags in other tools: - -| pnpm | Turborepo | Nx | -| ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| [`--workspace-concurrency`](https://pnpm.io/cli/recursive#--workspace-concurrency) | [`--concurrency`](https://turborepo.dev/docs/reference/run#--concurrency-number--percentage) | [`--parallel`](https://nx.dev/nx-api/nx/documents/run-many#parallel) | - -## `--parallel` - -Ignores dependency order and removes the concurrency limit: - -```sh -vp run -r --parallel dev -``` - -Useful for starting dev servers that all need to run at the same time. Same behavior as `--parallel` in pnpm. - -To ignore dependency order but still cap concurrency, combine both flags: - -```sh -vp run -r --parallel --concurrency-limit 8 lint -``` - -Equivalent flags in other tools: - -| pnpm | Turborepo | Nx | -| -------------------------------------------------- | ------------------------------------------------------------------- | --- | -| [`--parallel`](https://pnpm.io/cli/run#--parallel) | [`--parallel`](https://turborepo.dev/docs/reference/run#--parallel) | n/a | - -## Resolution order of concurrency limit - -The concurrency limit is resolved in this order (first match wins): - -1. `--concurrency-limit` CLI flag -2. `--parallel` without the above → unlimited -3. `VP_RUN_CONCURRENCY_LIMIT` env var -4. Default: 4 diff --git a/docs/inputs.md b/docs/inputs.md deleted file mode 100644 index 1d5f772bb..000000000 --- a/docs/inputs.md +++ /dev/null @@ -1,245 +0,0 @@ -# Task Inputs Configuration - -The `inputs` field controls which files are tracked for cache invalidation. When any tracked input file changes, the task's cache is invalidated and the task will re-run. - -## Default Behavior - -When `inputs` is omitted, vite-task automatically tracks files that the command reads during execution using fspy (file system spy): - -```json -{ - "tasks": { - "build": { - "command": "tsc" - } - } -} -``` - -Files read by `tsc` are automatically tracked. **In most cases, you don't need to configure `inputs` at all.** - -## Input Types - -### Glob Patterns - -Specify files using glob patterns relative to the package directory: - -```json -{ - "inputs": ["src/**/*.ts", "package.json"] -} -``` - -Supported glob syntax: - -- `*` - matches any characters except `/` -- `**` - matches any characters including `/` -- `?` - matches a single character -- `[abc]` - matches any character in the brackets -- `[!abc]` - matches any character not in the brackets - -### Auto-Inference - -Enable automatic input detection using fspy (file system spy): - -```json -{ - "inputs": [{ "auto": true }] -} -``` - -When enabled, vite-task automatically tracks files that the command actually reads during execution. This is the default behavior when `inputs` is omitted. - -### Negative Patterns - -Exclude files from tracking using `!` prefix: - -```json -{ - "inputs": ["src/**", "!src/**/*.test.ts"] -} -``` - -Negative patterns filter out files that would otherwise be matched by positive patterns or auto-inference. - -### Glob with Base Directory - -By default, glob patterns are resolved relative to the package directory. Use the object form to resolve patterns relative to a different base: - -```json -{ - "input": [ - "src/**", - { "pattern": "configs/tsconfig.json", "base": "workspace" }, - { "pattern": "!dist/**", "base": "workspace" } - ] -} -``` - -The `base` field is required and accepts: - -- `"package"` — resolve relative to the package directory (same as bare string globs) -- `"workspace"` — resolve relative to the workspace root - -This is useful for tracking shared configuration files at the workspace root, or excluding workspace-level directories from cache fingerprinting. - -Negation (`!` prefix) works in both bare strings and the object form. - -## Configuration Examples - -### Explicit Globs Only - -Specify exact files to track, disabling auto-inference: - -```json -{ - "tasks": { - "build": { - "command": "tsc", - "inputs": ["src/**/*.ts", "tsconfig.json"] - } - } -} -``` - -Only files matching the globs are tracked. Files read by the command but not matching the globs are ignored. - -### Auto-Inference with Exclusions - -Track inferred files but exclude certain patterns: - -```json -{ - "tasks": { - "build": { - "command": "tsc", - "inputs": [{ "auto": true }, "!dist/**", "!node_modules/**"] - } - } -} -``` - -Files in `dist/` and `node_modules/` won't trigger cache invalidation even if the command reads them. - -### Mixed Mode - -Combine explicit globs with auto-inference: - -```json -{ - "tasks": { - "build": { - "command": "tsc", - "inputs": ["package.json", { "auto": true }, "!**/*.test.ts"] - } - } -} -``` - -- `package.json` is always tracked (explicit) -- Files read by the command are tracked (auto) -- Test files are excluded from both (negative pattern) - -### No File Inputs - -Disable all file tracking (cache only on command/env changes): - -```json -{ - "tasks": { - "echo": { - "command": "echo hello", - "inputs": [] - } - } -} -``` - -The cache will only invalidate when the command itself or environment variables change. - -## Behavior Summary - -| Configuration | Auto-Inference | File Tracking | -| -------------------------------------------------------------- | -------------- | ---------------------------------- | -| `inputs` omitted | Enabled | Inferred files | -| `inputs: [{ "auto": true }]` | Enabled | Inferred files | -| `inputs: ["src/**"]` | Disabled | Matched files only | -| `inputs: [{ "auto": true }, "!dist/**"]` | Enabled | Inferred files except `dist/` | -| `inputs: ["pkg.json", { "auto": true }]` | Enabled | `pkg.json` + inferred files | -| `input: [{ "pattern": "tsconfig.json", "base": "workspace" }]` | Disabled | Matched files (workspace-relative) | -| `inputs: []` | Disabled | No files tracked | - -## Important Notes - -### Glob Base Directory - -By default, glob patterns (bare strings) are resolved relative to the **package directory** (where `package.json` is located), not the task's working directory (`cwd`). To resolve relative to the workspace root instead, use the object form with `"base": "workspace"` (see [Glob with Base Directory](#glob-with-base-directory)). - -```json -{ - "tasks": { - "build": { - "command": "tsc", - "cwd": "src", - "inputs": ["src/**/*.ts"] // Still relative to package root - } - } -} -``` - -### Negative Patterns Apply to Both Modes - -When using mixed mode, negative patterns filter both explicit globs AND auto-inferred files: - -```json -{ - "inputs": ["src/**", { "auto": true }, "!**/*.generated.ts"] -} -``` - -Files matching `*.generated.ts` are excluded whether they come from the `src/**` glob or from auto-inference. - -### Auto-Inference Behavior - -The auto-inference (fspy) is intentionally **cautious** - it tracks all files that a command reads, even auxiliary files. This means **negative patterns are expected to be useful** for filtering out files you don't want to trigger cache invalidation. - -Common files you might want to exclude: - -```json -{ - "inputs": [ - { "auto": true }, - "!**/*.tsbuildinfo", // TypeScript incremental build info - "!**/tsconfig.tsbuildinfo", - "!dist/**" // Build outputs that get read during builds - ] -} -``` - -**When to use positive patterns vs negative patterns:** - -- **Negative patterns (expected)**: Use these to exclude files that fspy correctly detected but you don't want tracked (like `.tsbuildinfo`, cache files, build outputs) -- **Positive patterns (usually indicates a bug)**: If you find yourself adding explicit positive patterns because fspy missed files that your command actually reads, this likely indicates a bug in fspy - -If you encounter a case where fspy fails to detect a file read, please [report the issue](https://github.com/voidzero-dev/vite-task/issues) with: - -1. The command being run -2. The file(s) that weren't detected -3. Steps to reproduce - -### Cache Disabled - -The `inputs` field cannot be used with `cache: false`: - -```json -// ERROR: inputs cannot be specified when cache is disabled -{ - "tasks": { - "dev": { - "command": "vite dev", - "cache": false, - "inputs": ["src/**"] // This will cause a parse error - } - } -} -``` diff --git a/docs/stdio.md b/docs/stdio.md deleted file mode 100644 index 2a9d8ebf0..000000000 --- a/docs/stdio.md +++ /dev/null @@ -1,104 +0,0 @@ -# Task Standard I/O - -How stdin, stdout, and stderr are connected to task processes, controlled by the `--log` flag. Largely inspired by [npm-run-all2](https://github.com/bcomnes/npm-run-all2)'s stdio handling, with differences explained in the [appendix](#appendix-npm-run-all2-behavior). - -## The `--log` Flag - -``` -vp run build --log= -``` - -| Mode | Description | -| --------------------------- | ----------------------------------------------------------------------------- | -| `interleaved` **(default)** | Output streams directly to the terminal as tasks produce it. | -| `labeled` | Each line is prefixed with `[packageName#taskName]`. | -| `grouped` | Output is buffered per task and printed as a block after each task completes. | - -### Examples - -In `interleaved` mode, task names are omitted from the command line to keep output clean. In `labeled` and `grouped` modes, the `[pkg#task]` prefix is necessary to identify which task produced each line. - -#### `interleaved` - -Output goes to the terminal as soon as it's produced. When running multiple tasks in parallel, lines from different tasks may intermix: - -``` -~/packages/app$ vp dev -~/packages/docs$ vp dev - VITE v6.0.0 ready in 200 ms - - ➜ Local: http://localhost:5173/ - VITE v6.0.0 ready in 150 ms - - ➜ Local: http://localhost:5174/ -``` - -#### `labeled` - -Each line of stdout and stderr is prefixed with the task identifier. Output is still streamed as it arrives (not buffered): - -``` -[app#dev] ~/packages/app$ vp dev -[docs#dev] ~/packages/docs$ vp dev -[app#dev] VITE v6.0.0 ready in 200 ms -[app#dev] -[app#dev] ➜ Local: http://localhost:5173/ -[docs#dev] VITE v6.0.0 ready in 150 ms -[docs#dev] -[docs#dev] ➜ Local: http://localhost:5174/ -``` - -#### `grouped` - -All output (stdout and stderr) for each task is buffered and printed as a single block when the task completes. Nothing is shown for a task until it finishes: - -``` -[app#dev] ~/packages/app$ vp dev -[docs#dev] ~/packages/docs$ vp dev -── app#dev ── - VITE v6.0.0 ready in 200 ms - - ➜ Local: http://localhost:5173/ - -── docs#dev ── - VITE v6.0.0 ready in 150 ms - - ➜ Local: http://localhost:5174/ -``` - -## stdio by Mode - -| Mode | stdin | stdout | stderr | -| ------------------------ | ----------- | ---------------------------- | ---------------------------- | -| `interleaved`, cache on | `/dev/null` | Piped (streamed + collected) | Piped (streamed + collected) | -| `interleaved`, cache off | Inherited | Inherited | Inherited | -| `labeled` | `/dev/null` | Piped (prefixed + collected) | Piped (prefixed + collected) | -| `grouped` | `/dev/null` | Piped (buffered + collected) | Piped (buffered + collected) | - -### Key Rules - -1. **stdin is `/dev/null` except for uncached tasks in `interleaved` mode.** Cached tasks must have deterministic behavior — inheriting stdin would make output dependent on interactive input, breaking cache correctness. Uncached `interleaved` tasks inherit stdin, allowing interactive prompts. - -2. **stdout and stderr are piped (collected) when caching is enabled.** The collected output is stored in the cache and replayed on cache hits. In `interleaved` mode, output is still streamed to the terminal as it arrives — piping is transparent to the user. Uncached `interleaved` tasks inherit stdout/stderr directly. - -3. **stderr follows the same rules as stdout in all modes.** Unlike npm-run-all2 where `--aggregate-output` only groups stdout while inheriting stderr, `grouped` mode buffers both stdout and stderr, keeping a task's error output together with its regular output. - -## Cache Replay - -When a cached task is replayed, its stored stdout and stderr are written to the terminal using the same formatting rules as the current `--log` mode. For example, a task cached in `interleaved` mode can be replayed in `labeled` mode and will receive the appropriate prefix. - -## Appendix: npm-run-all2 Behavior - -For reference, npm-run-all2 controls stdio via two independent flags: - -| Mode | stdin | stdout | stderr | -| -------------------- | --------- | ------------------------------------------ | ------------------------------------ | -| Default | Inherited | Inherited | Inherited | -| `--print-label` | Inherited | Each line prefixed with `[taskName]` | Each line prefixed with `[taskName]` | -| `--aggregate-output` | Inherited | Grouped per task, printed after completion | Inherited (not grouped) | - -Notable differences from vite-task: - -- **stdin is always inherited.** npm-run-all2 does not have a caching system, so there is no need to prevent interactive input. -- **`--aggregate-output` only groups stdout.** stderr is inherited and streams directly to the terminal, meaning error output from parallel tasks can still intermix. vite-task's `grouped` mode buffers both streams. -- **Two separate flags** (`--print-label`, `--aggregate-output`) instead of a single `--log` enum. The flags are mutually exclusive in practice but this isn't enforced. diff --git a/justfile b/justfile deleted file mode 100644 index f4b8b865d..000000000 --- a/justfile +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env -S just --justfile - -set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] -set shell := ["bash", "-cu"] - -_default: - @just --list -u - -alias r := ready - -init: - cargo binstall watchexec-cli cargo-insta typos-cli cargo-shear@1.11.1 cargo-autoinherit taplo-cli -y - -ready: - git diff --exit-code --quiet - typos - just fmt - just check - just test - just lint - just doc - -watch *args='': - watchexec --no-vcs-ignore {{args}} - -fmt: - cargo autoinherit - cargo shear --fix - cargo fmt --all - pnpm exec vp fmt - -check: - cargo check --workspace --all-features --all-targets --locked - -watch-check: - just watch "'cargo check; cargo clippy'" - -test: - cargo test - -lint: - cargo clippy --workspace --all-targets --all-features -- --deny warnings - -lint-linux: - cargo-zigbuild clippy --workspace --all-targets --all-features --target x86_64-unknown-linux-gnu -- --deny warnings - -lint-windows: - cargo-xwin clippy --workspace --all-targets --all-features --target x86_64-pc-windows-msvc -- --deny warnings - -[unix] -doc: - RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --document-private-items - -[windows] -doc: - $Env:RUSTDOCFLAGS='-D warnings'; cargo doc --no-deps --document-private-items diff --git a/package.json b/package.json deleted file mode 100644 index f2cf88432..000000000 --- a/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "vite-task-monorepo", - "private": true, - "license": "MIT", - "type": "module", - "scripts": { - "prepare": "vp config", - "build-vite-task-client-types": "tsc -p packages/vite-task-client/tsconfig.json" - }, - "devDependencies": { - "@tsconfig/strictest": "catalog:", - "@types/node": "catalog:", - "typescript": "catalog:", - "vite": "catalog:", - "vite-plus": "catalog:" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "packageManager": "pnpm@11.1.2" -} diff --git a/packages/tools/README.md b/packages/tools/README.md deleted file mode 100644 index 629f20d92..000000000 --- a/packages/tools/README.md +++ /dev/null @@ -1 +0,0 @@ -This package provides Node.js dependencies (oxlint, oxfmt, cross-env) used by fspy tests. diff --git a/packages/tools/package.json b/packages/tools/package.json deleted file mode 100644 index 0fb97185d..000000000 --- a/packages/tools/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "vite-task-tools", - "private": true, - "type": "module", - "dependencies": { - "@playwright/browser-chromium": "catalog:", - "@vitest/browser-playwright": "catalog:", - "@voidzero-dev/vite-task-client": "workspace:*", - "bun": "catalog:", - "cross-env": "catalog:", - "deno": "catalog:", - "oxfmt": "catalog:", - "oxlint": "catalog:", - "oxlint-tsgolint": "catalog:", - "playwright": "catalog:", - "vite": "catalog:", - "vitest": "catalog:" - } -} diff --git a/packages/vite-task-client/README.md b/packages/vite-task-client/README.md deleted file mode 100644 index e186b6bf6..000000000 --- a/packages/vite-task-client/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# @voidzero-dev/vite-task-client - -A tiny Node.js client that lets your tool talk to the -[Vite+](https://github.com/voidzero-dev/vite-plus) task runner (`vp run`) -it's running under. Use it to hand the runner more precise -cache-correctness information than it can infer from the outside. - -Outside a runner-managed task, every call is a graceful no-op — you can -call into this from a tool that's also used standalone without any -runtime detection or conditionals. - -## Install - -```sh -npm install @voidzero-dev/vite-task-client -``` - -## Quick start - -```js -import { ignoreInput, ignoreOutput, disableCache, getEnv } from '@voidzero-dev/vite-task-client'; - -ignoreInput('./node_modules/.cache/my-tool'); - -// `vp run` only exposes envs the task config declares; for everything -// else, `getEnv` fetches the value from the runner and registers it as -// a cache-key dependency in the same call. -const apiVersion = process.env.MY_API_VERSION ?? getEnv('MY_API_VERSION'); - -if (somethingNonDeterministicHappened) disableCache(); -``` - -## Why this exists - -`vp run` decides whether a task's cached result is reusable by hashing -everything your task read and everything it depends on. It infers that -set automatically — watching filesystem syscalls, scanning declared -inputs and env vars. That's safe but can be too coarse: - -- Your tool maintains its own cache under `node_modules/.cache/…`. Every - miss there would invalidate every other run, even though the contents - don't actually affect your output. -- Your tool reads `process.env.MY_API_VERSION`, and `vp run`'s task - config doesn't list it. -- Your tool has a non-deterministic mode it sometimes falls into and - should skip the cache entirely. - -These functions give you a precise way to correct each case from inside -your tool, without forcing your users to rewrite their `vp run` task -config. - -## API - -See [`src/index.d.ts`](./src/index.d.ts) for the full signatures and per-function -behavior. - -## For `vite-task` developers - -This package is a thin pure-JS wrapper with **no `dependencies` in -`package.json`** — its only runtime artifact is the napi addon, which -the runner provides at execution time. - -That means when you change this package in a `vite-task` PR, the -consuming tool can pull your unpublished commit directly via a git URL -with a subpath, no npm release required: - -```jsonc -// In the consuming tool's package.json -{ - "dependencies": { - "@voidzero-dev/vite-task-client": "github:voidzero-dev/vite-task#&path:/packages/vite-task-client", - }, -} -``` - -(`&path:` is supported by pnpm. For npm/yarn, see your package -manager's docs on monorepo-subpath git installs.) diff --git a/packages/vite-task-client/package.json b/packages/vite-task-client/package.json deleted file mode 100644 index ffb25518e..000000000 --- a/packages/vite-task-client/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@voidzero-dev/vite-task-client", - "version": "0.2.0", - "description": "Hand the Vite+ task runner (vp run) precise cache-correctness information from inside your tool.", - "keywords": [ - "cache", - "caching", - "monorepo", - "runner", - "task", - "vite", - "vite-plus", - "vp" - ], - "homepage": "https://github.com/voidzero-dev/vite-task/tree/main/packages/vite-task-client#readme", - "bugs": "https://github.com/voidzero-dev/vite-task/issues", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/voidzero-dev/vite-task.git", - "directory": "packages/vite-task-client" - }, - "files": [ - "src" - ], - "type": "module", - "main": "./src/index.js", - "types": "./src/index.d.ts", - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=18" - } -} diff --git a/packages/vite-task-client/src/index.d.ts b/packages/vite-task-client/src/index.d.ts deleted file mode 100644 index f61f2882a..000000000 --- a/packages/vite-task-client/src/index.d.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Tell the runner to ignore reads under `path` when inferring cache inputs. - * - * No-op when not running inside a runner. - * - * @param {string} path - * @returns {void} - */ -export function ignoreInput(path: string): void; -/** - * Tell the runner to ignore writes under `path` when inferring cache outputs. - * - * No-op when not running inside a runner. - * - * @param {string} path - * @returns {void} - */ -export function ignoreOutput(path: string): void; -/** - * Tell the runner not to cache this run. - * - * No-op when not running inside a runner. - * - * @returns {void} - */ -export function disableCache(): void; -/** - * Ask the runner for the value of the env var `name` and return it, or - * `undefined` when the runner has no such env. - * - * With `tracked: true` (the default) the runner records `name` as a - * dependency, so a change to its value invalidates this run's cache entry. - * - * Has no effect on `process.env`; the caller decides what to do with the - * returned value. Returns `undefined` when not running inside a runner. - * - * @param {string} name - * @param {{ tracked?: boolean }} [options] - * @returns {string | undefined} - */ -export function getEnv(name: string, options?: { - tracked?: boolean; -}): string | undefined; -/** - * Ask the runner for matching envs and return the match-set as a plain object. - * - * Pass a glob string (e.g. `VITE_*`) to use glob matching, or pass - * `{ prefix: 'VITE_' }` to match env names by literal prefix. - * - * With `tracked: true` (the default) the runner records the pattern as a - * dependency, so adding, removing, or changing a matching env invalidates - * this run's cache entry. - * - * Has no effect on `process.env`; the caller decides what to do with the - * returned values. Returns an empty object when not running inside a runner. - * - * @param {GetEnvsQuery} query - * @param {GetEnvOptions} [options] - * @returns {Record} - */ -export function getEnvs(query: GetEnvsQuery, options?: GetEnvOptions): Record; -export type GetEnvOptions = { - tracked?: boolean; -}; -export type GetEnvsQuery = string | { - prefix: string; -}; diff --git a/packages/vite-task-client/src/index.js b/packages/vite-task-client/src/index.js deleted file mode 100644 index dd8771159..000000000 --- a/packages/vite-task-client/src/index.js +++ /dev/null @@ -1,130 +0,0 @@ -// The JSDoc in this file is the source of truth for the package's public -// types. `index.d.ts` is generated from it via `pnpm run build:types` -// (using `tsc` with `@tsconfig/strictest`) — edit JSDoc here, not the -// `.d.ts`. CI fails if the committed `.d.ts` drifts from a fresh regen. - -import { createRequire } from 'node:module'; - -/** - * @typedef {{ tracked?: boolean }} GetEnvOptions - */ - -/** - * @typedef {string | { prefix: string }} GetEnvsQuery - */ - -/** - * Methods exposed by the napi addon. Keep this shape in sync with the - * `RunnerClient` returned by `load()` in - * `crates/vite_task_client_napi/src/lib.rs` — any new method added there - * needs a matching entry here, and vice versa. - * - * @type {{ - * ignoreInput: (path: string) => void, - * ignoreOutput: (path: string) => void, - * disableCache: () => void, - * getEnv: (name: string, options?: GetEnvOptions) => string | undefined, - * getEnvs: (query: GetEnvsQuery, options?: GetEnvOptions) => Record, - * } | null | undefined} - */ -let addon; - -function load() { - if (addon !== undefined) return addon; - try { - const path = process.env['VP_RUN_NODE_CLIENT_PATH']; - if (path) { - // The addon exports a `load(options?)` factory rather than the - // methods directly, so the addon shape can evolve in lockstep with - // this wrapper: a future wrapper can pass `{ version: N }` to opt - // into a new shape without breaking older addons that only know v1. - // Today's wrapper passes nothing and accepts whatever the addon's - // current default version returns. - addon = createRequire(import.meta.url)(path).load(); - return addon; - } - } catch { - // Fall through — the runner's IPC env is absent or the addon refused to - // load. Memoize the unavailable decision so subsequent calls don't retry. - } - addon = null; - return addon; -} - -/** - * Tell the runner to ignore reads under `path` when inferring cache inputs. - * - * No-op when not running inside a runner. - * - * @param {string} path - * @returns {void} - */ -export function ignoreInput(path) { - load()?.ignoreInput(path); -} - -/** - * Tell the runner to ignore writes under `path` when inferring cache outputs. - * - * No-op when not running inside a runner. - * - * @param {string} path - * @returns {void} - */ -export function ignoreOutput(path) { - load()?.ignoreOutput(path); -} - -/** - * Tell the runner not to cache this run. - * - * No-op when not running inside a runner. - * - * @returns {void} - */ -export function disableCache() { - load()?.disableCache(); -} - -/** - * Ask the runner for the value of the env var `name` and return it, or - * `undefined` when the runner has no such env. - * - * With `tracked: true` (the default) the runner records `name` as a - * dependency, so a change to its value invalidates this run's cache entry. - * - * Has no effect on `process.env`; the caller decides what to do with the - * returned value. Returns `undefined` when not running inside a runner. - * - * @param {string} name - * @param {{ tracked?: boolean }} [options] - * @returns {string | undefined} - */ -export function getEnv(name, options) { - const a = load(); - if (!a) return undefined; - return a.getEnv(name, options); -} - -/** - * Ask the runner for matching envs and return the match-set as a plain object. - * - * Pass a glob string (e.g. `VITE_*`) to use glob matching, or pass - * `{ prefix: 'VITE_' }` to match env names by literal prefix. - * - * With `tracked: true` (the default) the runner records the pattern as a - * dependency, so adding, removing, or changing a matching env invalidates - * this run's cache entry. - * - * Has no effect on `process.env`; the caller decides what to do with the - * returned values. Returns an empty object when not running inside a runner. - * - * @param {GetEnvsQuery} query - * @param {GetEnvOptions} [options] - * @returns {Record} - */ -export function getEnvs(query, options) { - const a = load(); - if (!a) return {}; - return a.getEnvs(query, options); -} diff --git a/packages/vite-task-client/tsconfig.json b/packages/vite-task-client/tsconfig.json deleted file mode 100644 index 3a649a72b..000000000 --- a/packages/vite-task-client/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "@tsconfig/strictest/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "declaration": true, - "emitDeclarationOnly": true, - "module": "NodeNext", - "moduleResolution": "NodeNext", - "target": "ES2022", - "lib": ["ES2022"], - "types": ["node"] - }, - "include": ["src/**/*.js"] -} diff --git a/playground/README.md b/playground/README.md deleted file mode 100644 index 863dce49b..000000000 --- a/playground/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Playground - -A workspace for manually testing `cargo run --bin vt run ...`. - -## Structure - -``` -playground/ -├── packages/ -│ ├── app/ → depends on @playground/lib -│ ├── lib/ → depends on @playground/utils -│ └── utils/ → no dependencies -└── vite-task.json → workspace-level task config -``` - -Dependency chain: `app → lib → utils` - -## Scripts & Tasks - -Tasks are defined in each package's `vite-task.json` with caching enabled. `dev` is a package.json script (not cached). - -| Name | Type | Packages | Cached | Description | -| ----------- | ------ | --------------- | ------ | ---------------------------------------------- | -| `build` | task | app, lib, utils | yes | Prints a build message | -| `test` | task | app, lib, utils | yes | Prints a test message | -| `lint` | task | app, lib, utils | yes | Prints a lint message | -| `typecheck` | task | app, lib | yes | Prints a typecheck message | -| `dev` | script | app, lib | no | Long-running process (prints every 2s, ctrl-c) | diff --git a/playground/package.json b/playground/package.json deleted file mode 100644 index 51466b175..000000000 --- a/playground/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "playground", - "private": true -} diff --git a/playground/packages/app/build.mjs b/playground/packages/app/build.mjs deleted file mode 100644 index 9b3bc870b..000000000 --- a/playground/packages/app/build.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Building app'); diff --git a/playground/packages/app/dev.mjs b/playground/packages/app/dev.mjs deleted file mode 100644 index c6e4ab0e3..000000000 --- a/playground/packages/app/dev.mjs +++ /dev/null @@ -1,6 +0,0 @@ -process.on('SIGINT', () => { - console.log('app: dev server stopped'); - process.exit(0); -}); - -setInterval(() => console.log('app: waiting for changes...'), 2000); diff --git a/playground/packages/app/lint.mjs b/playground/packages/app/lint.mjs deleted file mode 100644 index 7f54bd9b9..000000000 --- a/playground/packages/app/lint.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Linting app'); diff --git a/playground/packages/app/package.json b/playground/packages/app/package.json deleted file mode 100644 index 055ace37f..000000000 --- a/playground/packages/app/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@playground/app", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "node dev.mjs" - }, - "dependencies": { - "@playground/lib": "workspace:*" - } -} diff --git a/playground/packages/app/src/index.ts b/playground/packages/app/src/index.ts deleted file mode 100644 index 7db55b497..000000000 --- a/playground/packages/app/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { sum } from '@playground/lib'; - -console.log(sum(1, 2, 3)); diff --git a/playground/packages/app/test.mjs b/playground/packages/app/test.mjs deleted file mode 100644 index 7ffabb41e..000000000 --- a/playground/packages/app/test.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Testing app'); diff --git a/playground/packages/app/typecheck.mjs b/playground/packages/app/typecheck.mjs deleted file mode 100644 index 2980e420e..000000000 --- a/playground/packages/app/typecheck.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Typechecking app'); diff --git a/playground/packages/app/vite-task.json b/playground/packages/app/vite-task.json deleted file mode 100644 index 58a9b2b48..000000000 --- a/playground/packages/app/vite-task.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "tasks": { - "build": { - "command": "node build.mjs" - }, - "test": { - "command": "node test.mjs" - }, - "typecheck": { - "command": "node typecheck.mjs" - }, - "lint": { - "command": "node lint.mjs" - } - } -} diff --git a/playground/packages/lib/build.mjs b/playground/packages/lib/build.mjs deleted file mode 100644 index 9af60efc3..000000000 --- a/playground/packages/lib/build.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Building lib'); diff --git a/playground/packages/lib/dev.mjs b/playground/packages/lib/dev.mjs deleted file mode 100644 index 8836ae3aa..000000000 --- a/playground/packages/lib/dev.mjs +++ /dev/null @@ -1,6 +0,0 @@ -process.on('SIGINT', () => { - console.log('lib: dev server stopped'); - process.exit(0); -}); - -setInterval(() => console.log('lib: waiting for changes...'), 2000); diff --git a/playground/packages/lib/lint.mjs b/playground/packages/lib/lint.mjs deleted file mode 100644 index 85e0d9d8a..000000000 --- a/playground/packages/lib/lint.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Linting lib'); diff --git a/playground/packages/lib/package.json b/playground/packages/lib/package.json deleted file mode 100644 index fddb3aab3..000000000 --- a/playground/packages/lib/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@playground/lib", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "node dev.mjs" - }, - "dependencies": { - "@playground/utils": "workspace:*" - } -} diff --git a/playground/packages/lib/src/index.ts b/playground/packages/lib/src/index.ts deleted file mode 100644 index f7fa1e131..000000000 --- a/playground/packages/lib/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { add } from '@playground/utils'; - -export function sum(...nums: number[]): number { - return nums.reduce((acc, n) => add(acc, n), 0); -} diff --git a/playground/packages/lib/test.mjs b/playground/packages/lib/test.mjs deleted file mode 100644 index 6309a2034..000000000 --- a/playground/packages/lib/test.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Testing lib'); diff --git a/playground/packages/lib/typecheck.mjs b/playground/packages/lib/typecheck.mjs deleted file mode 100644 index 5f7e4b7da..000000000 --- a/playground/packages/lib/typecheck.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Typechecking lib'); diff --git a/playground/packages/lib/vite-task.json b/playground/packages/lib/vite-task.json deleted file mode 100644 index 58a9b2b48..000000000 --- a/playground/packages/lib/vite-task.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "tasks": { - "build": { - "command": "node build.mjs" - }, - "test": { - "command": "node test.mjs" - }, - "typecheck": { - "command": "node typecheck.mjs" - }, - "lint": { - "command": "node lint.mjs" - } - } -} diff --git a/playground/packages/utils/build.mjs b/playground/packages/utils/build.mjs deleted file mode 100644 index f0cc45c76..000000000 --- a/playground/packages/utils/build.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Building utils'); diff --git a/playground/packages/utils/lint.mjs b/playground/packages/utils/lint.mjs deleted file mode 100644 index 64299d21d..000000000 --- a/playground/packages/utils/lint.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Linting utils'); diff --git a/playground/packages/utils/package.json b/playground/packages/utils/package.json deleted file mode 100644 index 8036670aa..000000000 --- a/playground/packages/utils/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@playground/utils", - "version": "0.0.0", - "private": true -} diff --git a/playground/packages/utils/src/index.ts b/playground/packages/utils/src/index.ts deleted file mode 100644 index 8d9b8a22a..000000000 --- a/playground/packages/utils/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function add(a: number, b: number): number { - return a + b; -} diff --git a/playground/packages/utils/test.mjs b/playground/packages/utils/test.mjs deleted file mode 100644 index 2c833107b..000000000 --- a/playground/packages/utils/test.mjs +++ /dev/null @@ -1 +0,0 @@ -console.log('Testing utils'); diff --git a/playground/packages/utils/vite-task.json b/playground/packages/utils/vite-task.json deleted file mode 100644 index b86327b6a..000000000 --- a/playground/packages/utils/vite-task.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "tasks": { - "build": { - "command": "node build.mjs" - }, - "test": { - "command": "node test.mjs" - }, - "lint": { - "command": "node lint.mjs" - } - } -} diff --git a/playground/pnpm-lock.yaml b/playground/pnpm-lock.yaml deleted file mode 100644 index ef78a0a05..000000000 --- a/playground/pnpm-lock.yaml +++ /dev/null @@ -1,23 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: {} - - packages/app: - dependencies: - '@playground/lib': - specifier: workspace:* - version: link:../lib - - packages/lib: - dependencies: - '@playground/utils': - specifier: workspace:* - version: link:../utils - - packages/utils: {} diff --git a/playground/pnpm-workspace.yaml b/playground/pnpm-workspace.yaml deleted file mode 100644 index 924b55f42..000000000 --- a/playground/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - packages/* diff --git a/playground/vite-task.json b/playground/vite-task.json deleted file mode 100644 index 0967ef424..000000000 --- a/playground/vite-task.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 719ac9924..000000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,2857 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -catalogs: - default: - '@playwright/browser-chromium': - specifier: 1.61.1 - version: 1.61.1 - '@tsconfig/strictest': - specifier: ^2.0.8 - version: 2.0.8 - '@types/node': - specifier: 25.0.3 - version: 25.0.3 - '@vitest/browser-playwright': - specifier: 4.1.10 - version: 4.1.10 - bun: - specifier: 1.3.14 - version: 1.3.14 - cross-env: - specifier: ^10.1.0 - version: 10.1.0 - deno: - specifier: 2.9.3 - version: 2.9.3 - oxfmt: - specifier: 0.57.0 - version: 0.57.0 - oxlint: - specifier: ^1.55.0 - version: 1.73.0 - oxlint-tsgolint: - specifier: ^0.24.0 - version: 0.24.0 - typescript: - specifier: ^6.0.3 - version: 6.0.3 - vite-plus: - specifier: 0.2.5 - version: 0.2.5 - vitest: - specifier: 4.1.10 - version: 4.1.10 - -overrides: - playwright: 1.61.1 - vite: npm:@voidzero-dev/vite-plus-core@0.2.4 - vite-task-tools>vite: 8.1.5 - -importers: - - .: - devDependencies: - '@tsconfig/strictest': - specifier: 'catalog:' - version: 2.0.8 - '@types/node': - specifier: 'catalog:' - version: 25.0.3 - typescript: - specifier: 'catalog:' - version: 6.0.3 - vite: - specifier: npm:@voidzero-dev/vite-plus-core@0.2.4 - version: '@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)' - vite-plus: - specifier: 'catalog:' - version: 0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(typescript@6.0.3) - - packages/tools: - dependencies: - '@playwright/browser-chromium': - specifier: 'catalog:' - version: 1.61.1 - '@vitest/browser-playwright': - specifier: 'catalog:' - version: 4.1.10(playwright@1.61.1)(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10) - '@voidzero-dev/vite-task-client': - specifier: workspace:* - version: link:../vite-task-client - bun: - specifier: 'catalog:' - version: 1.3.14 - cross-env: - specifier: 'catalog:' - version: 10.1.0 - deno: - specifier: 'catalog:' - version: 2.9.3 - oxfmt: - specifier: 'catalog:' - version: 0.57.0(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3))) - oxlint: - specifier: 'catalog:' - version: 1.73.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3))) - oxlint-tsgolint: - specifier: 'catalog:' - version: 0.24.0 - playwright: - specifier: 1.61.1 - version: 1.61.1 - vite: - specifier: 8.1.5 - version: 8.1.5(@types/node@25.0.3) - vitest: - specifier: 'catalog:' - version: 4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(vite@8.1.5(@types/node@25.0.3)) - - packages/vite-task-client: {} - -packages: - - '@babel/code-frame@7.29.7': - resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} - engines: {node: '>=6.9.0'} - - '@babel/runtime@7.29.7': - resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} - engines: {node: '>=6.9.0'} - - '@blazediff/core@1.9.1': - resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} - - '@deno/darwin-arm64@2.9.3': - resolution: {integrity: sha512-G9BiE99ufFiZPQVN8oGIib1oIGdi3uIAuwfet7PkjPX7OFgC484nFJiRV/BsZDpCtFaeUi9zz8WYikXyLxtN/w==} - cpu: [arm64] - os: [darwin] - - '@deno/darwin-x64@2.9.3': - resolution: {integrity: sha512-JEPCaAkqHODcqjyY5a5ScOlPEAn/6Iyx44ldRpk9+gq0XjLR0rsvI6Xe2mt9cuQF1Yp8cvpvi1meSi1TYoE0nQ==} - cpu: [x64] - os: [darwin] - - '@deno/linux-arm64-glibc@2.9.3': - resolution: {integrity: sha512-N9OnMg9Ah1k2hvbqo942/lD6IA6ELs4NYYOYtTVuekKgupyhKtVMpNqwgDzfqxL/P4eebk4+l9/oKTw4NzpKdg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@deno/linux-x64-glibc@2.9.3': - resolution: {integrity: sha512-HSChBN7EkeErbDYVVu16dhLVHVIe/U+8eOAedrsuOsSThE+4Ln+IOfI+UnNU09pqnWc/ZMDI+fVRuAmKX/PRaw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@deno/win32-arm64@2.9.3': - resolution: {integrity: sha512-b0/yzyB4BrTzDy37fHOVoKChP5XgKxoShWkDVXcBNEh8ezxKwTgoHA8HO2BGkynrFoQ5HqxyQ6WXqSwFDSI21w==} - cpu: [arm64] - os: [win32] - - '@deno/win32-x64@2.9.3': - resolution: {integrity: sha512-d+z3ySG+sP8a7NYFf739z1QwEcWxjKYAnc6YvDErwmc/1JdjarPbKJNXhug2rb2XViM29t4t9jmQKU/+qHYMJg==} - cpu: [x64] - os: [win32] - - '@emnapi/core@1.11.1': - resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} - - '@emnapi/runtime@1.11.1': - resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - - '@emnapi/wasi-threads@1.2.2': - resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - - '@epic-web/invariant@1.0.0': - resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@napi-rs/wasm-runtime@1.1.6': - resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - - '@oven/bun-darwin-aarch64@1.3.14': - resolution: {integrity: sha512-Omj20SuiHBOUjUBIyqtkNjSUIjOtEOJwmbix/ZyFH4BaQ6OZTaaRWIR4TjHVz0yadHgli6lLTiAh1uarnvD49A==} - cpu: [arm64] - os: [darwin] - - '@oven/bun-darwin-x64-baseline@1.3.14': - resolution: {integrity: sha512-OSfsTZstc898HHElhU4NccaBGOSSDn5VfahiVTnidZ9B/+wb7WTyfZJaBeJcfjwJ9H2W9uTh2TGtl3UfcXgV9g==} - cpu: [x64] - os: [darwin] - - '@oven/bun-darwin-x64@1.3.14': - resolution: {integrity: sha512-FFj3QdU/OhlDyZOJ8CWfN5eWLpRlT4qjZg7lMQi7jA6GuoY5ajlO1zWLP/MuHYRSbXQUvV52RejNi8DVnAp13w==} - cpu: [x64] - os: [darwin] - - '@oven/bun-freebsd-aarch64@1.3.14': - resolution: {integrity: sha512-LIKrXaFxAHybVO5Pf+9XP2FHUj/5APvXTUKk9dqHm5iFz4oH+W24cmhjkJirNujh9hKeTyrpWSe3no9JZKowIw==} - cpu: [arm64] - os: [freebsd] - - '@oven/bun-freebsd-x64@1.3.14': - resolution: {integrity: sha512-uwD+fGUH1ADpIF3B1U2jWzzb20QwRLZfj5QZ28GUCGrAJ/nTmWrD6YYGsblCY1wuhldRez3lU40AyuvSCyLYmw==} - cpu: [x64] - os: [freebsd] - - '@oven/bun-linux-aarch64-android@1.3.14': - resolution: {integrity: sha512-y4kq5b85lsrmFb9Xvi4w9mA5IEFJkLMrSmYn06q24KjL9rUWDWO3VFZEtteZxUN5+ec3Zm5S8OnJw1umaCbVjA==} - cpu: [arm64] - os: [android] - - '@oven/bun-linux-aarch64-musl@1.3.14': - resolution: {integrity: sha512-jmqOA92Cd1NL/1XBd4bFkJLxQ86K0RW7ohxS2qzzAvuitO4JiIxjjTeCspoU44zCozH72HpfZfUE2On31OjnWA==} - cpu: [arm64] - os: [linux] - - '@oven/bun-linux-aarch64@1.3.14': - resolution: {integrity: sha512-X5SsPZHs+iYO8R/efIcRtc7gT2Q2DgPfliCxEkx4cXBumwkw0c/EsHMNwH3EgGpCDaZ7IYVPhpCG/xBOQHEwZw==} - cpu: [arm64] - os: [linux] - - '@oven/bun-linux-x64-android@1.3.14': - resolution: {integrity: sha512-qe9e1d+3VAEU7nAA2ol9Jvmy/o99PVMSgZhHn7Q/9O3YcDrfEqyQ8zm4zoe5qTEo8HZH0dN03Le0Ys2eQPs7eg==} - cpu: [x64] - os: [android] - - '@oven/bun-linux-x64-baseline@1.3.14': - resolution: {integrity: sha512-q/8EdOC0yUE8FPeoOVq8/Pw5I9/tJaYmUfO/uDUAREx8IUnOJH1RJ5A3BjFqre8pvJoiZA9AovPJq5FnNNjSxA==} - cpu: [x64] - os: [linux] - - '@oven/bun-linux-x64-musl-baseline@1.3.14': - resolution: {integrity: sha512-n6iE71G4lQE4XkrZhQQcL5YUlxDbnq6nqV7zeQi33PMsLT/0kYE+RvHOtBWZ3w0wMdXZfINmp63hIb9ijUBGtw==} - cpu: [x64] - os: [linux] - - '@oven/bun-linux-x64-musl@1.3.14': - resolution: {integrity: sha512-GBCB/k/sIqcr06eTNgg7g46qiUv35Jasx4XiccJ/n7RGqrE4RWUD/XJBbWFprVPjvqd59+QtSnS99XGqvftHfg==} - cpu: [x64] - os: [linux] - - '@oven/bun-linux-x64@1.3.14': - resolution: {integrity: sha512-7OVTAKvwfPmSbIV1HpdOoVVx5VRc427GuPPne93N6vk4eQBPId9nXmZDh9/zGaKPdbVjVtQSZafWQoUjx38Utw==} - cpu: [x64] - os: [linux] - - '@oven/bun-windows-aarch64@1.3.14': - resolution: {integrity: sha512-T7s3x/BsVKQObGU6QDkZeI6wKynzqGbBH1yI77jrrj5siElclxr3DQrDIk8CV4G5/SJq2HHq4kpLyYY2DKCSmA==} - cpu: [arm64] - os: [win32] - - '@oven/bun-windows-x64-baseline@1.3.14': - resolution: {integrity: sha512-uIjLUC1S9DWgICzuoMba7vurBJnBruE4S5CxnvmZkdqWVXRzx1Rgu636HoH+k0qeaQCFh3jeG3JQ1y6fRHv0sw==} - cpu: [x64] - os: [win32] - - '@oven/bun-windows-x64@1.3.14': - resolution: {integrity: sha512-mUFWL3BoYkNpjd8e9PqROiFF/1Xeotq20mABJsiQH62jM1g5zqWh4khw1RZ6bX8Q8fWvlPaxG1PjofkmjUi3vg==} - cpu: [x64] - os: [win32] - - '@oxc-project/runtime@0.138.0': - resolution: {integrity: sha512-yHhoXsN8tYxgdJCdD91PbySNjEEaBX/tH2OQRDXJpsQv5b184oC4/qVbU7qlblvfil/JP15Lh2HW7+HN5DS90Q==} - engines: {node: ^20.19.0 || >=22.12.0} - - '@oxc-project/runtime@0.139.0': - resolution: {integrity: sha512-WnuGdceWtRdqD7f3alOIDXN6bnGuGtFjtQf/dHjzgn2im7eKaYRJTEl2T1kFEWPhBWCDk+UDYgsTLUE5L6jc0w==} - engines: {node: ^20.19.0 || >=22.12.0} - - '@oxc-project/types@0.138.0': - resolution: {integrity: sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==} - - '@oxc-project/types@0.139.0': - resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} - - '@oxfmt/binding-android-arm-eabi@0.57.0': - resolution: {integrity: sha512-qVBsEO+KugOsCmUHcO8iqNnqc65p7PCKpCs8M66mPZ+Ri+CWbcpoQOEJBg2OTu03+0qu++NK1jj6IzvQVs0Sig==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - - '@oxfmt/binding-android-arm-eabi@0.58.0': - resolution: {integrity: sha512-Uz62sHduGGPftXtILGyxdSW4PX82rUg+rfdNqhsgxe881g4rIoXlIqmZQ6HVKcF4f+F8qMhdD03Bx5u7gmeTdg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - - '@oxfmt/binding-android-arm64@0.57.0': - resolution: {integrity: sha512-mp6PibWbao3aizijcheOeHQaYEhcUAt8pwLniYbtLfHxL/psFF0BykAwCj+s3c6qIpa8yN8keZICWrqtZ70w8g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@oxfmt/binding-android-arm64@0.58.0': - resolution: {integrity: sha512-rD0lRaJp1b+9vw6X4A2dJWKukd6X8yxiicN4JxXcXayolmUypRZxk+lKR+fVOu5q/iYc0fh5fR4bgmfOfVlbaA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@oxfmt/binding-darwin-arm64@0.57.0': - resolution: {integrity: sha512-T+0stuCBqmUVY+aMIvrgXhzGhHO3sD5tNiiEcYqgSdPsnukskQqn2u5qOVD0sv1l7RLdFS5Z/f5Wi9Ktyjr3Eg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@oxfmt/binding-darwin-arm64@0.58.0': - resolution: {integrity: sha512-uzbPPk7O6M+w2K65vcQ1woga3wgP8zghjL1KOG5b6qJ8dvYHZJ1VShaslg2KOK6yQIwCQtcMCXqLBM6sqXUNTg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@oxfmt/binding-darwin-x64@0.57.0': - resolution: {integrity: sha512-O+3JbqWs/mCI2oi4xfhRO2IVPFJNDDEBV8Odo+ZpmsUOeKJfjXoNH7nDmBEQcDgK7NfjDIyE7kRgYSZcTLDO0A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@oxfmt/binding-darwin-x64@0.58.0': - resolution: {integrity: sha512-L0nKYDxU32oxeQqJj21W9SlIMnf81VZEhyah6iDvFhf5q0oynq498Fopth7blErUJVBpVtxQ98RMCfMPqpJX6w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@oxfmt/binding-freebsd-x64@0.57.0': - resolution: {integrity: sha512-pxwhxVC+JkLX9twOQ/8C/vbuOQcMZyKIDmiRDZfO7yITuVcIdZCiLRqqf4QOxb2+8FWrRXzQpm+1DBKcMpHSSQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@oxfmt/binding-freebsd-x64@0.58.0': - resolution: {integrity: sha512-woNwfD58dC5PGS9LSLSD5JYfo/EFK5iG9vhDWkcCg3q78ag7KC8bpDqgvPHrMoXpx83OLXxoSOhu6z8FsVTHlg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': - resolution: {integrity: sha512-pxBU4zH2imB/MDBfth2rOMeVxXUMjRQLCazagwLARIFH3hVlxZJBlM4nSnHXaIHJK4/qezoFCIORN6AY8Mra4A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxfmt/binding-linux-arm-gnueabihf@0.58.0': - resolution: {integrity: sha512-Sqs8nMLxuQpY21NKJ1u4stPDmO5hskBCNNh2E3AdCfI1QqWtf4m+Qn4mGEIUO4KGmuq3SWc/SZ80uy5IiwTCDw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxfmt/binding-linux-arm-musleabihf@0.57.0': - resolution: {integrity: sha512-JAprOzt8tycYou36ZgEw14DlRHTiN8qdtKANdV3VZIRIvTI/lh/cX13c9pJ/EnDk2GT3FASH7KvCgQ2AufAifQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxfmt/binding-linux-arm-musleabihf@0.58.0': - resolution: {integrity: sha512-Vd4exzBI5B5hB9m22JiTQzIL23WvHo/Pe+sNXPNeBLXSP9swCBPKCEBRwKpmpQzYhlgYaCgfPcGXPKAJBRIiZQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxfmt/binding-linux-arm64-gnu@0.57.0': - resolution: {integrity: sha512-ajtjaxSaj9xl4BW7REt+Cef/ttzbAq00Bq4z7JUDZEfgFXdwSjH8K9bF+IcIJzZB9lKqMfQ4eHuSFOvvlvtqOg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@oxfmt/binding-linux-arm64-gnu@0.58.0': - resolution: {integrity: sha512-bUWi5mHV+4Vi56RLHE1h6q/HHfwAIT3XoB9vJAVeRzfu5NriXM8y6eeJu0vlKa0C9kq2rq1sOWRClhdLHPocrg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@oxfmt/binding-linux-arm64-musl@0.57.0': - resolution: {integrity: sha512-p4Y/+RYk9Bk5WO+zHSUXAClRmZ2fbJCejMuCAsU2HhyME4jqf6Ftt/mJYEwIah1wGCBDYOB7wEGV1x5bCEZ6hA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@oxfmt/binding-linux-arm64-musl@0.58.0': - resolution: {integrity: sha512-2ZHxemzgHcjtktAuVUwSoyXmGo/t+aF5tS1ciPpPei4rhSyrz3JOqDosXXrmhN/yLUSzJjtuW7ToTWqfQpCj2w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@oxfmt/binding-linux-ppc64-gnu@0.57.0': - resolution: {integrity: sha512-By6tRALAZsno0F4zedmtG+wdMvJiJmJoXM4d3+A9zHE4HRXLqXITwRH8mgrlcXc5yJM2g2W3riRPwTYdgemZLQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@oxfmt/binding-linux-ppc64-gnu@0.58.0': - resolution: {integrity: sha512-AwKkVwjVmFQ3bcO7j0McGYAqCKH2a326fswfofng/E8VewCT/raeeGQr4huVhY704deK8AWASSTlxzMj0eZc6Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@oxfmt/binding-linux-riscv64-gnu@0.57.0': - resolution: {integrity: sha512-skYeG+RgvyzspqVEBsEprL90OYYZfoVNqB3HcCNR6QDJyXKOzfDRT3zncnHmUaFluIlBHuY23mU1b5WGgR98hA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@oxfmt/binding-linux-riscv64-gnu@0.58.0': - resolution: {integrity: sha512-xsRpTxfUnJF8D3AUKko/qyWdjw4GZVHlCVFuGlzSCTeewLmykKINW8em1+wx+axsDVtJJcMtvsiaXggXxrlHgw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@oxfmt/binding-linux-riscv64-musl@0.57.0': - resolution: {integrity: sha512-FFgACrZOXAXUh5KQh2mt1CDOVOZmn+QzHP71wM9QobNwyQvoFfyAeefVUltW83g3sm7LTiH3yfFqLLVUpA5ZFQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@oxfmt/binding-linux-riscv64-musl@0.58.0': - resolution: {integrity: sha512-Z4AYOTcy7nYEIiXwD62PlerimyYRcfJOgUbQAEBjXz098kxKuERBlRntofGy69HHhe9E0TLVNMl1yspVNu+efw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@oxfmt/binding-linux-s390x-gnu@0.57.0': - resolution: {integrity: sha512-Nm/BAOfQeFiiKd502mZn/GAVKJwtd0RdCg17G3Wz/WSOIQmDi3+7/SZH4BHn1Ye5KvTVH3ua8WvfwLLycNIuvA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@oxfmt/binding-linux-s390x-gnu@0.58.0': - resolution: {integrity: sha512-A3nhhtZPC/TKVWOPj9q/H3p2znJDCcHWYlJBhWL8hGq/bFmBaNBHC8Np6E581yVq1w9Mi3rMDNzDalWvtUfJtQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@oxfmt/binding-linux-x64-gnu@0.57.0': - resolution: {integrity: sha512-BiSy5Ku3mQqyxS6YIqAJgd403wEUWvI7kerfzPxc2l/txZVmZM0pSj7oDM+4bGBExowxOi7o73jEam1W0EDTZg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@oxfmt/binding-linux-x64-gnu@0.58.0': - resolution: {integrity: sha512-2g+tVkgwqphw8R4hgo+kF4oz8+P5RwVOtr9+irsC7uwEp0e9j7Crw8kDGKL20uYlLPD7g02DqA61mC/UNYx98A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@oxfmt/binding-linux-x64-musl@0.57.0': - resolution: {integrity: sha512-BCRkJiotz5s9afLYD2LuMvzAoDYx9H17E/YbDyu4xK7l4zHDPeny9ErSXL//i/nJyaOwRk08x4b8cgJC00+JDg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@oxfmt/binding-linux-x64-musl@0.58.0': - resolution: {integrity: sha512-rc15P6AbyyB7426aN8AakLd02Trb3a6ML/mmfAQeVHJEfVofWLcWIrBdy6zDEY+DIaL/s8E4GGPboVw+oP3+EA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@oxfmt/binding-openharmony-arm64@0.57.0': - resolution: {integrity: sha512-4Oaxe1qrGgXfpCJ1C/ERJ2iCtV2rN1R79ga9fsfyVHfSQRu/hVW780u2KDqZWFZ/iGTHODJji0JemxqFZ63eIQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@oxfmt/binding-openharmony-arm64@0.58.0': - resolution: {integrity: sha512-ZWoTM27/HYPOh9iq86DAbhPu9nXb8qKvvGU/h8OfliyVUFAMMNTLDkGsWDKKnDqIkqvZ9+dXlgUOsH1LYO3O7g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@oxfmt/binding-win32-arm64-msvc@0.57.0': - resolution: {integrity: sha512-MYLAsDnhdNsSGheLYhWgbk0vfIrlS84iQYun/y21fX6u0jj8iBtYtbpZMdiqYeuf8U12eVPUjVY2xE2NrCfJ0g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@oxfmt/binding-win32-arm64-msvc@0.58.0': - resolution: {integrity: sha512-LHZnqFXe2dEfkRI4XdZS/57nEOT/I4UCRX5IyM9v4GYW9XwQCjGe1IUK59SuKw3POwvcgWQ4pme2cYXmNqTNPg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@oxfmt/binding-win32-ia32-msvc@0.57.0': - resolution: {integrity: sha512-PBwdzZALJY/jcCx2E6is0yu+cuVXeySTDmwuseD+9j0mHqlRNxwlKgsyRTBed/woPeqfVfuXfWjoq4Cx2Zt3Eg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - - '@oxfmt/binding-win32-ia32-msvc@0.58.0': - resolution: {integrity: sha512-mZKpg20TpheCJym1rarcZCUJeW1sSruw8zAAaCYWvuVfwIUDN1CXdrPU/JgCWReXTCTrEfCB8Wyo3hh9jSZ2EA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - - '@oxfmt/binding-win32-x64-msvc@0.57.0': - resolution: {integrity: sha512-bQJdH9i4RRfw55jm7+8/xS7GzHLLTbHx4huhrrDxQJaJtbSDbsyOnODvP1ftT7EG0KFKAYO2S+q6AcioXODx8w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@oxfmt/binding-win32-x64-msvc@0.58.0': - resolution: {integrity: sha512-N/wUU4N5PZ2orBtI+Ko7MnMfYLfE7K91UrGMY/c/pYyHR3lA9kwst1XugkZx+92YcRh/Eo+iv2eTESSWXfiZPA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@oxlint-tsgolint/darwin-arm64@0.24.0': - resolution: {integrity: sha512-C2uMmwK5Bc4ri4ysZ6sA8Rcu+A5zBQTp6ml2u0CLLbRZp4kMFPV3yWk8B5DK9Aw7y9bbjogIm75tUwGLFzlsYQ==} - cpu: [arm64] - os: [darwin] - - '@oxlint-tsgolint/darwin-x64@0.24.0': - resolution: {integrity: sha512-Wgvt/1lRbDxmoNqWQKKcL+UIiqLmdJ+EWLpQa1qzoNVAfNB0PJpa82/8dH1twT/3rSs4zrP5TXPWl4juB71WuQ==} - cpu: [x64] - os: [darwin] - - '@oxlint-tsgolint/linux-arm64@0.24.0': - resolution: {integrity: sha512-PB1rxII7KV83+ASY4sSkXtqvpij6ME66+QCRL49uksi/ofs2Rf/UVboYr095n0Rkbl2wgvlsHGl6DHC361jQUQ==} - cpu: [arm64] - os: [linux] - - '@oxlint-tsgolint/linux-x64@0.24.0': - resolution: {integrity: sha512-xcz3CxKmjTQLREtE/UShh+ruWmm9nAb7UM9zKcD65BStiuYgOakAKkPHl4YS5DztpVcDrE0+HqbOolTlRKYWmw==} - cpu: [x64] - os: [linux] - - '@oxlint-tsgolint/win32-arm64@0.24.0': - resolution: {integrity: sha512-A2i6ZGBec3i20S7RaxkgHc6r3HYtD5Mn7j/mb22NkTz14u0JuudvTu6JggAnbGMcv8+dBKQI//EasxSPJLD8pw==} - cpu: [arm64] - os: [win32] - - '@oxlint-tsgolint/win32-x64@0.24.0': - resolution: {integrity: sha512-0ZbGd9qRB6zs82moekaKdEvncRANq49EAwfNX62JpTS46feXUhKAuoyVDvZMj6Rywejylrmmu79Wo6faYCo4Ew==} - cpu: [x64] - os: [win32] - - '@oxlint/binding-android-arm-eabi@1.73.0': - resolution: {integrity: sha512-HZQRN/UMBu+Ut+/9MiAChkbP4qZqrNOWBcNI45vOT40GVhbGR0JgHB87L48D4iAqFQIdVmeQYtV9RF89AjTKkg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - - '@oxlint/binding-android-arm64@1.73.0': - resolution: {integrity: sha512-Gp+KJRylv2aW7thRpG5p1KTxZq4ZJFbWowrKzufNq9d3ssl3r3JviYV45/+p+7CN1Nv0zDd1e8Ex0b/HUDq4TQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@oxlint/binding-darwin-arm64@1.73.0': - resolution: {integrity: sha512-3de96NdtXhxERMjIz7wsp2HYMY6pMQycGxFWac2mFecAx6VeARF/IqFb1QIaqiCRIdfzBwzTed+pCTCoiS+CYA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@oxlint/binding-darwin-x64@1.73.0': - resolution: {integrity: sha512-5zx/uPW32TiaOeVY1dQ/H5iOf0K1HOdFKOJhLqGl4o63+i1fpzoqqu/mKtd7OFgFjNCdhlyTGgjVkQTZm1ELcg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@oxlint/binding-freebsd-x64@1.73.0': - resolution: {integrity: sha512-qNe4gKHaGnLuZJ8toUg90JAa0S2vTVvDw+0bRi3q1avXZXDT4u5mMeECf3nD4HYrbdn1O7dXqWut4onY/yx/Xg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@oxlint/binding-linux-arm-gnueabihf@1.73.0': - resolution: {integrity: sha512-cCehYh5hTbfShm/fxTD6wwrGUWIpvX+N5OxmAMhFhDeTGXvw+BeNj889tpxsFQ9ZLatQ6wImuY8tsKLZ+FMz7w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxlint/binding-linux-arm-musleabihf@1.73.0': - resolution: {integrity: sha512-d5j5GDU/2dMgjVhw7TQT9ITrsIr1Y02KEXKyVGIXUkD+KiaxE9TP65FS2ZdgTBemQvoRL+gSBdbrIm3cQIeacg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxlint/binding-linux-arm64-gnu@1.73.0': - resolution: {integrity: sha512-Eyf1SrP3+yR1DI3OJgOY2Pvrr9dWP9TK37xPaDYycwTtlGlI45erJAVIfH5/m/xosDt6BupJYEFi47bvbTuuyw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@oxlint/binding-linux-arm64-musl@1.73.0': - resolution: {integrity: sha512-IlT/OJApEDKaMmCooHuncgJZbbCe7T5QIWmTZBEtYscWvzPQuuEinVcid6kwQRVQOUdb7PUCz4jQHnaYXdfJXw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@oxlint/binding-linux-ppc64-gnu@1.73.0': - resolution: {integrity: sha512-L+JYcb/vdg5fmcH08V6o0YYLU28cTH1SPNulwJdvK9NK49aXSkYy6oNpKBmddArVOXYqNepriDGiZ04G54kh1Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@oxlint/binding-linux-riscv64-gnu@1.73.0': - resolution: {integrity: sha512-Qtk0g3bKV6OwWjIm7R8kQN1uOZRKQt/MODK2a8QfkwhTpXBD53ozx5XLVWLGDQAVyp2otLW4D2wB98XfAfMPGA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@oxlint/binding-linux-riscv64-musl@1.73.0': - resolution: {integrity: sha512-wX0NQKZVxltkAOVmzFcpOaMpdaUvsq1Eqpx9tkAfl71UdkTlSo1R4AdAnGccR1Fm2+TzFgZ22CyyGuZ41RDr/A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@oxlint/binding-linux-s390x-gnu@1.73.0': - resolution: {integrity: sha512-vPe7UGBMWyiLTtnqS4xxgMQFSFGmtQwhwCxuiw6lXygaO6bVt0D8dFVg8Xv05eaiN3ybC0HXXHUAohFMFvqoCQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@oxlint/binding-linux-x64-gnu@1.73.0': - resolution: {integrity: sha512-2CwIWr9cemFC/CbRBWZvuk5mffz6ObmfFkfcC/9rTQ7f+icNhYr2kOjf9Rt8lLvugvkdGDOmkoVoFFHh6ClCTw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@oxlint/binding-linux-x64-musl@1.73.0': - resolution: {integrity: sha512-nDadfJgg7NBBxG0N560wOe7LLX5QiYp6qBaI7viuk5EUORFBktU/NfV0MbTqU3gTqQDCh4VyxKdo5VADxk9w8Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@oxlint/binding-openharmony-arm64@1.73.0': - resolution: {integrity: sha512-wGjJC+NLH9xP+IKGn9RDW94ojJR/wPbg5WCnQjj/oReaOtCQthr8ws1zICe77JFmo4ouUdeTHHZL/ESGiF6Pmw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@oxlint/binding-win32-arm64-msvc@1.73.0': - resolution: {integrity: sha512-I7X47GPGljw225YUQ5SbC/rb1Kkdrd0yQf0x+hYxeKS6DpfjMbo9ccQPQ6LNY6BoJQ1sHhgDUGuMn5Vg5gHT6w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@oxlint/binding-win32-ia32-msvc@1.73.0': - resolution: {integrity: sha512-5lWj+3h+74Fm1jYOO9qkJA4xkAlZA099DkXppuXsk7UpnpZLttsefrZU469vChGaG6hcSqrkKXQOvMTZtbjeNg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - - '@oxlint/binding-win32-x64-msvc@1.73.0': - resolution: {integrity: sha512-WaNRvh4f6zY9CvUQk2YoA1O90ieWrIklI84+HXFr9Isjz9CSESrdqo/RtIYt4Dll/cAchqGDMehfaZd0vqEFZw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@oxlint/plugins@1.73.0': - resolution: {integrity: sha512-OhgMQeMmZA0dcFcX4/priaJZWdFECxiClgq6mRX6aatZEcV9PbKC3P3/v8U1hVjviT1i5U+vR8lAtBV6m4FXAA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@playwright/browser-chromium@1.61.1': - resolution: {integrity: sha512-t3/zE0i9gik5R/NpRs7G2Xo/6NPeABW6ReplGdtkeWeAkaV764CgFgoKjJo21D2xgjnvDvRYubqBUu4xl0VCqA==} - engines: {node: '>=18'} - - '@polka/url@1.0.0-next.29': - resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - - '@rolldown/binding-android-arm64@1.1.5': - resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.1.5': - resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.1.5': - resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.1.5': - resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': - resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.1.5': - resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.1.5': - resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.1.5': - resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.1.5': - resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.1.5': - resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.1.5': - resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.1.5': - resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.1.5': - resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.1.5': - resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.1.5': - resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@testing-library/dom@10.4.1': - resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} - engines: {node: '>=18'} - - '@testing-library/user-event@14.6.1': - resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} - engines: {node: '>=12', npm: '>=6'} - peerDependencies: - '@testing-library/dom': '>=7.21.4' - - '@tsconfig/strictest@2.0.8': - resolution: {integrity: sha512-XnQ7vNz5HRN0r88GYf1J9JJjqtZPiHt2woGJOo2dYqyHGGcd6OLGqSlBB6p1j9mpzja6Oe5BoPqWmeDx6X9rLw==} - - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - - '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - - '@types/node@25.0.3': - resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} - - '@vitest/browser-playwright@4.1.10': - resolution: {integrity: sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==} - peerDependencies: - playwright: 1.61.1 - vitest: 4.1.10 - - '@vitest/browser-preview@4.1.10': - resolution: {integrity: sha512-14MJrL59ZFkqXLjwfSk6RzTDy5Czf9UG4+8q8L6Gxjs2aPjEce/cVNYV14bXAc2BvMjUNu904+ZEZA1Xc1wtvQ==} - peerDependencies: - vitest: 4.1.10 - - '@vitest/browser@4.1.10': - resolution: {integrity: sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==} - peerDependencies: - vitest: 4.1.10 - - '@vitest/expect@4.1.10': - resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - - '@vitest/mocker@4.1.10': - resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@4.1.10': - resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - - '@vitest/runner@4.1.10': - resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - - '@vitest/snapshot@4.1.10': - resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - - '@vitest/spy@4.1.10': - resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - - '@vitest/utils@4.1.10': - resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} - - '@voidzero-dev/vite-plus-core@0.2.4': - resolution: {integrity: sha512-AoAYGPwNO56o9TuCR+KaQGA5XpSnTpn2QYHK0DQ0f8j3wvaMmToeWOJF6STo+XMntMDfiaB7sOsSFNE+hPYyjg==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} - peerDependencies: - '@arethetypeswrong/core': ^0.18.1 - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.3.0 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - publint: ^0.3.8 - 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 - typescript: ^5.0.0 || ^6.0.0 - unplugin-unused: ^0.5.0 - unrun: '*' - yaml: ^2.4.2 - peerDependenciesMeta: - '@arethetypeswrong/core': - optional: true - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - publint: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - typescript: - optional: true - unplugin-unused: - optional: true - unrun: - optional: true - yaml: - optional: true - - '@voidzero-dev/vite-plus-core@0.2.5': - resolution: {integrity: sha512-fxMGImIOyOipwCX6udOD1S9Q1xXfaimv6kcRgLWBxLsy7oryAyXqVfoYr7bmmAdSDlIutHRgvA6eiqfJjARTHA==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} - peerDependencies: - '@arethetypeswrong/core': ^0.18.1 - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.3.0 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - publint: ^0.3.8 - 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 - typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 - unplugin-unused: ^0.5.0 - unrun: '*' - yaml: ^2.4.2 - peerDependenciesMeta: - '@arethetypeswrong/core': - optional: true - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - publint: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - typescript: - optional: true - unplugin-unused: - optional: true - unrun: - optional: true - yaml: - optional: true - - '@voidzero-dev/vite-plus-darwin-arm64@0.2.5': - resolution: {integrity: sha512-M62R3gmoHZbhL+UHHTevJi9a3aJyY+Eid8GAOtxEsRMkHmJ8IwOSOBERXM3C4CULvEa/ORYKiUQnqo5ewF44Fw==} - engines: {node: '>=20.0.0'} - cpu: [arm64] - os: [darwin] - - '@voidzero-dev/vite-plus-darwin-x64@0.2.5': - resolution: {integrity: sha512-a1h1dv/7QcnlqlN6yZBIPjgHSxbeyY/IcTxepTAOpPB7eAi1RPb1+cCkwo7c3MnDPJb3iZzwD0rm2+fOUoZp0w==} - engines: {node: '>=20.0.0'} - cpu: [x64] - os: [darwin] - - '@voidzero-dev/vite-plus-linux-arm64-gnu@0.2.5': - resolution: {integrity: sha512-t8bS8fA2a3OSAEaHdgJFmzd0TkWh9yAxIoKAprsOleIcUEmzDxhH8drTj9TPyTrChKpv0aJTsK5ZK3RzcCUkdg==} - engines: {node: '>=20.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@voidzero-dev/vite-plus-linux-arm64-musl@0.2.5': - resolution: {integrity: sha512-vnsjQI3LEUYFMR3LCMqtAxaZav8BNypSAf8YzcFu9+Qtd1dcCrAUz9RrEBCIIqEiw0p0O+SrX2CcMHSSnzWbKA==} - engines: {node: '>=20.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@voidzero-dev/vite-plus-linux-x64-gnu@0.2.5': - resolution: {integrity: sha512-3xlXrxIz8UKGcGefifkhoMpsTIMdgqikwQuDUqgG5O7/b2tetpK9aoT4C9b2fQGkhYUpCJwdD83AtywQ4EhoWA==} - engines: {node: '>=20.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@voidzero-dev/vite-plus-linux-x64-musl@0.2.5': - resolution: {integrity: sha512-XylGiayBoD7vt1/SKfmh5FoBNdKI5EWlIb5Sd9A1oTQW8DLi97VcEgVZ7vh/8kG1OEe+9z1lyRis6RDql2KDUw==} - engines: {node: '>=20.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@voidzero-dev/vite-plus-win32-arm64-msvc@0.2.5': - resolution: {integrity: sha512-eN1zvUAqXVXOC72WOVu9gz/jxr9tBrga/5lCsDjHeZDJ/bzhDVJ4eYZUDZzasPE1QCbAhmuIJsv0WzEUxdGAzA==} - engines: {node: '>=20.0.0'} - cpu: [arm64] - os: [win32] - - '@voidzero-dev/vite-plus-win32-x64-msvc@0.2.5': - resolution: {integrity: sha512-jgXVYK8crlR5cQ07vX5Qw2K+boNLKMxarPgE3/AyPPRUtGC8iCpZz0RCPoJHDmTh7848Ra5Fnt4LEfbcUv1ByQ==} - engines: {node: '>=20.0.0'} - cpu: [x64] - os: [win32] - - '@yuku-codegen/binding-darwin-arm64@0.5.48': - resolution: {integrity: sha512-yo96Oef12WzqnphInfz/eexVse3+kWgfGS5g2S3rFS3dcGn1ENW9xLFDZUP9rh+yP76DOq38wBoFi1+I9+6qBg==} - cpu: [arm64] - os: [darwin] - - '@yuku-codegen/binding-darwin-x64@0.5.48': - resolution: {integrity: sha512-aRCTw0EZC4bVosmw//0OMYP5tGWFE0Cu5yUBFkUbhXx/iBzvORcJ2xPNlOp/vtCCo9Ys4vp8b0DigJV6uOVb2g==} - cpu: [x64] - os: [darwin] - - '@yuku-codegen/binding-freebsd-x64@0.5.48': - resolution: {integrity: sha512-CA0AQAEApDkbw51PdLWMtKPJ41/7rvXsS3SJs+phG7fHJI+MuFzWuLbkucZfZoEOiDscmcsfYIdgL8BsfuyKKQ==} - cpu: [x64] - os: [freebsd] - - '@yuku-codegen/binding-linux-arm-gnu@0.5.48': - resolution: {integrity: sha512-DuSQlk8bH4gpmW3/00P0NLagAcMv8jOxjT40cQmxKRkktr+SUOALCfkT89tdDq3qtY95NR2GXOZ7AjNh7KKqCw==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@yuku-codegen/binding-linux-arm-musl@0.5.48': - resolution: {integrity: sha512-bxj4Ee+wlaJcWJwft2ReJXWw5sfl1qavDz6+dlRdU1xfTEtjPSNiAWhiCHnJR0R4Ygd57DnzSQmAVGvFv6RcGw==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@yuku-codegen/binding-linux-arm64-gnu@0.5.48': - resolution: {integrity: sha512-mk5JVWh+0JOe5ue8k17kbYX8uGBoKt3ZqoCyxNh4nYAAcX7+X1tFUiU7jbjctu4vHeejCBFSTdQ021+V31cUCQ==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@yuku-codegen/binding-linux-arm64-musl@0.5.48': - resolution: {integrity: sha512-4q3vkrNghbllyxOm2KesFLxCPKHF7r3JyQ7BWZccY1j2Y05yKoIFhoWCqIuQ2W/dpte9RI0+OVfwyxnrKg6fkA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@yuku-codegen/binding-linux-x64-gnu@0.5.48': - resolution: {integrity: sha512-csd4M1EVrGaohM8acM6gq1zpUA/Rwe2ulUMBKUcwQXm/k6n7cq1A++qdew78SOVb4do3JH1WE+WFwoGQAcWc1w==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@yuku-codegen/binding-linux-x64-musl@0.5.48': - resolution: {integrity: sha512-KcDuEOT+GFoVKdvAWOv1v9iYjwnmvMZlO+j1Rw+5PYdeFLGWGzv/DD11y4SAAdwXIFcil4T0hibeIaF82WStMg==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@yuku-codegen/binding-win32-arm64@0.5.48': - resolution: {integrity: sha512-HI8qNrI8dWM5BuqIMKsqornRvTNFrE6sm5zToIJ9YIa9zt5+29P7fJ7Nr39EVf6dAWSb6q7JSpScJnRsQ+FgZA==} - cpu: [arm64] - os: [win32] - - '@yuku-codegen/binding-win32-x64@0.5.48': - resolution: {integrity: sha512-X5YWJLO6EfBZpeBqO0AYESnUizbpFDWArcvVD61w0PEWQ3CaFRLnbQXs+kpM4ZZfGMfIE22zfA08QSY67q7TNQ==} - cpu: [x64] - os: [win32] - - '@yuku-parser/binding-darwin-arm64@0.5.48': - resolution: {integrity: sha512-If8mb7HH3vqghJ2NNZ8SuHfhsnjVzOxJpB8xcNOXS5WjYrs2mUhHIh5KOIvK13hDOzh0htGeGK3A6MsiEqE7HQ==} - cpu: [arm64] - os: [darwin] - - '@yuku-parser/binding-darwin-x64@0.5.48': - resolution: {integrity: sha512-EimvPXfspzxf1K11eB6tCW5oiQEXB8g84T2wP1TwzQagdDKo33bkmmVF0B32vTIpXnk/Ifu5IB61izZ1MylljA==} - cpu: [x64] - os: [darwin] - - '@yuku-parser/binding-freebsd-x64@0.5.48': - resolution: {integrity: sha512-0GcUMrumLHheThY9r5Tp46gaZYzn0irWPS1Zba6WY+vVQfhUtzGiWgXxI6tuXX0N32kEaaEVRpkKctvo6Kx3aQ==} - cpu: [x64] - os: [freebsd] - - '@yuku-parser/binding-linux-arm-gnu@0.5.48': - resolution: {integrity: sha512-8S5T5wjCC73dmmpQeZ49aYsSunIUM3D4Fc6rdK96c+Ayg/p3FmeSPF3xuLZHejcTmqJIIvnbfPlUF+rB6DITjQ==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-arm-musl@0.5.48': - resolution: {integrity: sha512-tTmbxvnUHcK2/crS9547vk2SMmsajH1yqJ8ltXhIuHJgqR1v+d9n9KT+kSayo/5CS76LegeYxhMFjEivBH2hFA==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-linux-arm64-gnu@0.5.48': - resolution: {integrity: sha512-KGYCBMqI2zfwyhgq5tpPVNe7jpUeYTBm8DhjdS+zqWNumde/PEC170QE5RHxcOAlsirIDeIUk0jqx+r/axoFSw==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-arm64-musl@0.5.48': - resolution: {integrity: sha512-2wTSMsCSXLTc2lZUjMAuU5X4cje55u205WJqfV5NWNF6j9pW/tXyxr15dJeekj8ziLqBXzIsj4DbRh4sY/WcjA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-linux-x64-gnu@0.5.48': - resolution: {integrity: sha512-d/6v9UnGglVu1WC2JQyv/5aWSi5fXZeGSlidCfmHp4+N65N1GDKUnFtys5MK5eAPeAjTgSHGGtOc/yCcKTlv3A==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-x64-musl@0.5.48': - resolution: {integrity: sha512-gX19gw6u4ApPy7SYMPKfFlEkrtj6WlORvrTKK3sBQqjyV+8+mUAkQgxXNjHw4RnOiAmVYg7TOlZcg8d+Qqod9A==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-win32-arm64@0.5.48': - resolution: {integrity: sha512-w6cQQLbqj3Jcom5Q7ifm103NUOQ9d+Cb4VU5lkrZDjMnwVJ9Hzzg1vCQR7miJuF44vhCXldbme5UryE3giEKlA==} - cpu: [arm64] - os: [win32] - - '@yuku-parser/binding-win32-x64@0.5.48': - resolution: {integrity: sha512-4gO0HmG7fzFxrw1rs0dUdnnaY9YgennjETqDWrTSp7x9fmTUOAoN4VsMfP7YyliQeG1WJJHc55O+rOhmsLppow==} - cpu: [x64] - os: [win32] - - '@yuku-toolchain/types@0.5.43': - resolution: {integrity: sha512-kSpvPntnXw5+lYjO71ffBEnQ5ycQ74KGIYknh0TS4xeyCuBkOqxyJumxZkMhLBBUCLjDAbx2+Icnr3Zh4ftjpQ==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - bun@1.3.14: - resolution: {integrity: sha512-aB6GVd42x1Y5ie1K16SF+oLGtgSkwX9hgoDdIW88pjvfTccU8F1vfpoOt34QLv0dZ1v3XimtaxPlZUG81Gx9Zg==} - cpu: [arm64, x64] - os: [darwin, linux, android, freebsd, win32] - hasBin: true - - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cross-env@10.1.0: - resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} - engines: {node: '>=20'} - hasBin: true - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - deno@2.9.3: - resolution: {integrity: sha512-OuR2ZfUqnsXFcKHQIfyG2GDkZdsjFxMIlkWOiozbo3IpMUv1Meja45PmOiMtgzRedc627C54I1arWxn/FarEHg==} - hasBin: true - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - - oxfmt@0.57.0: - resolution: {integrity: sha512-ZB7Bi+rGDSqmVIo9jwcLyFgjxXvQhDdU+jx+ZrVy6VRiVXK2+CHc4hO3J4dUQjHe7V0ymHB+MDuv5z+NhK07HA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - svelte: ^5.0.0 - vite-plus: '*' - peerDependenciesMeta: - svelte: - optional: true - vite-plus: - optional: true - - oxfmt@0.58.0: - resolution: {integrity: sha512-8feG/7NVEHDVwc1OUpP6Pks+TnaDFUw2jLLFIMi5bcmmwxAX2wBQvjSzj62RRTYBf2Op1Wt8xbkmagmPTR5ETg==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - svelte: ^5.0.0 - vite-plus: '*' - peerDependenciesMeta: - svelte: - optional: true - vite-plus: - optional: true - - oxlint-tsgolint@0.24.0: - resolution: {integrity: sha512-giCk5sEvG02d5tzPmFMX3hem8ndzEEu1xvGYS5OwNfO2WGl6ZVxt5LjE0yiMDoz94INI7XkXwgFAQiydPvVHDw==} - hasBin: true - - oxlint@1.73.0: - resolution: {integrity: sha512-u91G9TJzU6yqKWNZUYprQB07W7YvntZXaRxQ6CkoytepYhLWUXWsr1M8zUJ34VatNPuUAr3Z8GH+O2A331CluQ==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - oxlint-tsgolint: '>=0.24.0' - vite-plus: '*' - peerDependenciesMeta: - oxlint-tsgolint: - optional: true - vite-plus: - optional: true - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} - engines: {node: '>=12'} - - playwright-core@1.61.1: - resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} - engines: {node: '>=18'} - hasBin: true - - playwright@1.61.1: - resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} - engines: {node: '>=18'} - hasBin: true - - pngjs@7.0.0: - resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} - engines: {node: '>=14.19.0'} - - postcss@8.5.19: - resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} - engines: {node: ^10 || ^12 || >=14} - - pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - - rolldown@1.1.5: - resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - sirv@3.0.2: - resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} - engines: {node: '>=18'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@1.1.2: - resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} - engines: {node: '>=18'} - - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} - - tinypool@2.1.0: - resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} - engines: {node: ^20.0.0 || >=22.0.0} - - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} - engines: {node: '>=14.0.0'} - - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - - vite-plus@0.2.5: - resolution: {integrity: sha512-QNJ0FnN8rfs5u8lZKZ1uR2Tegjg3VkT0AGTxSLGHg4fYmGxRNfAV0YX1parZrVa4VybSI56SWCoo7wQ6D7pMew==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} - hasBin: true - peerDependencies: - '@vitest/browser-playwright': 4.1.10 - '@vitest/browser-webdriverio': 4.1.10 - peerDependenciesMeta: - '@vitest/browser-playwright': - optional: true - '@vitest/browser-webdriverio': - optional: true - - vite@8.1.5: - resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.3.0 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - 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 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@4.1.10: - resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.10 - '@vitest/browser-preview': 4.1.10 - '@vitest/browser-webdriverio': 4.1.10 - '@vitest/coverage-istanbul': 4.1.10 - '@vitest/coverage-v8': 4.1.10 - '@vitest/ui': 4.1.10 - happy-dom: '*' - jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - yuku-codegen@0.5.48: - resolution: {integrity: sha512-p7HxD5Xl4jzDzqMrGePAOeSHmRY4g58h4HuGq15weQFPxuPWd/W6e7nqp/+Lea6JfpOdBwJOAyXFqIZ/J9Zfnw==} - - yuku-parser@0.5.48: - resolution: {integrity: sha512-OWBfhrpgK9+/4+IXG9oT8Bao4AhViQA7vdyNNH7EUg8dQYgwa70XtIBWTpCEme1P1ECyoDNYkn0wT63f8XRcVA==} - -snapshots: - - '@babel/code-frame@7.29.7': - dependencies: - '@babel/helper-validator-identifier': 7.29.7 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/helper-validator-identifier@7.29.7': {} - - '@babel/runtime@7.29.7': {} - - '@blazediff/core@1.9.1': {} - - '@deno/darwin-arm64@2.9.3': - optional: true - - '@deno/darwin-x64@2.9.3': - optional: true - - '@deno/linux-arm64-glibc@2.9.3': - optional: true - - '@deno/linux-x64-glibc@2.9.3': - optional: true - - '@deno/win32-arm64@2.9.3': - optional: true - - '@deno/win32-x64@2.9.3': - optional: true - - '@emnapi/core@1.11.1': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.11.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@epic-web/invariant@1.0.0': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@tybys/wasm-util': 0.10.3 - optional: true - - '@oven/bun-darwin-aarch64@1.3.14': - optional: true - - '@oven/bun-darwin-x64-baseline@1.3.14': - optional: true - - '@oven/bun-darwin-x64@1.3.14': - optional: true - - '@oven/bun-freebsd-aarch64@1.3.14': - optional: true - - '@oven/bun-freebsd-x64@1.3.14': - optional: true - - '@oven/bun-linux-aarch64-android@1.3.14': - optional: true - - '@oven/bun-linux-aarch64-musl@1.3.14': - optional: true - - '@oven/bun-linux-aarch64@1.3.14': - optional: true - - '@oven/bun-linux-x64-android@1.3.14': - optional: true - - '@oven/bun-linux-x64-baseline@1.3.14': - optional: true - - '@oven/bun-linux-x64-musl-baseline@1.3.14': - optional: true - - '@oven/bun-linux-x64-musl@1.3.14': - optional: true - - '@oven/bun-linux-x64@1.3.14': - optional: true - - '@oven/bun-windows-aarch64@1.3.14': - optional: true - - '@oven/bun-windows-x64-baseline@1.3.14': - optional: true - - '@oven/bun-windows-x64@1.3.14': - optional: true - - '@oxc-project/runtime@0.138.0': {} - - '@oxc-project/runtime@0.139.0': {} - - '@oxc-project/types@0.138.0': {} - - '@oxc-project/types@0.139.0': {} - - '@oxfmt/binding-android-arm-eabi@0.57.0': - optional: true - - '@oxfmt/binding-android-arm-eabi@0.58.0': - optional: true - - '@oxfmt/binding-android-arm64@0.57.0': - optional: true - - '@oxfmt/binding-android-arm64@0.58.0': - optional: true - - '@oxfmt/binding-darwin-arm64@0.57.0': - optional: true - - '@oxfmt/binding-darwin-arm64@0.58.0': - optional: true - - '@oxfmt/binding-darwin-x64@0.57.0': - optional: true - - '@oxfmt/binding-darwin-x64@0.58.0': - optional: true - - '@oxfmt/binding-freebsd-x64@0.57.0': - optional: true - - '@oxfmt/binding-freebsd-x64@0.58.0': - optional: true - - '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': - optional: true - - '@oxfmt/binding-linux-arm-gnueabihf@0.58.0': - optional: true - - '@oxfmt/binding-linux-arm-musleabihf@0.57.0': - optional: true - - '@oxfmt/binding-linux-arm-musleabihf@0.58.0': - optional: true - - '@oxfmt/binding-linux-arm64-gnu@0.57.0': - optional: true - - '@oxfmt/binding-linux-arm64-gnu@0.58.0': - optional: true - - '@oxfmt/binding-linux-arm64-musl@0.57.0': - optional: true - - '@oxfmt/binding-linux-arm64-musl@0.58.0': - optional: true - - '@oxfmt/binding-linux-ppc64-gnu@0.57.0': - optional: true - - '@oxfmt/binding-linux-ppc64-gnu@0.58.0': - optional: true - - '@oxfmt/binding-linux-riscv64-gnu@0.57.0': - optional: true - - '@oxfmt/binding-linux-riscv64-gnu@0.58.0': - optional: true - - '@oxfmt/binding-linux-riscv64-musl@0.57.0': - optional: true - - '@oxfmt/binding-linux-riscv64-musl@0.58.0': - optional: true - - '@oxfmt/binding-linux-s390x-gnu@0.57.0': - optional: true - - '@oxfmt/binding-linux-s390x-gnu@0.58.0': - optional: true - - '@oxfmt/binding-linux-x64-gnu@0.57.0': - optional: true - - '@oxfmt/binding-linux-x64-gnu@0.58.0': - optional: true - - '@oxfmt/binding-linux-x64-musl@0.57.0': - optional: true - - '@oxfmt/binding-linux-x64-musl@0.58.0': - optional: true - - '@oxfmt/binding-openharmony-arm64@0.57.0': - optional: true - - '@oxfmt/binding-openharmony-arm64@0.58.0': - optional: true - - '@oxfmt/binding-win32-arm64-msvc@0.57.0': - optional: true - - '@oxfmt/binding-win32-arm64-msvc@0.58.0': - optional: true - - '@oxfmt/binding-win32-ia32-msvc@0.57.0': - optional: true - - '@oxfmt/binding-win32-ia32-msvc@0.58.0': - optional: true - - '@oxfmt/binding-win32-x64-msvc@0.57.0': - optional: true - - '@oxfmt/binding-win32-x64-msvc@0.58.0': - optional: true - - '@oxlint-tsgolint/darwin-arm64@0.24.0': - optional: true - - '@oxlint-tsgolint/darwin-x64@0.24.0': - optional: true - - '@oxlint-tsgolint/linux-arm64@0.24.0': - optional: true - - '@oxlint-tsgolint/linux-x64@0.24.0': - optional: true - - '@oxlint-tsgolint/win32-arm64@0.24.0': - optional: true - - '@oxlint-tsgolint/win32-x64@0.24.0': - optional: true - - '@oxlint/binding-android-arm-eabi@1.73.0': - optional: true - - '@oxlint/binding-android-arm64@1.73.0': - optional: true - - '@oxlint/binding-darwin-arm64@1.73.0': - optional: true - - '@oxlint/binding-darwin-x64@1.73.0': - optional: true - - '@oxlint/binding-freebsd-x64@1.73.0': - optional: true - - '@oxlint/binding-linux-arm-gnueabihf@1.73.0': - optional: true - - '@oxlint/binding-linux-arm-musleabihf@1.73.0': - optional: true - - '@oxlint/binding-linux-arm64-gnu@1.73.0': - optional: true - - '@oxlint/binding-linux-arm64-musl@1.73.0': - optional: true - - '@oxlint/binding-linux-ppc64-gnu@1.73.0': - optional: true - - '@oxlint/binding-linux-riscv64-gnu@1.73.0': - optional: true - - '@oxlint/binding-linux-riscv64-musl@1.73.0': - optional: true - - '@oxlint/binding-linux-s390x-gnu@1.73.0': - optional: true - - '@oxlint/binding-linux-x64-gnu@1.73.0': - optional: true - - '@oxlint/binding-linux-x64-musl@1.73.0': - optional: true - - '@oxlint/binding-openharmony-arm64@1.73.0': - optional: true - - '@oxlint/binding-win32-arm64-msvc@1.73.0': - optional: true - - '@oxlint/binding-win32-ia32-msvc@1.73.0': - optional: true - - '@oxlint/binding-win32-x64-msvc@1.73.0': - optional: true - - '@oxlint/plugins@1.73.0': {} - - '@playwright/browser-chromium@1.61.1': - dependencies: - playwright-core: 1.61.1 - - '@polka/url@1.0.0-next.29': {} - - '@rolldown/binding-android-arm64@1.1.5': - optional: true - - '@rolldown/binding-darwin-arm64@1.1.5': - optional: true - - '@rolldown/binding-darwin-x64@1.1.5': - optional: true - - '@rolldown/binding-freebsd-x64@1.1.5': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.1.5': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.1.5': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.1.5': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.1.5': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.1.5': - optional: true - - '@rolldown/binding-linux-x64-musl@1.1.5': - optional: true - - '@rolldown/binding-openharmony-arm64@1.1.5': - optional: true - - '@rolldown/binding-wasm32-wasi@1.1.5': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.1.5': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.1.5': - optional: true - - '@rolldown/pluginutils@1.0.1': {} - - '@standard-schema/spec@1.1.0': {} - - '@testing-library/dom@10.4.1': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/runtime': 7.29.7 - '@types/aria-query': 5.0.4 - aria-query: 5.3.0 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - picocolors: 1.1.1 - pretty-format: 27.5.1 - - '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': - dependencies: - '@testing-library/dom': 10.4.1 - - '@tsconfig/strictest@2.0.8': {} - - '@tybys/wasm-util@0.10.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/aria-query@5.0.4': {} - - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.9': {} - - '@types/node@25.0.3': - dependencies: - undici-types: 7.16.0 - - '@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10)': - dependencies: - '@vitest/browser': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(vitest@4.1.10) - '@vitest/mocker': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)) - playwright: 1.61.1 - tinyrainbow: 3.1.0 - vitest: 4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@vitest/browser-preview@4.1.10)(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)) - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - optional: true - - '@vitest/browser-playwright@4.1.10(playwright@1.61.1)(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10)': - dependencies: - '@vitest/browser': 4.1.10(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10) - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@25.0.3)) - playwright: 1.61.1 - tinyrainbow: 3.1.0 - vitest: 4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(vite@8.1.5(@types/node@25.0.3)) - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - - '@vitest/browser-preview@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(vitest@4.1.10)': - dependencies: - '@testing-library/dom': 10.4.1 - '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/browser': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(vitest@4.1.10) - vitest: 4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@vitest/browser-preview@4.1.10)(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)) - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - - '@vitest/browser-preview@4.1.10(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10)': - dependencies: - '@testing-library/dom': 10.4.1 - '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/browser': 4.1.10(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10) - vitest: 4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(vite@8.1.5(@types/node@25.0.3)) - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - optional: true - - '@vitest/browser@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(vitest@4.1.10)': - dependencies: - '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)) - '@vitest/utils': 4.1.10 - magic-string: 0.30.21 - pngjs: 7.0.0 - sirv: 3.0.2 - tinyrainbow: 3.1.0 - vitest: 4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@vitest/browser-preview@4.1.10)(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)) - ws: 8.21.0 - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - - '@vitest/browser@4.1.10(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10)': - dependencies: - '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@25.0.3)) - '@vitest/utils': 4.1.10 - magic-string: 0.30.21 - pngjs: 7.0.0 - sirv: 3.0.2 - tinyrainbow: 3.1.0 - vitest: 4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(vite@8.1.5(@types/node@25.0.3)) - ws: 8.21.0 - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - - '@vitest/expect@4.1.10': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - chai: 6.2.2 - tinyrainbow: 3.1.0 - - '@vitest/mocker@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))': - dependencies: - '@vitest/spy': 4.1.10 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)' - - '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@25.0.3))': - dependencies: - '@vitest/spy': 4.1.10 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.1.5(@types/node@25.0.3) - - '@vitest/pretty-format@4.1.10': - dependencies: - tinyrainbow: 3.1.0 - - '@vitest/runner@4.1.10': - dependencies: - '@vitest/utils': 4.1.10 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.10': - dependencies: - '@vitest/pretty-format': 4.1.10 - '@vitest/utils': 4.1.10 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.10': {} - - '@vitest/utils@4.1.10': - dependencies: - '@vitest/pretty-format': 4.1.10 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - - '@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)': - dependencies: - '@oxc-project/runtime': 0.138.0 - '@oxc-project/types': 0.138.0 - lightningcss: 1.32.0 - postcss: 8.5.19 - optionalDependencies: - '@types/node': 25.0.3 - fsevents: 2.3.3 - typescript: 6.0.3 - - '@voidzero-dev/vite-plus-core@0.2.5(@types/node@25.0.3)(typescript@6.0.3)': - dependencies: - '@oxc-project/runtime': 0.139.0 - '@oxc-project/types': 0.139.0 - lightningcss: 1.32.0 - postcss: 8.5.19 - yuku-codegen: 0.5.48 - yuku-parser: 0.5.48 - optionalDependencies: - '@types/node': 25.0.3 - fsevents: 2.3.3 - typescript: 6.0.3 - - '@voidzero-dev/vite-plus-darwin-arm64@0.2.5': - optional: true - - '@voidzero-dev/vite-plus-darwin-x64@0.2.5': - optional: true - - '@voidzero-dev/vite-plus-linux-arm64-gnu@0.2.5': - optional: true - - '@voidzero-dev/vite-plus-linux-arm64-musl@0.2.5': - optional: true - - '@voidzero-dev/vite-plus-linux-x64-gnu@0.2.5': - optional: true - - '@voidzero-dev/vite-plus-linux-x64-musl@0.2.5': - optional: true - - '@voidzero-dev/vite-plus-win32-arm64-msvc@0.2.5': - optional: true - - '@voidzero-dev/vite-plus-win32-x64-msvc@0.2.5': - optional: true - - '@yuku-codegen/binding-darwin-arm64@0.5.48': - optional: true - - '@yuku-codegen/binding-darwin-x64@0.5.48': - optional: true - - '@yuku-codegen/binding-freebsd-x64@0.5.48': - optional: true - - '@yuku-codegen/binding-linux-arm-gnu@0.5.48': - optional: true - - '@yuku-codegen/binding-linux-arm-musl@0.5.48': - optional: true - - '@yuku-codegen/binding-linux-arm64-gnu@0.5.48': - optional: true - - '@yuku-codegen/binding-linux-arm64-musl@0.5.48': - optional: true - - '@yuku-codegen/binding-linux-x64-gnu@0.5.48': - optional: true - - '@yuku-codegen/binding-linux-x64-musl@0.5.48': - optional: true - - '@yuku-codegen/binding-win32-arm64@0.5.48': - optional: true - - '@yuku-codegen/binding-win32-x64@0.5.48': - optional: true - - '@yuku-parser/binding-darwin-arm64@0.5.48': - optional: true - - '@yuku-parser/binding-darwin-x64@0.5.48': - optional: true - - '@yuku-parser/binding-freebsd-x64@0.5.48': - optional: true - - '@yuku-parser/binding-linux-arm-gnu@0.5.48': - optional: true - - '@yuku-parser/binding-linux-arm-musl@0.5.48': - optional: true - - '@yuku-parser/binding-linux-arm64-gnu@0.5.48': - optional: true - - '@yuku-parser/binding-linux-arm64-musl@0.5.48': - optional: true - - '@yuku-parser/binding-linux-x64-gnu@0.5.48': - optional: true - - '@yuku-parser/binding-linux-x64-musl@0.5.48': - optional: true - - '@yuku-parser/binding-win32-arm64@0.5.48': - optional: true - - '@yuku-parser/binding-win32-x64@0.5.48': - optional: true - - '@yuku-toolchain/types@0.5.43': {} - - ansi-regex@5.0.1: {} - - ansi-styles@5.2.0: {} - - aria-query@5.3.0: - dependencies: - dequal: 2.0.3 - - assertion-error@2.0.1: {} - - bun@1.3.14: - optionalDependencies: - '@oven/bun-darwin-aarch64': 1.3.14 - '@oven/bun-darwin-x64': 1.3.14 - '@oven/bun-darwin-x64-baseline': 1.3.14 - '@oven/bun-freebsd-aarch64': 1.3.14 - '@oven/bun-freebsd-x64': 1.3.14 - '@oven/bun-linux-aarch64': 1.3.14 - '@oven/bun-linux-aarch64-android': 1.3.14 - '@oven/bun-linux-aarch64-musl': 1.3.14 - '@oven/bun-linux-x64': 1.3.14 - '@oven/bun-linux-x64-android': 1.3.14 - '@oven/bun-linux-x64-baseline': 1.3.14 - '@oven/bun-linux-x64-musl': 1.3.14 - '@oven/bun-linux-x64-musl-baseline': 1.3.14 - '@oven/bun-windows-aarch64': 1.3.14 - '@oven/bun-windows-x64': 1.3.14 - '@oven/bun-windows-x64-baseline': 1.3.14 - - chai@6.2.2: {} - - convert-source-map@2.0.0: {} - - cross-env@10.1.0: - dependencies: - '@epic-web/invariant': 1.0.0 - cross-spawn: 7.0.6 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - deno@2.9.3: - optionalDependencies: - '@deno/darwin-arm64': 2.9.3 - '@deno/darwin-x64': 2.9.3 - '@deno/linux-arm64-glibc': 2.9.3 - '@deno/linux-x64-glibc': 2.9.3 - '@deno/win32-arm64': 2.9.3 - '@deno/win32-x64': 2.9.3 - - dequal@2.0.3: {} - - detect-libc@2.1.2: {} - - dom-accessibility-api@0.5.16: {} - - es-module-lexer@2.1.0: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.9 - - expect-type@1.3.0: {} - - fdir@6.5.0(picomatch@4.0.5): - optionalDependencies: - picomatch: 4.0.5 - - fsevents@2.3.2: - optional: true - - fsevents@2.3.3: - optional: true - - isexe@2.0.0: {} - - js-tokens@4.0.0: {} - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - lz-string@1.5.0: {} - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - mrmime@2.0.1: {} - - nanoid@3.3.12: {} - - obug@2.1.1: {} - - oxfmt@0.57.0(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3))): - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.57.0 - '@oxfmt/binding-android-arm64': 0.57.0 - '@oxfmt/binding-darwin-arm64': 0.57.0 - '@oxfmt/binding-darwin-x64': 0.57.0 - '@oxfmt/binding-freebsd-x64': 0.57.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.57.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.57.0 - '@oxfmt/binding-linux-arm64-gnu': 0.57.0 - '@oxfmt/binding-linux-arm64-musl': 0.57.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.57.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.57.0 - '@oxfmt/binding-linux-riscv64-musl': 0.57.0 - '@oxfmt/binding-linux-s390x-gnu': 0.57.0 - '@oxfmt/binding-linux-x64-gnu': 0.57.0 - '@oxfmt/binding-linux-x64-musl': 0.57.0 - '@oxfmt/binding-openharmony-arm64': 0.57.0 - '@oxfmt/binding-win32-arm64-msvc': 0.57.0 - '@oxfmt/binding-win32-ia32-msvc': 0.57.0 - '@oxfmt/binding-win32-x64-msvc': 0.57.0 - vite-plus: 0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3)) - - oxfmt@0.58.0(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(typescript@6.0.3)): - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.58.0 - '@oxfmt/binding-android-arm64': 0.58.0 - '@oxfmt/binding-darwin-arm64': 0.58.0 - '@oxfmt/binding-darwin-x64': 0.58.0 - '@oxfmt/binding-freebsd-x64': 0.58.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.58.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.58.0 - '@oxfmt/binding-linux-arm64-gnu': 0.58.0 - '@oxfmt/binding-linux-arm64-musl': 0.58.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.58.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.58.0 - '@oxfmt/binding-linux-riscv64-musl': 0.58.0 - '@oxfmt/binding-linux-s390x-gnu': 0.58.0 - '@oxfmt/binding-linux-x64-gnu': 0.58.0 - '@oxfmt/binding-linux-x64-musl': 0.58.0 - '@oxfmt/binding-openharmony-arm64': 0.58.0 - '@oxfmt/binding-win32-arm64-msvc': 0.58.0 - '@oxfmt/binding-win32-ia32-msvc': 0.58.0 - '@oxfmt/binding-win32-x64-msvc': 0.58.0 - vite-plus: 0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(typescript@6.0.3) - - oxfmt@0.58.0(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3))): - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.58.0 - '@oxfmt/binding-android-arm64': 0.58.0 - '@oxfmt/binding-darwin-arm64': 0.58.0 - '@oxfmt/binding-darwin-x64': 0.58.0 - '@oxfmt/binding-freebsd-x64': 0.58.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.58.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.58.0 - '@oxfmt/binding-linux-arm64-gnu': 0.58.0 - '@oxfmt/binding-linux-arm64-musl': 0.58.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.58.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.58.0 - '@oxfmt/binding-linux-riscv64-musl': 0.58.0 - '@oxfmt/binding-linux-s390x-gnu': 0.58.0 - '@oxfmt/binding-linux-x64-gnu': 0.58.0 - '@oxfmt/binding-linux-x64-musl': 0.58.0 - '@oxfmt/binding-openharmony-arm64': 0.58.0 - '@oxfmt/binding-win32-arm64-msvc': 0.58.0 - '@oxfmt/binding-win32-ia32-msvc': 0.58.0 - '@oxfmt/binding-win32-x64-msvc': 0.58.0 - vite-plus: 0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3)) - optional: true - - oxlint-tsgolint@0.24.0: - optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.24.0 - '@oxlint-tsgolint/darwin-x64': 0.24.0 - '@oxlint-tsgolint/linux-arm64': 0.24.0 - '@oxlint-tsgolint/linux-x64': 0.24.0 - '@oxlint-tsgolint/win32-arm64': 0.24.0 - '@oxlint-tsgolint/win32-x64': 0.24.0 - - oxlint@1.73.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(typescript@6.0.3)): - optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.73.0 - '@oxlint/binding-android-arm64': 1.73.0 - '@oxlint/binding-darwin-arm64': 1.73.0 - '@oxlint/binding-darwin-x64': 1.73.0 - '@oxlint/binding-freebsd-x64': 1.73.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.73.0 - '@oxlint/binding-linux-arm-musleabihf': 1.73.0 - '@oxlint/binding-linux-arm64-gnu': 1.73.0 - '@oxlint/binding-linux-arm64-musl': 1.73.0 - '@oxlint/binding-linux-ppc64-gnu': 1.73.0 - '@oxlint/binding-linux-riscv64-gnu': 1.73.0 - '@oxlint/binding-linux-riscv64-musl': 1.73.0 - '@oxlint/binding-linux-s390x-gnu': 1.73.0 - '@oxlint/binding-linux-x64-gnu': 1.73.0 - '@oxlint/binding-linux-x64-musl': 1.73.0 - '@oxlint/binding-openharmony-arm64': 1.73.0 - '@oxlint/binding-win32-arm64-msvc': 1.73.0 - '@oxlint/binding-win32-ia32-msvc': 1.73.0 - '@oxlint/binding-win32-x64-msvc': 1.73.0 - oxlint-tsgolint: 0.24.0 - vite-plus: 0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(typescript@6.0.3) - - oxlint@1.73.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3))): - optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.73.0 - '@oxlint/binding-android-arm64': 1.73.0 - '@oxlint/binding-darwin-arm64': 1.73.0 - '@oxlint/binding-darwin-x64': 1.73.0 - '@oxlint/binding-freebsd-x64': 1.73.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.73.0 - '@oxlint/binding-linux-arm-musleabihf': 1.73.0 - '@oxlint/binding-linux-arm64-gnu': 1.73.0 - '@oxlint/binding-linux-arm64-musl': 1.73.0 - '@oxlint/binding-linux-ppc64-gnu': 1.73.0 - '@oxlint/binding-linux-riscv64-gnu': 1.73.0 - '@oxlint/binding-linux-riscv64-musl': 1.73.0 - '@oxlint/binding-linux-s390x-gnu': 1.73.0 - '@oxlint/binding-linux-x64-gnu': 1.73.0 - '@oxlint/binding-linux-x64-musl': 1.73.0 - '@oxlint/binding-openharmony-arm64': 1.73.0 - '@oxlint/binding-win32-arm64-msvc': 1.73.0 - '@oxlint/binding-win32-ia32-msvc': 1.73.0 - '@oxlint/binding-win32-x64-msvc': 1.73.0 - oxlint-tsgolint: 0.24.0 - vite-plus: 0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3)) - - path-key@3.1.1: {} - - pathe@2.0.3: {} - - picocolors@1.1.1: {} - - picomatch@4.0.5: {} - - playwright-core@1.61.1: {} - - playwright@1.61.1: - dependencies: - playwright-core: 1.61.1 - optionalDependencies: - fsevents: 2.3.2 - - pngjs@7.0.0: {} - - postcss@8.5.19: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - pretty-format@27.5.1: - dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 - - react-is@17.0.2: {} - - rolldown@1.1.5: - dependencies: - '@oxc-project/types': 0.139.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.1.5 - '@rolldown/binding-darwin-arm64': 1.1.5 - '@rolldown/binding-darwin-x64': 1.1.5 - '@rolldown/binding-freebsd-x64': 1.1.5 - '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 - '@rolldown/binding-linux-arm64-gnu': 1.1.5 - '@rolldown/binding-linux-arm64-musl': 1.1.5 - '@rolldown/binding-linux-ppc64-gnu': 1.1.5 - '@rolldown/binding-linux-s390x-gnu': 1.1.5 - '@rolldown/binding-linux-x64-gnu': 1.1.5 - '@rolldown/binding-linux-x64-musl': 1.1.5 - '@rolldown/binding-openharmony-arm64': 1.1.5 - '@rolldown/binding-wasm32-wasi': 1.1.5 - '@rolldown/binding-win32-arm64-msvc': 1.1.5 - '@rolldown/binding-win32-x64-msvc': 1.1.5 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - siginfo@2.0.0: {} - - sirv@3.0.2: - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 - - source-map-js@1.2.1: {} - - stackback@0.0.2: {} - - std-env@4.1.0: {} - - tinybench@2.9.0: {} - - tinyexec@1.1.2: {} - - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 - - tinypool@2.1.0: {} - - tinyrainbow@3.1.0: {} - - totalist@3.0.1: {} - - tslib@2.8.1: - optional: true - - typescript@6.0.3: {} - - undici-types@7.16.0: {} - - vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(typescript@6.0.3): - dependencies: - '@oxc-project/types': 0.139.0 - '@oxlint/plugins': 1.73.0 - '@vitest/browser': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(vitest@4.1.10) - '@vitest/browser-preview': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(vitest@4.1.10) - '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)) - '@vitest/pretty-format': 4.1.10 - '@vitest/runner': 4.1.10 - '@vitest/snapshot': 4.1.10 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - '@voidzero-dev/vite-plus-core': 0.2.5(@types/node@25.0.3)(typescript@6.0.3) - oxfmt: 0.58.0(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(typescript@6.0.3)) - oxlint: 1.73.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(typescript@6.0.3)) - oxlint-tsgolint: 0.24.0 - vitest: 4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@vitest/browser-preview@4.1.10)(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)) - optionalDependencies: - '@vitest/browser-playwright': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10) - '@voidzero-dev/vite-plus-darwin-arm64': 0.2.5 - '@voidzero-dev/vite-plus-darwin-x64': 0.2.5 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.2.5 - '@voidzero-dev/vite-plus-linux-arm64-musl': 0.2.5 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.2.5 - '@voidzero-dev/vite-plus-linux-x64-musl': 0.2.5 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.2.5 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.2.5 - transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@edge-runtime/vm' - - '@opentelemetry/api' - - '@types/node' - - '@vitejs/devtools' - - '@vitest/coverage-istanbul' - - '@vitest/coverage-v8' - - '@vitest/ui' - - bufferutil - - esbuild - - happy-dom - - jiti - - jsdom - - less - - msw - - publint - - sass - - sass-embedded - - stylus - - sugarss - - svelte - - terser - - tsx - - typescript - - unplugin-unused - - unrun - - utf-8-validate - - vite - - yaml - - vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3)): - dependencies: - '@oxc-project/types': 0.139.0 - '@oxlint/plugins': 1.73.0 - '@vitest/browser': 4.1.10(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10) - '@vitest/browser-preview': 4.1.10(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10) - '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@25.0.3)) - '@vitest/pretty-format': 4.1.10 - '@vitest/runner': 4.1.10 - '@vitest/snapshot': 4.1.10 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - '@voidzero-dev/vite-plus-core': 0.2.5(@types/node@25.0.3)(typescript@6.0.3) - oxfmt: 0.58.0(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3))) - oxlint: 1.73.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.5(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(typescript@6.0.3)(vite@8.1.5(@types/node@25.0.3))) - oxlint-tsgolint: 0.24.0 - vitest: 4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(vite@8.1.5(@types/node@25.0.3)) - optionalDependencies: - '@vitest/browser-playwright': 4.1.10(playwright@1.61.1)(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10) - '@voidzero-dev/vite-plus-darwin-arm64': 0.2.5 - '@voidzero-dev/vite-plus-darwin-x64': 0.2.5 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.2.5 - '@voidzero-dev/vite-plus-linux-arm64-musl': 0.2.5 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.2.5 - '@voidzero-dev/vite-plus-linux-x64-musl': 0.2.5 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.2.5 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.2.5 - transitivePeerDependencies: - - '@arethetypeswrong/core' - - '@edge-runtime/vm' - - '@opentelemetry/api' - - '@types/node' - - '@vitejs/devtools' - - '@vitest/coverage-istanbul' - - '@vitest/coverage-v8' - - '@vitest/ui' - - bufferutil - - esbuild - - happy-dom - - jiti - - jsdom - - less - - msw - - publint - - sass - - sass-embedded - - stylus - - sugarss - - svelte - - terser - - tsx - - typescript - - unplugin-unused - - unrun - - utf-8-validate - - vite - - yaml - optional: true - - vite@8.1.5(@types/node@25.0.3): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.5 - postcss: 8.5.19 - rolldown: 1.1.5 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 25.0.3 - fsevents: 2.3.3 - - vitest@4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10))(@vitest/browser-preview@4.1.10)(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)): - dependencies: - '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)) - '@vitest/pretty-format': 4.1.10 - '@vitest/runner': 4.1.10 - '@vitest/snapshot': 4.1.10 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.5 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.1.2 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: '@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3)' - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.0.3 - '@vitest/browser-playwright': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(playwright@1.61.1)(vitest@4.1.10) - '@vitest/browser-preview': 4.1.10(@voidzero-dev/vite-plus-core@0.2.4(@types/node@25.0.3)(typescript@6.0.3))(vitest@4.1.10) - transitivePeerDependencies: - - msw - - vitest@4.1.10(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(@vitest/browser-preview@4.1.10)(vite@8.1.5(@types/node@25.0.3)): - dependencies: - '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@25.0.3)) - '@vitest/pretty-format': 4.1.10 - '@vitest/runner': 4.1.10 - '@vitest/snapshot': 4.1.10 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.5 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.1.2 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 8.1.5(@types/node@25.0.3) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.0.3 - '@vitest/browser-playwright': 4.1.10(playwright@1.61.1)(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10) - '@vitest/browser-preview': 4.1.10(vite@8.1.5(@types/node@25.0.3))(vitest@4.1.10) - transitivePeerDependencies: - - msw - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - ws@8.21.0: {} - - yuku-codegen@0.5.48: - dependencies: - '@yuku-toolchain/types': 0.5.43 - optionalDependencies: - '@yuku-codegen/binding-darwin-arm64': 0.5.48 - '@yuku-codegen/binding-darwin-x64': 0.5.48 - '@yuku-codegen/binding-freebsd-x64': 0.5.48 - '@yuku-codegen/binding-linux-arm-gnu': 0.5.48 - '@yuku-codegen/binding-linux-arm-musl': 0.5.48 - '@yuku-codegen/binding-linux-arm64-gnu': 0.5.48 - '@yuku-codegen/binding-linux-arm64-musl': 0.5.48 - '@yuku-codegen/binding-linux-x64-gnu': 0.5.48 - '@yuku-codegen/binding-linux-x64-musl': 0.5.48 - '@yuku-codegen/binding-win32-arm64': 0.5.48 - '@yuku-codegen/binding-win32-x64': 0.5.48 - - yuku-parser@0.5.48: - dependencies: - '@yuku-toolchain/types': 0.5.43 - optionalDependencies: - '@yuku-parser/binding-darwin-arm64': 0.5.48 - '@yuku-parser/binding-darwin-x64': 0.5.48 - '@yuku-parser/binding-freebsd-x64': 0.5.48 - '@yuku-parser/binding-linux-arm-gnu': 0.5.48 - '@yuku-parser/binding-linux-arm-musl': 0.5.48 - '@yuku-parser/binding-linux-arm64-gnu': 0.5.48 - '@yuku-parser/binding-linux-arm64-musl': 0.5.48 - '@yuku-parser/binding-linux-x64-gnu': 0.5.48 - '@yuku-parser/binding-linux-x64-musl': 0.5.48 - '@yuku-parser/binding-win32-arm64': 0.5.48 - '@yuku-parser/binding-win32-x64': 0.5.48 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml deleted file mode 100644 index d02ce9988..000000000 --- a/pnpm-workspace.yaml +++ /dev/null @@ -1,44 +0,0 @@ -packages: - - . - - packages/tools - - packages/vite-task-client - -allowBuilds: - '@playwright/browser-chromium': true - bun: true - deno: true - -catalog: - '@tsconfig/strictest': ^2.0.8 - '@types/node': 25.0.3 - '@playwright/browser-chromium': 1.61.1 - '@vitest/browser-playwright': 4.1.10 - bun: 1.3.14 - cross-env: ^10.1.0 - deno: 2.9.3 - husky: ^9.1.7 - lint-staged: ^17.0.0 - oxfmt: 0.57.0 - oxlint: ^1.55.0 - oxlint-tsgolint: ^0.24.0 - # Playwright 1.52 pins the browser package to Chromium 136. - playwright: 1.61.1 - typescript: ^6.0.3 - vite: npm:@voidzero-dev/vite-plus-core@0.2.4 - vite-plus: 0.2.5 - vitest: 4.1.10 - -catalogMode: prefer -overrides: - playwright: 1.61.1 - vite: 'catalog:' - # The e2e harness symlinks packages/tools' `vite` binary into fixtures to - # exercise the real vite-task-client integration. The global `vite: catalog:` - # override above routes vite to the Vite+ toolchain fork, which does not carry - # the integration — so scope vite-task-tools' `vite` to a real upstream build. - vite-task-tools>vite: 8.1.5 -peerDependencyRules: - allowAny: - - vite - allowedVersions: - vite: '*' diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index c42b17fcc..000000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,6 +0,0 @@ -[toolchain] -# Needed nightly features: -# - cargo `Z-bindeps` to build and embed preload shared libraries as dependencies of fspy -# - `windows_process_extensions_main_thread_handle` to get the main thread handle for Detours injection -channel = "nightly-2026-06-07" -profile = "default" diff --git a/vite.config.ts b/vite.config.ts deleted file mode 100644 index a933166c4..000000000 --- a/vite.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineConfig } from 'vite-plus'; - -export default defineConfig({ - staged: { - '*': 'vp check --fix', - '*.rs': 'cargo fmt --', - }, - lint: { - jsPlugins: [{ name: 'vite-plus', specifier: 'vite-plus/oxlint-plugin' }], - rules: { 'vite-plus/prefer-vite-plus-imports': 'error' }, - options: { typeAware: true, typeCheck: true }, - ignorePatterns: ['playground/**', '**/fixtures/**'], - }, - fmt: { - singleQuote: true, - ignorePatterns: [ - 'crates/fspy_detours_sys/detours', - 'crates/vite_task_graph/run-config.ts', - '**/fixtures/*/snapshots', - 'packages/vite-task-client/src/index.d.ts', - ], - }, -});