From d894d50eccf012da586bc83445ba54c5fd520d56 Mon Sep 17 00:00:00 2001 From: VikramChennai Date: Thu, 30 Apr 2026 09:49:18 -0700 Subject: [PATCH 01/15] ci: add ardent branch workflow to validate CI flow on PRs Made-with: Cursor --- .github/workflows/ardent-branch.yml | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/ardent-branch.yml diff --git a/.github/workflows/ardent-branch.yml b/.github/workflows/ardent-branch.yml new file mode 100644 index 0000000..63b18e5 --- /dev/null +++ b/.github/workflows/ardent-branch.yml @@ -0,0 +1,42 @@ +name: Test with Ardent branch + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Install Ardent CLI + run: npm install -g ardent-cli + + - name: Log in to Ardent + run: ardent login --token ${{ secrets.ARDENT_TOKEN }} + + - name: Create database branch + run: | + BRANCH_NAME="pr-${{ github.event.pull_request.number }}-${{ github.run_id }}" + ardent branch create "$BRANCH_NAME" + echo "DATABASE_URL=$(ardent branch info | awk '/URL:/{print $2}')" >> "$GITHUB_ENV" + echo "ARDENT_BRANCH=$BRANCH_NAME" >> "$GITHUB_ENV" + + - name: Verify DATABASE_URL is set + run: | + echo "DATABASE_URL is ${#DATABASE_URL} chars" + test -n "$DATABASE_URL" || (echo "DATABASE_URL is empty" && exit 1) + + - name: Test database connection + run: | + npx pg-connection-string@2 "$DATABASE_URL" > /dev/null 2>&1 || true + echo "SELECT 1" | npx psql-simple "$DATABASE_URL" 2>/dev/null || echo "psql not available, skipping live query test" + + - name: Delete database branch + if: always() + run: ardent branch delete "$ARDENT_BRANCH" From a905fb4a79a63ad6e852521ea2cbc535451c92fd Mon Sep 17 00:00:00 2001 From: VikramChennai Date: Thu, 30 Apr 2026 10:01:41 -0700 Subject: [PATCH 02/15] ci: add project and connector switch after login Made-with: Cursor --- .github/workflows/ardent-branch.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ardent-branch.yml b/.github/workflows/ardent-branch.yml index 63b18e5..cdae5a6 100644 --- a/.github/workflows/ardent-branch.yml +++ b/.github/workflows/ardent-branch.yml @@ -20,6 +20,11 @@ jobs: - name: Log in to Ardent run: ardent login --token ${{ secrets.ARDENT_TOKEN }} + - name: Select project and connector + run: | + ardent project switch newer-testing + ardent connector switch new-connector + - name: Create database branch run: | BRANCH_NAME="pr-${{ github.event.pull_request.number }}-${{ github.run_id }}" From d4b4afe77d7abd3717300e4a2f4def036275e7de Mon Sep 17 00:00:00 2001 From: VikramChennai Date: Thu, 30 Apr 2026 10:04:09 -0700 Subject: [PATCH 03/15] ci: install psql and run real query against branch Made-with: Cursor --- .github/workflows/ardent-branch.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ardent-branch.yml b/.github/workflows/ardent-branch.yml index cdae5a6..9dc90cd 100644 --- a/.github/workflows/ardent-branch.yml +++ b/.github/workflows/ardent-branch.yml @@ -32,15 +32,11 @@ jobs: echo "DATABASE_URL=$(ardent branch info | awk '/URL:/{print $2}')" >> "$GITHUB_ENV" echo "ARDENT_BRANCH=$BRANCH_NAME" >> "$GITHUB_ENV" - - name: Verify DATABASE_URL is set - run: | - echo "DATABASE_URL is ${#DATABASE_URL} chars" - test -n "$DATABASE_URL" || (echo "DATABASE_URL is empty" && exit 1) + - name: Install psql + run: sudo apt-get install -y postgresql-client - - name: Test database connection - run: | - npx pg-connection-string@2 "$DATABASE_URL" > /dev/null 2>&1 || true - echo "SELECT 1" | npx psql-simple "$DATABASE_URL" 2>/dev/null || echo "psql not available, skipping live query test" + - name: Run query against branch + run: psql "$DATABASE_URL" -c "SELECT 1 AS connected;" - name: Delete database branch if: always() From eda64cf4e8d1e573b4d16b2aa7e873710cf3610b Mon Sep 17 00:00:00 2001 From: VikramChennai Date: Thu, 30 Apr 2026 10:07:54 -0700 Subject: [PATCH 04/15] docs(ci-cd): full validated GitHub Actions workflow with setup instructions Made-with: Cursor --- workflows/ci-cd.mdx | 65 ++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/workflows/ci-cd.mdx b/workflows/ci-cd.mdx index 9b7a00a..77c8b67 100644 --- a/workflows/ci-cd.mdx +++ b/workflows/ci-cd.mdx @@ -5,34 +5,63 @@ description: "A fresh database for every PR. Isolated, production-like, torn dow Every PR gets its own fresh database cloned from production, torn down after merge. No shared staging that drifts out of sync. No test runs that corrupt each other. -## GitHub Actions example +## Setup -The CLI needs to authenticate before running any commands. In CI, use `ardent login --token` with a token stored in your repo secrets. This creates a local session the same way `ardent login` does interactively. +1. Create an API token in the [Ardent dashboard](https://app.tryardent.com) (or `POST /v1/orgs/{org_id}/api-keys`). +2. Add the token as a repository secret called **`ARDENT_TOKEN`** in **Settings > Secrets and variables > Actions**. -After login, create the branch. The CLI doesn't expose the URL as a structured output yet, so grab it from `ardent branch info` and append it to `GITHUB_ENV` so later steps (npm, pytest, your migration runner) see `DATABASE_URL`. +## GitHub Actions workflow + +Copy this to `.github/workflows/ardent-branch.yml`. It creates an isolated database branch for every PR, makes `DATABASE_URL` available to your tests, and cleans up after. ```yaml -- name: Install Ardent CLI - run: npm install -g ardent-cli +name: Test with Ardent branch + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22" -- name: Log in to Ardent - run: ardent login --token ${{ secrets.ARDENT_TOKEN }} + - name: Install Ardent CLI + run: npm install -g ardent-cli -- name: Create database branch - run: | - NAME="pr-${{ github.event.pull_request.number }}" - ardent branch create "$NAME" - echo "DATABASE_URL=$(ardent branch info | awk '/URL:/{print $2}')" >> "$GITHUB_ENV" + - name: Log in to Ardent + run: ardent login --token ${{ secrets.ARDENT_TOKEN }} -- name: Run migrations + tests - run: npm test + - name: Select project and connector + run: | + ardent project switch + ardent connector switch -- name: Delete branch - if: always() - run: ardent branch delete pr-${{ github.event.pull_request.number }} + - name: Create database branch + run: | + BRANCH_NAME="pr-${{ github.event.pull_request.number }}-${{ github.run_id }}" + ardent branch create "$BRANCH_NAME" + echo "DATABASE_URL=$(ardent branch info | awk '/URL:/{print $2}')" >> "$GITHUB_ENV" + echo "ARDENT_BRANCH=$BRANCH_NAME" >> "$GITHUB_ENV" + + - name: Run tests + run: npm test + + - name: Delete database branch + if: always() + run: ardent branch delete "$ARDENT_BRANCH" ``` -Use the same `DATABASE_URL` for `pytest`, `pnpm test`, or any tool your repo already uses in CI. If your framework reads a different variable, write that name to `GITHUB_ENV` instead. +Replace `` and `` with the names from `ardent project list` and `ardent connector list`. If you only have one of each, the CLI auto-selects after login and you can skip that step. + +Replace `npm test` with whatever your project uses (`pytest`, `pnpm test`, `make test`, etc.). `DATABASE_URL` is available to every step after the create step. If your framework reads a different env var, change the name in the `echo` line. + +The branch name includes the PR number and run ID so concurrent runs and re-runs don't collide. `if: always()` on the delete step ensures cleanup even when tests fail. ## Why this beats a shared staging database From db53aa14f5227095a39c834494d8e9baf73553fd Mon Sep 17 00:00:00 2001 From: VikramChennai Date: Thu, 30 Apr 2026 10:25:39 -0700 Subject: [PATCH 05/15] ci: real migration tests with vitest + pg against Ardent branch Made-with: Cursor --- .github/workflows/ardent-branch.yml | 10 +- package-lock.json | 1423 +++++++++++++++++++++++++++ package.json | 24 + tests/branch-migration.test.js | 79 ++ 4 files changed, 1531 insertions(+), 5 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tests/branch-migration.test.js diff --git a/.github/workflows/ardent-branch.yml b/.github/workflows/ardent-branch.yml index 9dc90cd..f1408d7 100644 --- a/.github/workflows/ardent-branch.yml +++ b/.github/workflows/ardent-branch.yml @@ -14,6 +14,9 @@ jobs: with: node-version: "22" + - name: Install dependencies + run: npm ci + - name: Install Ardent CLI run: npm install -g ardent-cli @@ -32,11 +35,8 @@ jobs: echo "DATABASE_URL=$(ardent branch info | awk '/URL:/{print $2}')" >> "$GITHUB_ENV" echo "ARDENT_BRANCH=$BRANCH_NAME" >> "$GITHUB_ENV" - - name: Install psql - run: sudo apt-get install -y postgresql-client - - - name: Run query against branch - run: psql "$DATABASE_URL" -c "SELECT 1 AS connected;" + - name: Run migration tests + run: npm test - name: Delete database branch if: always() diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d7d4993 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1423 @@ +{ + "name": "docs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "docs", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "pg": "^8.20.0", + "vitest": "^4.1.5" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "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" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", + "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.127.0", + "@rolldown/pluginutils": "1.0.0-rc.17" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-x64": "1.0.0-rc.17", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/vite": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", + "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.17", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.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 + } + } + }, + "node_modules/vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "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.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", + "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 + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a321eb9 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "docs", + "version": "1.0.0", + "description": "Documentation for [Ardent](https://tryardent.com) — Git for your data infrastructure.", + "main": "index.js", + "scripts": { + "test": "vitest run" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ArdentAILabs/docs.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/ArdentAILabs/docs/issues" + }, + "homepage": "https://github.com/ArdentAILabs/docs#readme", + "devDependencies": { + "pg": "^8.20.0", + "vitest": "^4.1.5" + } +} diff --git a/tests/branch-migration.test.js b/tests/branch-migration.test.js new file mode 100644 index 0000000..245ccd1 --- /dev/null +++ b/tests/branch-migration.test.js @@ -0,0 +1,79 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import pg from "pg"; + +const { Client } = pg; + +let client; + +beforeAll(async () => { + client = new Client({ connectionString: process.env.DATABASE_URL }); + await client.connect(); +}); + +afterAll(async () => { + await client?.end(); +}); + +describe("migration on Ardent branch", () => { + it("connects to the branch database", async () => { + const res = await client.query("SELECT 1 AS ok"); + expect(res.rows[0].ok).toBe(1); + }); + + it("runs a CREATE TABLE migration", async () => { + await client.query(` + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + email TEXT NOT NULL UNIQUE, + created_at TIMESTAMPTZ DEFAULT now() + ) + `); + + const res = await client.query(` + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'users' + ORDER BY ordinal_position + `); + + const columns = res.rows.map((r) => r.column_name); + expect(columns).toContain("id"); + expect(columns).toContain("email"); + expect(columns).toContain("created_at"); + }); + + it("inserts and reads data", async () => { + await client.query( + "INSERT INTO users (email) VALUES ($1) ON CONFLICT DO NOTHING", + ["ci-test@example.com"] + ); + + const res = await client.query("SELECT email FROM users WHERE email = $1", [ + "ci-test@example.com", + ]); + expect(res.rows.length).toBe(1); + expect(res.rows[0].email).toBe("ci-test@example.com"); + }); + + it("runs an ALTER TABLE migration", async () => { + await client.query(` + ALTER TABLE users ADD COLUMN IF NOT EXISTS name TEXT + `); + + const res = await client.query(` + SELECT column_name FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'name' + `); + expect(res.rows.length).toBe(1); + }); + + it("cleans up with DROP TABLE", async () => { + await client.query("DROP TABLE users"); + + const res = await client.query(` + SELECT table_name FROM information_schema.tables + WHERE table_name = 'users' + `); + expect(res.rows.length).toBe(0); + }); +}); From ee045126a154586fd3165f710b1d36b373cd0dee Mon Sep 17 00:00:00 2001 From: VikramChennai Date: Thu, 30 Apr 2026 10:31:15 -0700 Subject: [PATCH 06/15] =?UTF-8?q?ci:=20fix=20tests=20=E2=80=94=20unique=20?= =?UTF-8?q?table=20name,=20proper=20timeouts,=20no=20conflicts=20with=20pr?= =?UTF-8?q?od=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- tests/branch-migration.test.js | 46 ++++++++++++++++---------------- workflows/regression-testing.mdx | 4 +-- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/tests/branch-migration.test.js b/tests/branch-migration.test.js index 245ccd1..f3c00ef 100644 --- a/tests/branch-migration.test.js +++ b/tests/branch-migration.test.js @@ -2,27 +2,30 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest"; import pg from "pg"; const { Client } = pg; +const TABLE = "ardent_ci_test"; let client; beforeAll(async () => { client = new Client({ connectionString: process.env.DATABASE_URL }); await client.connect(); -}); +}, 30000); afterAll(async () => { + await client?.query(`DROP TABLE IF EXISTS ${TABLE}`).catch(() => {}); await client?.end(); -}); +}, 30000); describe("migration on Ardent branch", () => { it("connects to the branch database", async () => { const res = await client.query("SELECT 1 AS ok"); expect(res.rows[0].ok).toBe(1); - }); + }, 15000); it("runs a CREATE TABLE migration", async () => { + await client.query(`DROP TABLE IF EXISTS ${TABLE}`); await client.query(` - CREATE TABLE IF NOT EXISTS users ( + CREATE TABLE ${TABLE} ( id SERIAL PRIMARY KEY, email TEXT NOT NULL UNIQUE, created_at TIMESTAMPTZ DEFAULT now() @@ -30,50 +33,47 @@ describe("migration on Ardent branch", () => { `); const res = await client.query(` - SELECT column_name, data_type + SELECT column_name FROM information_schema.columns - WHERE table_name = 'users' + WHERE table_name = '${TABLE}' ORDER BY ordinal_position `); const columns = res.rows.map((r) => r.column_name); - expect(columns).toContain("id"); - expect(columns).toContain("email"); - expect(columns).toContain("created_at"); - }); + expect(columns).toEqual(["id", "email", "created_at"]); + }, 15000); it("inserts and reads data", async () => { await client.query( - "INSERT INTO users (email) VALUES ($1) ON CONFLICT DO NOTHING", + `INSERT INTO ${TABLE} (email) VALUES ($1)`, ["ci-test@example.com"] ); - const res = await client.query("SELECT email FROM users WHERE email = $1", [ - "ci-test@example.com", - ]); + const res = await client.query( + `SELECT email FROM ${TABLE} WHERE email = $1`, + ["ci-test@example.com"] + ); expect(res.rows.length).toBe(1); expect(res.rows[0].email).toBe("ci-test@example.com"); - }); + }, 15000); it("runs an ALTER TABLE migration", async () => { - await client.query(` - ALTER TABLE users ADD COLUMN IF NOT EXISTS name TEXT - `); + await client.query(`ALTER TABLE ${TABLE} ADD COLUMN name TEXT`); const res = await client.query(` SELECT column_name FROM information_schema.columns - WHERE table_name = 'users' AND column_name = 'name' + WHERE table_name = '${TABLE}' AND column_name = 'name' `); expect(res.rows.length).toBe(1); - }); + }, 15000); it("cleans up with DROP TABLE", async () => { - await client.query("DROP TABLE users"); + await client.query(`DROP TABLE ${TABLE}`); const res = await client.query(` SELECT table_name FROM information_schema.tables - WHERE table_name = 'users' + WHERE table_name = '${TABLE}' `); expect(res.rows.length).toBe(0); - }); + }, 15000); }); diff --git a/workflows/regression-testing.mdx b/workflows/regression-testing.mdx index c790bac..b166ed9 100644 --- a/workflows/regression-testing.mdx +++ b/workflows/regression-testing.mdx @@ -25,9 +25,7 @@ npm test ardent branch delete regression-run ``` -**One-line alternative:** `DATABASE_URL='…' npm test` sets the variable only for that one process, which is why it looked as if the URL and `npm test` had to share a line. That is valid shell, but `export` and a separate test command are easier to read and match how most people run pytest or rerun commands. - -**In CI:** create the branch in the job, assign the URL to `DATABASE_URL` (or wire it into your framework), run your test command, then delete the branch. See [CI / CD](/workflows/ci-cd) for a GitHub Actions–shaped example. +**In CI:** see [CI / CD](/workflows/ci-cd) for a full GitHub Actions workflow you can copy into your repo. ## What this unlocks From 32f135e3e4cc06d9f7f4cfdd9d195dd6c5f5b9a9 Mon Sep 17 00:00:00 2001 From: VikramChennai Date: Thu, 30 Apr 2026 10:39:03 -0700 Subject: [PATCH 07/15] docs(ci-cd): add npm ci step to match validated workflow Made-with: Cursor --- workflows/ci-cd.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/workflows/ci-cd.mdx b/workflows/ci-cd.mdx index 77c8b67..a29f546 100644 --- a/workflows/ci-cd.mdx +++ b/workflows/ci-cd.mdx @@ -31,6 +31,9 @@ jobs: with: node-version: "22" + - name: Install dependencies + run: npm ci + - name: Install Ardent CLI run: npm install -g ardent-cli From cfb689df693f2f76593035be4664e4b8b2296b51 Mon Sep 17 00:00:00 2001 From: VikramChennai Date: Thu, 30 Apr 2026 10:43:45 -0700 Subject: [PATCH 08/15] docs(ci-cd): full walkthrough with prereqs, steps, explanations, and Claude snippet Made-with: Cursor --- workflows/ci-cd.mdx | 68 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/workflows/ci-cd.mdx b/workflows/ci-cd.mdx index a29f546..f929b9c 100644 --- a/workflows/ci-cd.mdx +++ b/workflows/ci-cd.mdx @@ -5,14 +5,50 @@ description: "A fresh database for every PR. Isolated, production-like, torn dow Every PR gets its own fresh database cloned from production, torn down after merge. No shared staging that drifts out of sync. No test runs that corrupt each other. +## Prerequisites + +You need an Ardent account with at least one connector set up. If you haven't done that yet, follow the [Quickstart](/quickstart) first. + ## Setup -1. Create an API token in the [Ardent dashboard](https://app.tryardent.com) (or `POST /v1/orgs/{org_id}/api-keys`). -2. Add the token as a repository secret called **`ARDENT_TOKEN`** in **Settings > Secrets and variables > Actions**. + + -## GitHub Actions workflow +Run these locally to find the values you'll need for the workflow: + +```bash +ardent project list +ardent connector list +``` + +Note the project name and connector name. If you only have one of each, the CLI auto-selects after login and you can skip the "Select project and connector" step in the workflow below. + + + + + +Create a token in the [Ardent dashboard](https://app.tryardent.com) or with: + +```bash +curl -X POST https://api.tryardent.com/v1/orgs/{org_id}/api-keys +``` -Copy this to `.github/workflows/ardent-branch.yml`. It creates an isolated database branch for every PR, makes `DATABASE_URL` available to your tests, and cleans up after. + + + + +In your repo, go to **Settings > Secrets and variables > Actions** and click **New repository secret**. Name it **`ARDENT_TOKEN`** and paste your token. + + + + + +Copy the YAML below to `.github/workflows/ardent-branch.yml` in your repo, replacing `my-project` and `my-connector` with your values from step 1. + + + + +## GitHub Actions workflow ```yaml name: Test with Ardent branch @@ -42,8 +78,8 @@ jobs: - name: Select project and connector run: | - ardent project switch - ardent connector switch + ardent project switch my-project + ardent connector switch my-connector - name: Create database branch run: | @@ -60,11 +96,25 @@ jobs: run: ardent branch delete "$ARDENT_BRANCH" ``` -Replace `` and `` with the names from `ardent project list` and `ardent connector list`. If you only have one of each, the CLI auto-selects after login and you can skip that step. +**What each step does:** + +- **`ardent login --token`** authenticates the CLI using your repo secret. +- **`ardent project switch` / `ardent connector switch`** selects which database to branch from. Replace `my-project` and `my-connector` with your names. Skip this step if you only have one project and one connector. +- **`ardent branch create`** creates an isolated database branch. The name includes the PR number and run ID so concurrent runs and re-runs don't collide. +- **`ardent branch info | awk '/URL:/{print $2}'`** extracts the branch connection URL and writes it to `DATABASE_URL` in `GITHUB_ENV`. Your test step reads `DATABASE_URL` like any other environment variable. If your app uses a different env var name, change it in the `echo` line. +- **`npm test`** runs your test suite against the branch. Replace with `pytest`, `pnpm test`, `make test`, or whatever your project uses. +- **`ardent branch delete`** cleans up the branch. `if: always()` ensures this runs even when tests fail. -Replace `npm test` with whatever your project uses (`pytest`, `pnpm test`, `make test`, etc.). `DATABASE_URL` is available to every step after the create step. If your framework reads a different env var, change the name in the `echo` line. +## Copy this into Claude Code or Cursor -The branch name includes the PR number and run ID so concurrent runs and re-runs don't collide. `if: always()` on the delete step ensures cleanup even when tests fail. +``` +Set up Ardent database branching in our GitHub Actions CI. +I have an ARDENT_TOKEN secret already configured. +My project name is [project name] and connector is [connector name]. +Create .github/workflows/ardent-branch.yml that creates an Ardent branch +for each PR, sets DATABASE_URL for our tests, runs `npm test`, and deletes +the branch after. Use the docs at https://docs.tryardent.com/workflows/ci-cd +``` ## Why this beats a shared staging database From a17b0b8e71aa6b5109d508e8433041760df65dbf Mon Sep 17 00:00:00 2001 From: VikramChennai Date: Thu, 30 Apr 2026 10:48:39 -0700 Subject: [PATCH 09/15] docs(ci-cd): deterministic CI, dashboard-only token, connector = Postgres server Made-with: Cursor --- workflows/ci-cd.mdx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/workflows/ci-cd.mdx b/workflows/ci-cd.mdx index f929b9c..6324dda 100644 --- a/workflows/ci-cd.mdx +++ b/workflows/ci-cd.mdx @@ -21,17 +21,13 @@ ardent project list ardent connector list ``` -Note the project name and connector name. If you only have one of each, the CLI auto-selects after login and you can skip the "Select project and connector" step in the workflow below. +Note the project name and connector name. You will use these in the workflow to explicitly select which database to branch from. -Create a token in the [Ardent dashboard](https://app.tryardent.com) or with: - -```bash -curl -X POST https://api.tryardent.com/v1/orgs/{org_id}/api-keys -``` +Go to the [Ardent dashboard](https://app.tryardent.com) and create an API token. @@ -99,7 +95,7 @@ jobs: **What each step does:** - **`ardent login --token`** authenticates the CLI using your repo secret. -- **`ardent project switch` / `ardent connector switch`** selects which database to branch from. Replace `my-project` and `my-connector` with your names. Skip this step if you only have one project and one connector. +- **`ardent project switch` / `ardent connector switch`** explicitly selects which project and connector to branch from. A connector represents one Postgres server. Replace `my-project` and `my-connector` with your names. Always set these in CI so the workflow is deterministic. - **`ardent branch create`** creates an isolated database branch. The name includes the PR number and run ID so concurrent runs and re-runs don't collide. - **`ardent branch info | awk '/URL:/{print $2}'`** extracts the branch connection URL and writes it to `DATABASE_URL` in `GITHUB_ENV`. Your test step reads `DATABASE_URL` like any other environment variable. If your app uses a different env var name, change it in the `echo` line. - **`npm test`** runs your test suite against the branch. Replace with `pytest`, `pnpm test`, `make test`, or whatever your project uses. From 9a9d4f1878aa86e966779576da669f6b7d76dd30 Mon Sep 17 00:00:00 2001 From: VikramChennai Date: Thu, 30 Apr 2026 10:59:16 -0700 Subject: [PATCH 10/15] docs(ci-cd): direct link to API keys page Made-with: Cursor --- workflows/ci-cd.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflows/ci-cd.mdx b/workflows/ci-cd.mdx index 6324dda..61438b2 100644 --- a/workflows/ci-cd.mdx +++ b/workflows/ci-cd.mdx @@ -27,7 +27,7 @@ Note the project name and connector name. You will use these in the workflow to -Go to the [Ardent dashboard](https://app.tryardent.com) and create an API token. +Go to [Ardent dashboard > API keys](https://app.tryardent.com/app/settings/api-keys) and create a token. From 4a92923ac1e8e21b62e5d8d1b1edc7add3253e36 Mon Sep 17 00:00:00 2001 From: Evan Volgas Date: Mon, 1 Jun 2026 14:19:53 -0700 Subject: [PATCH 11/15] ci: harden ardent branch workflow cleanup --- .github/workflows/ardent-branch.yml | 10 ++++++---- workflows/ci-cd.mdx | 15 +++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ardent-branch.yml b/.github/workflows/ardent-branch.yml index f1408d7..1cb9791 100644 --- a/.github/workflows/ardent-branch.yml +++ b/.github/workflows/ardent-branch.yml @@ -7,6 +7,8 @@ on: jobs: test: runs-on: ubuntu-latest + env: + ARDENT_BRANCH: pr-${{ github.event.pull_request.number }}-${{ github.run_id }} steps: - uses: actions/checkout@v4 @@ -21,7 +23,9 @@ jobs: run: npm install -g ardent-cli - name: Log in to Ardent - run: ardent login --token ${{ secrets.ARDENT_TOKEN }} + env: + ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} + run: ardent login --token "$ARDENT_TOKEN" - name: Select project and connector run: | @@ -30,10 +34,8 @@ jobs: - name: Create database branch run: | - BRANCH_NAME="pr-${{ github.event.pull_request.number }}-${{ github.run_id }}" - ardent branch create "$BRANCH_NAME" + ardent branch create "$ARDENT_BRANCH" echo "DATABASE_URL=$(ardent branch info | awk '/URL:/{print $2}')" >> "$GITHUB_ENV" - echo "ARDENT_BRANCH=$BRANCH_NAME" >> "$GITHUB_ENV" - name: Run migration tests run: npm test diff --git a/workflows/ci-cd.mdx b/workflows/ci-cd.mdx index 61438b2..2d6265c 100644 --- a/workflows/ci-cd.mdx +++ b/workflows/ci-cd.mdx @@ -56,6 +56,8 @@ on: jobs: test: runs-on: ubuntu-latest + env: + ARDENT_BRANCH: pr-${{ github.event.pull_request.number }}-${{ github.run_id }} steps: - uses: actions/checkout@v4 @@ -70,7 +72,9 @@ jobs: run: npm install -g ardent-cli - name: Log in to Ardent - run: ardent login --token ${{ secrets.ARDENT_TOKEN }} + env: + ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} + run: ardent login --token "$ARDENT_TOKEN" - name: Select project and connector run: | @@ -79,10 +83,8 @@ jobs: - name: Create database branch run: | - BRANCH_NAME="pr-${{ github.event.pull_request.number }}-${{ github.run_id }}" - ardent branch create "$BRANCH_NAME" + ardent branch create "$ARDENT_BRANCH" echo "DATABASE_URL=$(ardent branch info | awk '/URL:/{print $2}')" >> "$GITHUB_ENV" - echo "ARDENT_BRANCH=$BRANCH_NAME" >> "$GITHUB_ENV" - name: Run tests run: npm test @@ -94,9 +96,10 @@ jobs: **What each step does:** -- **`ardent login --token`** authenticates the CLI using your repo secret. +- **`ardent login --token "$ARDENT_TOKEN"`** authenticates the CLI using your repo secret. Passing the secret through `env` keeps shell metacharacters in the token from being interpreted by the shell. - **`ardent project switch` / `ardent connector switch`** explicitly selects which project and connector to branch from. A connector represents one Postgres server. Replace `my-project` and `my-connector` with your names. Always set these in CI so the workflow is deterministic. -- **`ardent branch create`** creates an isolated database branch. The name includes the PR number and run ID so concurrent runs and re-runs don't collide. +- **`ARDENT_BRANCH`** defines the branch name for both create and delete steps. The name includes the PR number and run ID so concurrent runs and re-runs don't collide. +- **`ardent branch create`** creates an isolated database branch. - **`ardent branch info | awk '/URL:/{print $2}'`** extracts the branch connection URL and writes it to `DATABASE_URL` in `GITHUB_ENV`. Your test step reads `DATABASE_URL` like any other environment variable. If your app uses a different env var name, change it in the `echo` line. - **`npm test`** runs your test suite against the branch. Replace with `pytest`, `pnpm test`, `make test`, or whatever your project uses. - **`ardent branch delete`** cleans up the branch. `if: always()` ensures this runs even when tests fail. From 3d0314e88f32f33bacc6cb9ec5f32cabdc8f7d09 Mon Sep 17 00:00:00 2001 From: Evan Volgas Date: Mon, 1 Jun 2026 14:24:33 -0700 Subject: [PATCH 12/15] ci: restore ardent token login --- .github/workflows/ardent-branch.yml | 12 ++++++------ workflows/ci-cd.mdx | 17 +++++++++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ardent-branch.yml b/.github/workflows/ardent-branch.yml index 1b684de..06a71fe 100644 --- a/.github/workflows/ardent-branch.yml +++ b/.github/workflows/ardent-branch.yml @@ -22,18 +22,20 @@ jobs: - name: Install Ardent CLI run: npm install -g ardent-cli - - name: Select project and connector + - name: Log in to Ardent env: ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} + run: ardent login --token "$ARDENT_TOKEN" + + - name: Select project and connector run: | ardent project switch newer-testing ardent connector switch new-connector - name: Create database branch - env: - ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} run: | DATABASE_URL="$(ardent branch create "$ARDENT_BRANCH" --print-url)" + echo "ARDENT_BRANCH_CREATED=true" >> "$GITHUB_ENV" if [ -z "$DATABASE_URL" ]; then echo "Ardent did not return a database URL" >&2 exit 1 @@ -44,7 +46,5 @@ jobs: run: npm test - name: Delete database branch - if: always() && env.ARDENT_BRANCH != '' - env: - ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} + if: always() && env.ARDENT_BRANCH_CREATED == 'true' run: ardent branch delete "$ARDENT_BRANCH" diff --git a/workflows/ci-cd.mdx b/workflows/ci-cd.mdx index 370003d..cfc16f2 100644 --- a/workflows/ci-cd.mdx +++ b/workflows/ci-cd.mdx @@ -49,18 +49,20 @@ jobs: - name: Install Ardent CLI run: npm install -g ardent-cli - - name: Select project and connector + - name: Log in to Ardent env: ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} + run: ardent login --token "$ARDENT_TOKEN" + + - name: Select project and connector run: | ardent project switch my-project ardent connector switch my-connector - name: Create database branch - env: - ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} run: | DATABASE_URL="$(ardent branch create "$ARDENT_BRANCH" --print-url)" + echo "ARDENT_BRANCH_CREATED=true" >> "$GITHUB_ENV" if [ -z "$DATABASE_URL" ]; then echo "Ardent did not return a database URL" >&2 exit 1 @@ -71,20 +73,19 @@ jobs: run: npm test - name: Delete database branch - if: always() && env.ARDENT_BRANCH != '' - env: - ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} + if: always() && env.ARDENT_BRANCH_CREATED == 'true' run: ardent branch delete "$ARDENT_BRANCH" ``` ## Why this shape works -- `ARDENT_TOKEN` authenticates the CLI without writing an interactive login session or interpolating the secret into a shell command. It is scoped only to the Ardent CLI steps, not the test step. +- `ardent login --token "$ARDENT_TOKEN"` authenticates the CLI without interpolating the secret directly into the shell command. - `ardent project switch` and `ardent connector switch` make the workflow deterministic. - `ARDENT_BRANCH` defines the branch name before the branch is created, so cleanup does not depend on the create step writing that name to `GITHUB_ENV`. - `ardent branch create --print-url` returns only the connection URL, which is safer and cleaner than scraping human output. - The branch name includes both PR number and run ID so re-runs and concurrent jobs do not collide. -- Cleanup runs with `if: always()` so failed tests still delete the branch. +- `ARDENT_BRANCH_CREATED` makes cleanup run only after branch creation succeeds, so earlier setup failures do not try to delete a branch that never existed. +- Cleanup runs with `if: always()` after branch creation so failed tests still delete the branch. ## Other CI providers From 5e96a097ef7fef204afa4bb439b1b238eae25dec Mon Sep 17 00:00:00 2001 From: Evan Volgas Date: Mon, 1 Jun 2026 14:41:02 -0700 Subject: [PATCH 13/15] ci: harden ardent branch validation --- .github/workflows/ardent-branch.yml | 17 +++++++--- tests/branch-migration.test.js | 49 ++++++++++++++++------------- workflows/ci-cd.mdx | 21 +++++++++---- 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ardent-branch.yml b/.github/workflows/ardent-branch.yml index 06a71fe..da0e5f7 100644 --- a/.github/workflows/ardent-branch.yml +++ b/.github/workflows/ardent-branch.yml @@ -7,6 +7,7 @@ on: jobs: test: runs-on: ubuntu-latest + timeout-minutes: 15 env: ARDENT_BRANCH: pr-${{ github.event.pull_request.number }}-${{ github.run_id }} steps: @@ -28,18 +29,24 @@ jobs: run: ardent login --token "$ARDENT_TOKEN" - name: Select project and connector + env: + ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} run: | + # Internal docs validation fixture; see workflows/ci-cd.mdx for the generic workflow. ardent project switch newer-testing ardent connector switch new-connector - name: Create database branch + env: + ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} run: | DATABASE_URL="$(ardent branch create "$ARDENT_BRANCH" --print-url)" echo "ARDENT_BRANCH_CREATED=true" >> "$GITHUB_ENV" - if [ -z "$DATABASE_URL" ]; then - echo "Ardent did not return a database URL" >&2 - exit 1 - fi + case "$DATABASE_URL" in + postgres://*|postgresql://*) ;; + *) echo "Ardent did not return a Postgres database URL" >&2; exit 1 ;; + esac + echo "::add-mask::$DATABASE_URL" echo "DATABASE_URL=$DATABASE_URL" >> "$GITHUB_ENV" - name: Run migration tests @@ -47,4 +54,6 @@ jobs: - name: Delete database branch if: always() && env.ARDENT_BRANCH_CREATED == 'true' + env: + ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} run: ardent branch delete "$ARDENT_BRANCH" diff --git a/tests/branch-migration.test.js b/tests/branch-migration.test.js index f3c00ef..40f051e 100644 --- a/tests/branch-migration.test.js +++ b/tests/branch-migration.test.js @@ -7,6 +7,10 @@ const TABLE = "ardent_ci_test"; let client; beforeAll(async () => { + if (!process.env.DATABASE_URL) { + throw new Error("DATABASE_URL is required"); + } + client = new Client({ connectionString: process.env.DATABASE_URL }); await client.connect(); }, 30000); @@ -17,12 +21,10 @@ afterAll(async () => { }, 30000); describe("migration on Ardent branch", () => { - it("connects to the branch database", async () => { + it("runs a full migration lifecycle", async () => { const res = await client.query("SELECT 1 AS ok"); expect(res.rows[0].ok).toBe(1); - }, 15000); - it("runs a CREATE TABLE migration", async () => { await client.query(`DROP TABLE IF EXISTS ${TABLE}`); await client.query(` CREATE TABLE ${TABLE} ( @@ -32,18 +34,19 @@ describe("migration on Ardent branch", () => { ) `); - const res = await client.query(` + const createRes = await client.query( + ` SELECT column_name FROM information_schema.columns - WHERE table_name = '${TABLE}' + WHERE table_name = $1 ORDER BY ordinal_position - `); + `, + [TABLE] + ); - const columns = res.rows.map((r) => r.column_name); + const columns = createRes.rows.map((r) => r.column_name); expect(columns).toEqual(["id", "email", "created_at"]); - }, 15000); - it("inserts and reads data", async () => { await client.query( `INSERT INTO ${TABLE} (email) VALUES ($1)`, ["ci-test@example.com"] @@ -55,25 +58,27 @@ describe("migration on Ardent branch", () => { ); expect(res.rows.length).toBe(1); expect(res.rows[0].email).toBe("ci-test@example.com"); - }, 15000); - it("runs an ALTER TABLE migration", async () => { await client.query(`ALTER TABLE ${TABLE} ADD COLUMN name TEXT`); - const res = await client.query(` + const alterRes = await client.query( + ` SELECT column_name FROM information_schema.columns - WHERE table_name = '${TABLE}' AND column_name = 'name' - `); - expect(res.rows.length).toBe(1); - }, 15000); + WHERE table_name = $1 AND column_name = $2 + `, + [TABLE, "name"] + ); + expect(alterRes.rows.length).toBe(1); - it("cleans up with DROP TABLE", async () => { await client.query(`DROP TABLE ${TABLE}`); - const res = await client.query(` + const dropRes = await client.query( + ` SELECT table_name FROM information_schema.tables - WHERE table_name = '${TABLE}' - `); - expect(res.rows.length).toBe(0); - }, 15000); + WHERE table_name = $1 + `, + [TABLE] + ); + expect(dropRes.rows.length).toBe(0); + }, 60000); }); diff --git a/workflows/ci-cd.mdx b/workflows/ci-cd.mdx index cfc16f2..00f7b64 100644 --- a/workflows/ci-cd.mdx +++ b/workflows/ci-cd.mdx @@ -34,6 +34,7 @@ on: jobs: test: runs-on: ubuntu-latest + timeout-minutes: 15 env: ARDENT_BRANCH: pr-${{ github.event.pull_request.number }}-${{ github.run_id }} steps: @@ -55,18 +56,23 @@ jobs: run: ardent login --token "$ARDENT_TOKEN" - name: Select project and connector + env: + ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} run: | ardent project switch my-project ardent connector switch my-connector - name: Create database branch + env: + ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} run: | DATABASE_URL="$(ardent branch create "$ARDENT_BRANCH" --print-url)" echo "ARDENT_BRANCH_CREATED=true" >> "$GITHUB_ENV" - if [ -z "$DATABASE_URL" ]; then - echo "Ardent did not return a database URL" >&2 - exit 1 - fi + case "$DATABASE_URL" in + postgres://*|postgresql://*) ;; + *) echo "Ardent did not return a Postgres database URL" >&2; exit 1 ;; + esac + echo "::add-mask::$DATABASE_URL" echo "DATABASE_URL=$DATABASE_URL" >> "$GITHUB_ENV" - name: Run tests @@ -74,18 +80,21 @@ jobs: - name: Delete database branch if: always() && env.ARDENT_BRANCH_CREATED == 'true' + env: + ARDENT_TOKEN: ${{ secrets.ARDENT_TOKEN }} run: ardent branch delete "$ARDENT_BRANCH" ``` ## Why this shape works -- `ardent login --token "$ARDENT_TOKEN"` authenticates the CLI without interpolating the secret directly into the shell command. +- `ARDENT_TOKEN` is passed through `env` to Ardent CLI steps so it is not interpolated directly into shell commands or exposed to the test step. - `ardent project switch` and `ardent connector switch` make the workflow deterministic. - `ARDENT_BRANCH` defines the branch name before the branch is created, so cleanup does not depend on the create step writing that name to `GITHUB_ENV`. - `ardent branch create --print-url` returns only the connection URL, which is safer and cleaner than scraping human output. +- The workflow validates the captured URL starts with `postgres://` or `postgresql://`, then masks it before writing it to `GITHUB_ENV`. - The branch name includes both PR number and run ID so re-runs and concurrent jobs do not collide. - `ARDENT_BRANCH_CREATED` makes cleanup run only after branch creation succeeds, so earlier setup failures do not try to delete a branch that never existed. -- Cleanup runs with `if: always()` after branch creation so failed tests still delete the branch. +- `timeout-minutes` bounds external-service hangs, and cleanup runs with `if: always()` after branch creation so failed tests still delete the branch. ## Other CI providers From cb92fcbdab3a3506e4b422b5ff8f468b965b5aad Mon Sep 17 00:00:00 2001 From: Evan Volgas Date: Mon, 1 Jun 2026 14:47:23 -0700 Subject: [PATCH 14/15] ci: pin ardent cli install --- .github/workflows/ardent-branch.yml | 2 +- workflows/ci-cd.mdx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ardent-branch.yml b/.github/workflows/ardent-branch.yml index da0e5f7..ef588af 100644 --- a/.github/workflows/ardent-branch.yml +++ b/.github/workflows/ardent-branch.yml @@ -21,7 +21,7 @@ jobs: run: npm ci - name: Install Ardent CLI - run: npm install -g ardent-cli + run: npm install -g ardent-cli@0.0.46 - name: Log in to Ardent env: diff --git a/workflows/ci-cd.mdx b/workflows/ci-cd.mdx index 00f7b64..1da519d 100644 --- a/workflows/ci-cd.mdx +++ b/workflows/ci-cd.mdx @@ -48,7 +48,7 @@ jobs: run: npm ci - name: Install Ardent CLI - run: npm install -g ardent-cli + run: npm install -g ardent-cli@0.0.46 - name: Log in to Ardent env: @@ -101,7 +101,7 @@ jobs: The pattern is the same everywhere: ```bash -npm install -g ardent-cli +npm install -g ardent-cli@0.0.46 ardent project switch my-project ardent connector switch my-connector export DATABASE_URL="$(ardent branch create "$CI_BRANCH_NAME" --print-url)" From 0e1eaf79335ae05462ff8e379f2ea5507c92c5eb Mon Sep 17 00:00:00 2001 From: Evan Volgas Date: Mon, 1 Jun 2026 14:48:29 -0700 Subject: [PATCH 15/15] test: fix migration lifecycle syntax --- tests/branch-migration.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/branch-migration.test.js b/tests/branch-migration.test.js index 40f051e..f99ddca 100644 --- a/tests/branch-migration.test.js +++ b/tests/branch-migration.test.js @@ -52,12 +52,12 @@ describe("migration on Ardent branch", () => { ["ci-test@example.com"] ); - const res = await client.query( + const selectRes = await client.query( `SELECT email FROM ${TABLE} WHERE email = $1`, ["ci-test@example.com"] ); - expect(res.rows.length).toBe(1); - expect(res.rows[0].email).toBe("ci-test@example.com"); + expect(selectRes.rows.length).toBe(1); + expect(selectRes.rows[0].email).toBe("ci-test@example.com"); await client.query(`ALTER TABLE ${TABLE} ADD COLUMN name TEXT`);