diff --git a/.github/workflows/dbt_ci.yml b/.github/workflows/dbt_ci.yml index d31e13d..09a5b9a 100644 --- a/.github/workflows/dbt_ci.yml +++ b/.github/workflows/dbt_ci.yml @@ -1,17 +1,17 @@ name: dbt CI -# NOTE: Pull request CI jobs are disabled by default. Uncomment to enable standard CI workflows -# Check the README.md to make sure you understand the credit costs associated with running dbt. -# on: -# pull_request: -# types: [opened, synchronize, reopened] -# paths: -# - 'models/**' -# - 'macros/**' -# - 'dbt_project.yml' -# - 'profiles.yml' -# - 'packages.yml' -# - '.github/workflows/dbt_ci.yml' +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - 'models/**' + - 'macros/**' + - 'tests/**' + - 'dbt_project.yml' + - 'profiles.yml' + - 'packages.yml' + - '.github/workflows/dbt_ci.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -24,13 +24,21 @@ jobs: env: DUNE_API_KEY: ${{ secrets.DUNE_API_KEY }} - DUNE_TEAM_NAME: ${{ vars.DUNE_TEAM_NAME || 'dune' }} # Set as GitHub Variable or defaults to 'dune' - DEV_SCHEMA_SUFFIX: pr${{ github.event.pull_request.number }} + DUNE_TEAM_NAME: ${{ vars.DUNE_TEAM_NAME }} + DEV_SCHEMA_SUFFIX: ${{ github.event_name == 'pull_request' && format('pr{0}', github.event.pull_request.number) || 'manual' }} steps: - name: Check out code uses: actions/checkout@v4 + - name: Validate required environment + run: | + if [ -z "${DUNE_TEAM_NAME}" ]; then + echo "❌ Missing required GitHub variable: DUNE_TEAM_NAME" + echo "Set it in Settings -> Secrets and variables -> Actions -> Variables." + exit 1 + fi + - name: Install uv uses: astral-sh/setup-uv@v5 with: diff --git a/.github/workflows/dbt_deploy.yml b/.github/workflows/dbt_deploy.yml index 8203acd..6000a26 100644 --- a/.github/workflows/dbt_deploy.yml +++ b/.github/workflows/dbt_deploy.yml @@ -28,13 +28,24 @@ jobs: env: DUNE_API_KEY: ${{ secrets.DUNE_API_KEY }} - DUNE_TEAM_NAME: ${{ vars.DUNE_TEAM_NAME || 'dune' }} # Set as GitHub Variable or defaults to 'dune' + DUNE_TEAM_NAME: ${{ vars.DUNE_TEAM_NAME }} DBT_TARGET: prod # All dbt commands will use the prod target from profiles.yml + # Workaround for API keys without permission to call dune._internal.alter_view_properties. + # Remove once key permissions are upgraded for view/table metadata property updates. + DUNE_SKIP_VIEW_PROPERTIES: ${{ vars.DUNE_SKIP_VIEW_PROPERTIES || 'true' }} steps: - name: Check out code uses: actions/checkout@v4 + - name: Validate required environment + run: | + if [ -z "${DUNE_TEAM_NAME}" ]; then + echo "❌ Missing required GitHub variable: DUNE_TEAM_NAME" + echo "Set it in Settings -> Secrets and variables -> Actions -> Variables." + exit 1 + fi + - name: Install uv uses: astral-sh/setup-uv@v5 with: diff --git a/.github/workflows/dbt_prod.yml b/.github/workflows/dbt_prod.yml index bb2b8eb..d8aeb8a 100644 --- a/.github/workflows/dbt_prod.yml +++ b/.github/workflows/dbt_prod.yml @@ -18,8 +18,11 @@ jobs: env: DUNE_API_KEY: ${{ secrets.DUNE_API_KEY }} - DUNE_TEAM_NAME: ${{ vars.DUNE_TEAM_NAME || 'dune' }} # Set as GitHub Variable or defaults to 'dune' + DUNE_TEAM_NAME: ${{ vars.DUNE_TEAM_NAME }} DBT_TARGET: prod # All dbt commands will use the prod target from profiles.yml + # Workaround for API keys without permission to call dune._internal.alter_view_properties. + # Remove once key permissions are upgraded for view/table metadata property updates. + DUNE_SKIP_VIEW_PROPERTIES: ${{ vars.DUNE_SKIP_VIEW_PROPERTIES || 'true' }} steps: - name: Check out code @@ -27,6 +30,14 @@ jobs: with: ref: main + - name: Validate required environment + run: | + if [ -z "${DUNE_TEAM_NAME}" ]; then + echo "❌ Missing required GitHub variable: DUNE_TEAM_NAME" + echo "Set it in Settings -> Secrets and variables -> Actions -> Variables." + exit 1 + fi + - name: Install uv uses: astral-sh/setup-uv@v5 with: diff --git a/.github/workflows/dbt_quality.yml b/.github/workflows/dbt_quality.yml new file mode 100644 index 0000000..1ad1379 --- /dev/null +++ b/.github/workflows/dbt_quality.yml @@ -0,0 +1,42 @@ +name: dbt quality + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - 'models/**' + - 'macros/**' + - 'tests/**' + - 'dbt_project.yml' + - 'packages.yml' + - 'profiles.yml' + - 'pyproject.toml' + - 'uv.lock' + - '.github/workflows/dbt_quality.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + parse: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync --locked + + - name: Install dbt packages + run: uv run dbt deps + + - name: Parse dbt project + run: uv run dbt parse --no-partial-parse diff --git a/.gitignore b/.gitignore index 38593ed..bfc328d 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,8 @@ logs/ .envrc # Compiled files -*.pyc \ No newline at end of file +*.pyc + +# JavaScript tooling (husky/lint-staged) +node_modules/ +.husky/_/ \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..4e51b48 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +npm exec lint-staged diff --git a/README.md b/README.md index 8a889b6..6a753c0 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,22 @@ uv run dbt test --select model_name # Test specific model uv run dbt docs generate && uv run dbt docs serve # View documentation ``` +## Contributing + +Install local git hooks to run fast dbt checks before commits: + +```bash +npm install +npm run prepare +``` + +Manually run the same local validation used in pre-commit: + +```bash +uv run dbt deps +uv run dbt parse --no-partial-parse +``` + ## Cursor AI Rules This repo includes **optional** Cursor AI guidelines in `.cursor/rules/`: diff --git a/docs/cicd.md b/docs/cicd.md index 24d34a6..1aa70a2 100644 --- a/docs/cicd.md +++ b/docs/cicd.md @@ -11,16 +11,17 @@ GitHub provides and manages these runners - no infrastructure setup required on ## Pull Request Workflow (CI) **Trigger:** Every pull request -**File:** `.github/workflows/dbt_run.yml` +**File:** `.github/workflows/dbt_ci.yml` ### What It Does -1. Enforces branch is up-to-date with main +1. Runs on every pull request affecting dbt project paths (or manual dispatch) 2. Installs dependencies and dbt packages -3. Runs modified models with `--full-refresh` -4. Tests modified models -5. Runs modified incremental models (incremental run) -6. Tests modified incremental models again +3. Compiles project and compares against main manifest +4. Runs modified models with `--full-refresh` +5. Tests modified models +6. Runs modified incremental models (incremental run) +7. Tests modified incremental models again ### PR Schema Isolation @@ -53,6 +54,14 @@ uv run dbt test --select modified_model ✅ **Fix failing tests** - Don't skip tests or disable checks +### Cost Control Policy + +The Dune integration CI runs on matching PR changes, so monitor credit usage: + +- Keep the quality workflow (`dbt_quality.yml`) as a zero-credit static validation baseline +- Use `state:modified` in CI (already configured) to limit heavy runs to changed models +- Use path filters to avoid triggering on irrelevant repository changes + ## Production Workflow **Trigger:** Manual (schedule disabled by default) @@ -99,7 +108,20 @@ DUNE_API_KEY=your_api_key DUNE_TEAM_NAME=your_team_name ``` -Optional - defaults to `'dune'` if not set. +This variable is required for deploy/prod/CI workflows. + +## Quality Workflow (No Dune Credits) + +**Trigger:** Every pull request affecting dbt project files +**File:** `.github/workflows/dbt_quality.yml` + +This workflow does not require `DUNE_API_KEY` and runs only static project checks: + +1. `uv sync --locked` +2. `uv run dbt deps` +3. `uv run dbt parse --no-partial-parse` + +Use this as the mandatory baseline check for every PR. ## Email Notifications @@ -119,8 +141,19 @@ To receive failure alerts: Runs when: -- PR opened, synchronized, or reopened -- Changes to: `models/`, `macros/`, `dbt_project.yml`, `profiles.yml`, `packages.yml`, workflow file +- PR opened, synchronized, reopened, or marked ready for review +- Changes to: `models/`, `macros/`, `tests/`, `dbt_project.yml`, `profiles.yml`, `packages.yml`, workflow file + +## Branch Protection (Required Checks) + +To block merges unless CI passes: + +1. Go to GitHub → Repository Settings → Branches +2. Edit the protection rule for `main` +3. Enable **Require status checks to pass before merging** +4. Mark these checks as required: + - `parse` (from `dbt quality`) + - `dbt-ci` (from `dbt CI`) ### Production Workflow @@ -149,6 +182,17 @@ dbt test output → specific test name → error message Query the model in Dune to investigate. +### Main Manifest Missing in PR CI + +PR Dune CI depends on `prod-manifest-latest` uploaded by deploy workflow. + +If CI fails with missing manifest: + +1. Go to Actions → `dbt deploy` +2. Run workflow on `main` +3. Wait for completion (artifact upload) +4. Re-run PR check + ### Connection Errors - Verify `DUNE_API_KEY` secret is set correctly diff --git a/docs/testing.md b/docs/testing.md index 9778a7e..3ad9961 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -102,6 +102,7 @@ Use based on your data quality requirements: - `accepted_values` - Enum/categorical validation - `relationships` - Foreign key checks - Custom tests in `tests/` directory +- dbt native `unit_tests` for deterministic model logic checks with mocked inputs Example: ```yaml @@ -126,6 +127,12 @@ uv run dbt test # Test specific model uv run dbt test --select my_model +# Test only singular tests in tests/ +uv run dbt test --select test_type:singular + +# Test only dbt unit tests +uv run dbt test --select test_type:unit + # Test specific model and downstream uv run dbt test --select my_model+ @@ -133,6 +140,36 @@ uv run dbt test --select my_model+ uv run dbt run --select my_model && uv run dbt test --select my_model ``` +## Singular Tests (Business Invariants) + +Store custom SQL tests in `tests/` when a rule is business-specific and not covered by generic tests. + +Current examples: + +- `tests/labels/test_labels_balancer_v2_pools_category.sql` +- `tests/labels/test_labels_balancer_v3_pools_category.sql` + +These tests enforce that labels keep their expected category values. + +## Unit Tests (Model Logic with Mocked Inputs) + +Use dbt `unit_tests` for models that mostly combine or transform upstream model outputs deterministically. + +Current examples are defined in: + +- `models/_projects/balancer/labels/_schema.yml` + - `labels_balancer_v2_pools_unions_chain_outputs` + - `labels_balancer_v3_pools_unions_chain_outputs` + +These tests verify that cross-chain union models preserve rows from upstream chain models. + +## Local vs CI Test Matrix + +- **Pre-commit (Husky):** `dbt parse` only (fast and zero-credit) +- **PR Quality CI (`dbt_quality.yml`):** `dbt parse` only (fast and zero-credit) +- **PR Dune CI (`dbt_ci.yml`):** full integration runs/tests against Dune (credit-consuming, label-gated) +- **Prod workflows (`dbt_deploy.yml`, `dbt_prod.yml`):** deployment and incremental health checks + ## Why These Tests? **For incremental models:** diff --git a/models/_projects/balancer/labels/_schema.yml b/models/_projects/balancer/labels/_schema.yml index de07e1b..cc5573f 100644 --- a/models/_projects/balancer/labels/_schema.yml +++ b/models/_projects/balancer/labels/_schema.yml @@ -296,6 +296,54 @@ models: 'pools', ] description: 'Balancer V2 liquidity pools created across blockchains.' + data_tests: + - dbt_utils.unique_combination_of_columns: + combination_of_columns: + - blockchain + - address + unit_tests: + - name: labels_balancer_v2_pools_unions_chain_outputs + given: + - input: ref('labels_balancer_v2_pools_ethereum') + rows: + - blockchain: ethereum + address: 0x0000000000000000000000000000000000000001 + name: balancer-test-pool + pool_type: weighted + category: balancer_v2_pool + contributor: balancerlabs + source: query + created_at: '2023-08-17 00:00:00' + updated_at: '2023-08-18 00:00:00' + model_name: balancer_v2_pools_ethereum + label_type: identifier + - input: ref('labels_balancer_v2_pools_arbitrum') + rows: [] + - input: ref('labels_balancer_v2_pools_optimism') + rows: [] + - input: ref('labels_balancer_v2_pools_polygon') + rows: [] + - input: ref('labels_balancer_v2_pools_gnosis') + rows: [] + - input: ref('labels_balancer_v2_pools_avalanche_c') + rows: [] + - input: ref('labels_balancer_v2_pools_base') + rows: [] + - input: ref('labels_balancer_v2_pools_zkevm') + rows: [] + expect: + rows: + - blockchain: ethereum + address: 0x0000000000000000000000000000000000000001 + name: balancer-test-pool + pool_type: weighted + category: balancer_v2_pool + contributor: balancerlabs + source: query + created_at: '2023-08-17 00:00:00' + updated_at: '2023-08-18 00:00:00' + model_name: balancer_v2_pools_ethereum + label_type: identifier columns: - *blockchain - *address @@ -517,6 +565,54 @@ models: config: tags: ['labels', 'arbitrum', 'avalanche_c', 'base', 'ethereum', 'gnosis', 'hyperevm', 'monad', 'plasma', 'balancer', 'pools'] description: 'Balancer V3 liquidity pools created across blockchains.' + data_tests: + - dbt_utils.unique_combination_of_columns: + combination_of_columns: + - blockchain + - address + unit_tests: + - name: labels_balancer_v3_pools_unions_chain_outputs + given: + - input: ref('labels_balancer_v3_pools_ethereum') + rows: + - blockchain: ethereum + address: 0x0000000000000000000000000000000000000002 + name: balancer-v3-test-pool + pool_type: weighted + category: balancer_v3_pool + contributor: balancerlabs + source: query + created_at: '2023-08-17 00:00:00' + updated_at: '2023-08-18 00:00:00' + model_name: balancer_v3_pools_ethereum + label_type: identifier + - input: ref('labels_balancer_v3_pools_gnosis') + rows: [] + - input: ref('labels_balancer_v3_pools_arbitrum') + rows: [] + - input: ref('labels_balancer_v3_pools_base') + rows: [] + - input: ref('labels_balancer_v3_pools_plasma') + rows: [] + - input: ref('labels_balancer_v3_pools_avalanche_c') + rows: [] + - input: ref('labels_balancer_v3_pools_hyperevm') + rows: [] + - input: ref('labels_balancer_v3_pools_monad') + rows: [] + expect: + rows: + - blockchain: ethereum + address: 0x0000000000000000000000000000000000000002 + name: balancer-v3-test-pool + pool_type: weighted + category: balancer_v3_pool + contributor: balancerlabs + source: query + created_at: '2023-08-17 00:00:00' + updated_at: '2023-08-18 00:00:00' + model_name: balancer_v3_pools_ethereum + label_type: identifier columns: - *blockchain - *address diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3ad022c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,784 @@ +{ + "name": "balancer-dune-dbt-hooks", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "balancer-dune-dbt-hooks", + "devDependencies": { + "husky": "^9.1.7", + "lint-staged": "^15.5.2" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lint-staged": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", + "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b1752db --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "balancer-dune-dbt-hooks", + "private": true, + "scripts": { + "prepare": "husky", + "precommit:dbt": "bash ./scripts/precommit_dbt_parse.sh" + }, + "devDependencies": { + "husky": "^9.1.7", + "lint-staged": "^15.5.2" + }, + "lint-staged": { + "{models,macros,tests}/**/*.{sql,yml,yaml}": [ + "npm run precommit:dbt" + ], + "{dbt_project.yml,packages.yml,profiles.yml}": [ + "npm run precommit:dbt" + ] + } +} diff --git a/scripts/precommit_dbt_parse.sh b/scripts/precommit_dbt_parse.sh new file mode 100755 index 0000000..450301b --- /dev/null +++ b/scripts/precommit_dbt_parse.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ ! -d "dbt_packages" ]; then + echo "dbt_packages not found, running dbt deps..." + uv run dbt deps +fi + +echo "Running dbt parse..." +uv run dbt parse --no-partial-parse diff --git a/tests/labels/test_labels_balancer_v2_pools_category.sql b/tests/labels/test_labels_balancer_v2_pools_category.sql new file mode 100644 index 0000000..eba4944 --- /dev/null +++ b/tests/labels/test_labels_balancer_v2_pools_category.sql @@ -0,0 +1,8 @@ +-- This test fails if any v2 pool label is emitted with the wrong category. +select + blockchain, + address, + category +from {{ ref('labels_balancer_v2_pools') }} +where category <> 'balancer_v2_pool' +limit 1 diff --git a/tests/labels/test_labels_balancer_v3_pools_category.sql b/tests/labels/test_labels_balancer_v3_pools_category.sql new file mode 100644 index 0000000..098f449 --- /dev/null +++ b/tests/labels/test_labels_balancer_v3_pools_category.sql @@ -0,0 +1,8 @@ +-- This test fails if any v3 pool label is emitted with the wrong category. +select + blockchain, + address, + category +from {{ ref('labels_balancer_v3_pools') }} +where category <> 'balancer_v3_pool' +limit 1