From b73fb48176a181df7db380e6c425cf26a3b547a9 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Thu, 30 Jul 2026 23:15:37 -0600 Subject: [PATCH 1/7] Adopt cached Nx CI and automated releases --- .changeset/README.md | 11 + .changeset/config.json | 33 + .github/actions/setup/action.yml | 41 + .../workflows/chart-library-benchmarks.yml | 230 +- .github/workflows/release.yml | 159 +- .gitignore | 2 + .nvmrc | 1 + API-FRICTION.md | 62 + CONTRIBUTING.md | 93 + README.md | 5 +- nx.json | 179 ++ package.json | 34 +- pnpm-lock.yaml | 2553 ++++++++++++++++- pnpm-workspace.yaml | 3 + project.json | 169 ++ scripts/benchmark/filters.mjs | 23 + scripts/benchmark/filters.test.mjs | 25 +- scripts/check-framework-adapters.mjs | 61 +- scripts/ci-workflow.test.mjs | 307 ++ scripts/compare-chart-libraries.mjs | 120 +- scripts/compare-plot-catalog.mjs | 29 +- scripts/publish-release.mjs | 50 +- scripts/release-package-config.mjs | 14 +- scripts/release-package-config.test.mjs | 20 + scripts/release-security.mjs | 6 +- scripts/release-security.test.mjs | 10 + scripts/release-status.mjs | 232 ++ scripts/release-status.test.mjs | 90 + scripts/release-workflow.test.mjs | 241 +- scripts/sync-release-changelog.mjs | 99 + scripts/sync-release-changelog.test.mjs | 117 + scripts/verify-ci-success.mjs | 57 + scripts/verify-ci-success.test.mjs | 28 + scripts/vitest-config.test.mjs | 11 + scripts/write-release-notes.mjs | 18 + vitest.config.ts | 3 +- 36 files changed, 4830 insertions(+), 306 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .github/actions/setup/action.yml create mode 100644 .nvmrc create mode 100644 CONTRIBUTING.md create mode 100644 nx.json create mode 100644 project.json create mode 100644 scripts/ci-workflow.test.mjs create mode 100644 scripts/release-package-config.test.mjs create mode 100644 scripts/release-status.mjs create mode 100644 scripts/release-status.test.mjs create mode 100644 scripts/sync-release-changelog.mjs create mode 100644 scripts/sync-release-changelog.test.mjs create mode 100644 scripts/verify-ci-success.mjs create mode 100644 scripts/verify-ci-success.test.mjs create mode 100644 scripts/vitest-config.test.mjs create mode 100644 scripts/write-release-notes.mjs diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..b6e6944 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,11 @@ +# Changesets + +Add a changeset for every user-visible package change: + +```sh +pnpm changeset +``` + +All ten public Charts packages release as one fixed version. Documentation, +tests, benchmarks, and build-only changes do not need a changeset unless they +change the published package contract. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..d5a0f07 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json", + "changelog": [ + "@changesets/changelog-github", + { + "repo": "TanStack/charts", + "disableThanks": true + } + ], + "commit": false, + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "fixed": [ + [ + "@tanstack/charts", + "@tanstack/react-charts", + "@tanstack/octane-charts", + "@tanstack/preact-charts", + "@tanstack/vue-charts", + "@tanstack/solid-charts", + "@tanstack/svelte-charts", + "@tanstack/angular-charts", + "@tanstack/lit-charts", + "@tanstack/alpine-charts" + ] + ], + "linked": [], + "ignore": [], + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "onlyUpdatePeerDependentsWhenOutOfRange": true + } +} diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..166675c --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,41 @@ +name: Setup Charts +description: Install the workspace and restore local Nx and Playwright caches + +inputs: + playwright: + description: Install the Chromium runtime used by browser checks + required: false + default: 'false' + +runs: + using: composite + steps: + - name: Setup tools + uses: TanStack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 + + - name: Restore Nx cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7d # v5.0.4 + with: + path: .nx/cache + key: ${{ runner.os }}-nx-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.job }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-nx-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.job }}- + ${{ runner.os }}-nx-${{ hashFiles('pnpm-lock.yaml') }}- + + - name: Restore Playwright + if: inputs.playwright == 'true' + id: playwright-cache + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7d # v5.0.4 + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-1.62.0 + + - name: Install Chromium + if: inputs.playwright == 'true' && steps.playwright-cache.outputs.cache-hit != 'true' + shell: bash + run: pnpm exec playwright install --only-shell chromium + + - name: Install Chromium system dependencies + if: inputs.playwright == 'true' + shell: bash + run: pnpm exec playwright install-deps chromium diff --git a/.github/workflows/chart-library-benchmarks.yml b/.github/workflows/chart-library-benchmarks.yml index e1f5f80..b40c6a8 100644 --- a/.github/workflows/chart-library-benchmarks.yml +++ b/.github/workflows/chart-library-benchmarks.yml @@ -23,46 +23,34 @@ concurrency: cancel-in-progress: true jobs: - validate: + static: + name: Static checks runs-on: ubuntu-24.04 - timeout-minutes: 20 + timeout-minutes: 15 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - - run: corepack enable + - name: Setup + uses: ./.github/actions/setup - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Set affected revisions + if: github.event_name == 'pull_request' + uses: nrwl/nx-set-shas@3e9ad7370203c1e93d109be57f3b72eb0eb511b1 # v4.4.0 with: - node-version: 24.18.0 - cache: pnpm - - - run: pnpm install --frozen-lockfile - - run: pnpm format:check - - run: pnpm docs:check - - run: pnpm typecheck - - run: pnpm test - - run: pnpm package:check - - run: pnpm bundle:check - - name: Check comparison bundle baseline - id: benchmark-check - run: pnpm benchmark:check + main-branch-name: main - - name: Upload comparison bundle baseline candidate - if: ${{ always() && steps.benchmark-check.outcome != 'skipped' && (steps.benchmark-check.outcome == 'failure' || inputs.upload_bundle_baseline_candidate) }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: comparison-bundle-baseline-${{ github.sha }} - path: .benchmark-output/results/bundle-baseline.candidate.json - if-no-files-found: error + - name: Run affected checks + if: github.event_name == 'pull_request' + run: pnpm ci:pr - - run: pnpm catalog:check - - run: pnpm catalog:build - - run: pnpm catalog:loading:check - - run: git diff --check + - name: Run all checks + if: github.event_name != 'pull_request' + run: pnpm ci:all - name: Upload production catalog if: github.event_name == 'push' && github.ref == 'refs/heads/main' @@ -73,102 +61,160 @@ jobs: if-no-files-found: error include-hidden-files: true - compare: - needs: validate + bundle-baseline: + name: Bundle baseline runs-on: ubuntu-24.04 - timeout-minutes: 30 + timeout-minutes: 10 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + fetch-depth: 0 persist-credentials: false - - run: corepack enable + - name: Setup + uses: ./.github/actions/setup + + - name: Check comparison bundle baseline + id: benchmark-check + run: pnpm benchmark:check - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Upload comparison bundle baseline candidate + if: ${{ always() && steps.benchmark-check.outcome != 'skipped' && (steps.benchmark-check.outcome == 'failure' || inputs.upload_bundle_baseline_candidate) }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - node-version: 24.18.0 - cache: pnpm + name: comparison-bundle-baseline-${{ github.sha }} + path: .benchmark-output/results/bundle-baseline.candidate.json + if-no-files-found: error - - run: pnpm install --frozen-lockfile - - run: pnpm exec playwright install --with-deps chromium - - run: pnpm benchmark -- --profile=ci - - run: cat .benchmark-output/results/comparison.md >> "$GITHUB_STEP_SUMMARY" + compare: + name: Comparison (${{ matrix.chart }}) + runs-on: ubuntu-24.04 + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + chart: + - line + - bar + - area + - scatter + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup + uses: ./.github/actions/setup + with: + playwright: 'true' + + - name: Run comparison shard + run: pnpm benchmark -- --profile=ci --chart=${{ matrix.chart }} + + - name: Publish comparison summary + if: success() + run: cat .benchmark-output/results/*.md >> "$GITHUB_STEP_SUMMARY" - name: Upload comparison if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: chart-library-comparison-${{ github.run_id }} + name: chart-library-comparison-${{ matrix.chart }}-${{ github.run_id }} path: .benchmark-output/results if-no-files-found: error conformance: - needs: validate + name: Conformance (${{ matrix.shard }}/8) runs-on: ubuntu-24.04 - timeout-minutes: 45 + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + shard: + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - run: corepack enable - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Setup + uses: ./.github/actions/setup with: - node-version: 24.18.0 - cache: pnpm + playwright: 'true' - - run: pnpm install --frozen-lockfile - - run: pnpm exec playwright install --with-deps chromium - - name: Run pull-request conformance + - name: Run pull-request conformance shard if: github.event_name == 'pull_request' - run: pnpm conformance:quick - - name: Run production conformance + run: pnpm conformance:quick -- --shard=${{ matrix.shard }}/8 + + - name: Run production conformance shard if: github.event_name != 'pull_request' - run: pnpm conformance - - run: cat .benchmark-output/conformance/results/plot-catalog.md >> "$GITHUB_STEP_SUMMARY" + run: pnpm conformance -- --shard=${{ matrix.shard }}/8 + + - name: Publish conformance summary + if: success() + run: cat .benchmark-output/conformance/results/*.md >> "$GITHUB_STEP_SUMMARY" - name: Upload conformance if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: chart-library-conformance-${{ github.run_id }} + name: chart-library-conformance-${{ matrix.shard }}-${{ github.run_id }} path: | .benchmark-output/conformance/results .benchmark-output/conformance/screenshots if-no-files-found: error stress: - needs: validate + name: Stress (${{ matrix.name }}) runs-on: ubuntu-24.04 - timeout-minutes: 45 + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + include: + - name: raw + workloads: raw-line,raw-scatter,interactive-scatter + - name: envelopes + workloads: binned-density,pixel-envelope,viewport-envelope + - name: transforms + workloads: stats-multi-series-line,rolling-keyed-window,histogram-128 + - name: collections + workloads: top-categories,dashboard-lines steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - run: corepack enable - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Setup + uses: ./.github/actions/setup with: - node-version: 24.18.0 - cache: pnpm + playwright: 'true' - - run: pnpm install --frozen-lockfile - name: Validate stress workload invariants run: pnpm exec vitest run benchmarks/comparison/stress - - run: pnpm exec playwright install --with-deps chromium - - name: Run quick stress profile + - name: Run quick stress shard if: github.event_name == 'pull_request' || github.event_name == 'push' - run: pnpm benchmark:stress:quick + run: pnpm benchmark:stress:quick -- --workload=${{ matrix.workloads }} - - name: Run standard stress profile + - name: Run standard stress shard if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - run: pnpm benchmark:stress:standard + run: pnpm benchmark:stress:standard -- --workload=${{ matrix.workloads }} - name: Publish stress summary if: success() @@ -178,15 +224,46 @@ jobs: if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: chart-library-stress-${{ github.run_id }} + name: chart-library-stress-${{ matrix.name }}-${{ github.run_id }} path: .benchmark-output/stress/results if-no-files-found: error + ci: + name: CI + if: always() + needs: + - static + - bundle-baseline + - compare + - conformance + - stress + runs-on: ubuntu-24.04 + timeout-minutes: 5 + + steps: + - name: Require every CI partition + env: + STATIC_RESULT: ${{ needs.static.result }} + BUNDLE_RESULT: ${{ needs.bundle-baseline.result }} + COMPARISON_RESULT: ${{ needs.compare.result }} + CONFORMANCE_RESULT: ${{ needs.conformance.result }} + STRESS_RESULT: ${{ needs.stress.result }} + run: | + for result in \ + "$STATIC_RESULT" \ + "$BUNDLE_RESULT" \ + "$COMPARISON_RESULT" \ + "$CONFORMANCE_RESULT" \ + "$STRESS_RESULT" + do + test "$result" = success + done + publish-catalog: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: - - validate - - conformance + - static + - ci runs-on: ubuntu-24.04 timeout-minutes: 10 concurrency: @@ -196,7 +273,8 @@ jobs: contents: write steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b1155a0..7852a7e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,45 +4,138 @@ on: push: tags: - 'v*' + workflow_dispatch: + workflow_run: + workflows: + - Chart library benchmarks + types: + - completed permissions: contents: read concurrency: - group: charts-release-${{ github.ref }} + group: charts-release cancel-in-progress: false jobs: + version: + name: Version pull request + if: >- + github.event_name == 'workflow_run' && + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'main' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout validated main + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.workflow_run.head_sha }} + fetch-depth: 0 + persist-credentials: true + + - name: Setup + uses: ./.github/actions/setup + + - name: Create or update version pull request + uses: changesets/action@63a615b9cd06ba9a3e6d13796c7fbcb080a60a0b # v1.8.0 + with: + branch: main + version: pnpm changeset:version + title: 'ci: Version Packages' + commit: 'ci: changeset release' + createGithubReleases: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + tag: + name: Dispatch validated release + needs: version + if: >- + github.event_name == 'workflow_run' && + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'main' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + actions: write + contents: write + + steps: + - name: Checkout validated main + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.workflow_run.head_sha }} + fetch-depth: 0 + persist-credentials: true + + - name: Setup + uses: ./.github/actions/setup + + - name: Determine release status + id: status + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_REVISION: ${{ github.event.workflow_run.head_sha }} + run: node scripts/release-status.mjs + + - name: Create annotated release tag + if: steps.status.outputs.create_tag == 'true' + env: + RELEASE_TAG: ${{ steps.status.outputs.tag }} + RELEASE_REVISION: ${{ github.event.workflow_run.head_sha }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "$RELEASE_TAG" "$RELEASE_REVISION" -m "TanStack Charts $RELEASE_TAG" + git push origin "refs/tags/$RELEASE_TAG" + + - name: Dispatch tag-scoped OIDC release + if: steps.status.outputs.dispatch == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ steps.status.outputs.tag }} + run: gh workflow run release.yml --ref "$RELEASE_TAG" + validate: + name: Build release candidate + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-24.04 - timeout-minutes: 30 + timeout-minutes: 15 permissions: + actions: read contents: read steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Checkout tagged revision + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.sha }} fetch-depth: 0 persist-credentials: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: 24.18.0 - registry-url: https://registry.npmjs.org - cache: '' + - name: Setup + uses: ./.github/actions/setup + + - name: Verify tag and successful exact-SHA CI + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + node scripts/verify-release-revision.mjs + node scripts/verify-ci-success.mjs - - name: Verify tagged main revision - run: node scripts/verify-release-revision.mjs + - name: Build fresh package artifacts + run: node scripts/build-release-artifacts.mjs - - run: corepack enable - - run: pnpm install --frozen-lockfile - - run: pnpm format:check - - run: pnpm docs:check - - run: pnpm typecheck - - run: pnpm test - - run: pnpm release:artifacts - - run: pnpm release:check + - name: Check release artifacts + run: node scripts/publish-release.mjs --check - name: Upload checked package artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 @@ -53,21 +146,25 @@ jobs: include-hidden-files: true publish: + name: Publish with npm OIDC needs: validate + if: needs.validate.result == 'success' runs-on: ubuntu-24.04 - timeout-minutes: 30 + timeout-minutes: 15 permissions: contents: read id-token: write steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Checkout tagged revision + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.sha }} fetch-depth: 0 persist-credentials: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 24.18.0 registry-url: https://registry.npmjs.org @@ -85,19 +182,22 @@ jobs: node scripts/publish-release.mjs verify: + name: Verify published packages needs: publish runs-on: ubuntu-24.04 - timeout-minutes: 30 + timeout-minutes: 15 permissions: contents: read steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Checkout tagged revision + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.sha }} persist-credentials: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 24.18.0 registry-url: https://registry.npmjs.org @@ -113,19 +213,23 @@ jobs: run: node scripts/verify-published-release.mjs release: + name: Create GitHub release needs: verify runs-on: ubuntu-24.04 + timeout-minutes: 10 permissions: contents: write steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Checkout tagged revision + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.sha }} fetch-depth: 0 persist-credentials: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 24.18.0 cache: '' @@ -138,8 +242,9 @@ jobs: if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then exit 0 fi + node scripts/write-release-notes.mjs /tmp/charts-release-notes.md gh release create "$GITHUB_REF_NAME" \ --repo "$GITHUB_REPOSITORY" \ --verify-tag \ --title "TanStack Charts $GITHUB_REF_NAME" \ - --notes-file CHANGELOG.md + --notes-file /tmp/charts-release-notes.md diff --git a/.gitignore b/.gitignore index e16f3b3..075a927 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ dist .benchmark-output .catalog-artifact .release-artifacts +.nx/cache +.nx/workspace-data coverage *.log tanstack.com-parity/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..ca5c350 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24.18.0 diff --git a/API-FRICTION.md b/API-FRICTION.md index 70e42d5..152beb7 100644 --- a/API-FRICTION.md +++ b/API-FRICTION.md @@ -176,6 +176,9 @@ Each entry records: | F-138 | Publisher pin predated explicit trust permissions | Tooling/Release | resolved | | F-139 | Top-level entries bypassed tarball validation | Tooling/Release | resolved | | F-140 | Behavior config could erase responsive datum inference | API | monitoring | +| F-141 | Vitest followed pnpm workspace symlinks | Tooling | resolved | +| F-142 | Package verification reinstalled during release builds | Tooling/Release | resolved | +| F-143 | The `ci` script name collided with pnpm's clean install | Tooling/Docs | resolved | ## Findings @@ -3394,3 +3397,62 @@ Each entry records: - Follow-up: add a focused type regression for the single-call form and decide whether its overload can retain builder datum inference without making behavior ownership ambiguous. + +### F-141 — Vitest followed pnpm workspace symlinks + +- Status: resolved +- Severity: high +- Owner: Tooling +- Observed in: Nx and CI migration after the `0.0.1` release +- Friction: the root Vitest configuration replaced the default exclusion list + when it excluded framework-owned suites. Vitest therefore followed pnpm + workspace links through `node_modules` and ran the same physical tests under + packages and examples. CI reported 464 files and 2,989 tests even though the + intended root suite contained 106 files and 537 tests. That one invocation + consumed 206 seconds. +- Decision: extend `configDefaults.exclude` before adding the framework-specific + exclusions. Keep the seven framework environments as independent Nx targets + so they run in parallel and cache independently. +- Verification: the corrected direct root suite completes in 4.92 seconds. + The cold seven-target Nx unit graph completes in 6.90 seconds, and an + unchanged warm local-cache run completes in 0.54 seconds. + +### F-142 — Package verification reinstalled dependencies during release builds + +- Status: resolved +- Severity: high +- Owner: Tooling/Release +- Observed in: validating the fresh release-artifact path during the Nx and CI + migration +- Friction: pnpm's dependency verification treated newly generated adapter + `dist` files as a stale workspace and started a clean install when the + builder later called `pnpm exec` or `pnpm pack`. With the packed-consumer and + framework builders running together, that reinstall removed root package + links while the packed consumer was bundling. Its installed + `@tanstack/charts` tarball could no longer resolve `d3-array` or `d3-scale`. + The cached package gate did not reproduce the release-only failure. +- Decision: disable `verifyDepsBeforeRun` because setup already performs one + frozen install before every CI job. Run the two top-level release builders + sequentially and serialize every nested pnpm command through one + framework-builder queue. Independent in-process adapter builds and post-pack + checks retain four-worker pools. +- Verification: a frozen install restores the workspace graph; the uncached + release-artifact command builds and validates all ten tarballs; and the + artifact-only publisher check accepts the resulting manifest and integrity + records. + +### F-143 — The `ci` script name collided with pnpm's clean install + +- Status: resolved +- Severity: high +- Owner: Tooling/Documentation +- Observed in: validating the documented local Nx command +- Friction: pnpm 11 reserves `pnpm ci` for its clean-install command, so the + root `"ci"` package script was not reachable through the documented + shorthand. Running the command removed `node_modules` instead of executing + the Nx validation graph. +- Decision: expose the local graph as `pnpm run validate`. Keep the unambiguous + `ci:pr` and `ci:all` scripts for GitHub Actions. +- Verification: `pnpm run validate` resolves the `charts-workspace:ci` Nx + target, while contributor docs no longer instruct maintainers to invoke + pnpm's clean-install command. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c8dcfc3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,93 @@ +# Contributing + +## Setup + +Use the Node.js version in [`.nvmrc`](./.nvmrc) and run commands from the +repository root. + +```sh +corepack enable +pnpm install +``` + +Workspace packages and examples use local `workspace:*` dependencies. Do not +install dependencies from an individual package or example. + +## Validation + +CI checks are Nx targets. Nx caches deterministic results in `.nx/cache`, so +unchanged local tasks and later CI runs can replay them. + +```sh +pnpm run validate +``` + +Run a narrower target while developing: + +```sh +pnpm test +pnpm typecheck +pnpm docs:check +pnpm package:check +pnpm bundle:check +``` + +Pull-request CI runs the cached Nx target graph, the locked comparison bundle +gate, and these uncached browser matrices in parallel: + +- four chart-library comparison shards; +- eight conformance shards; +- four stress-workload shards. + +Browser measurements and the revision-stamped catalog artifact are not cached +because their results depend on the browser environment or exact Git commit. +Install Chromium before running a browser suite locally: + +```sh +pnpm browser:install +pnpm conformance:quick -- --shard=1/8 +pnpm benchmark -- --profile=ci --chart=line +pnpm benchmark:stress:quick -- --workload=raw-line +``` + +## Changesets + +Add and commit a changeset for every user-visible package change: + +```sh +pnpm changeset +``` + +Choose the release impact and write the summary that should appear in the +changelog. All ten public Charts packages form one fixed release group, so one +package change advances every package to the same version. Documentation, +tests, benchmarks, and build-only changes do not need a changeset unless they +alter the published package contract. + +Do not edit package versions, generated package changelogs, or release tags by +hand. The automated version pull request owns those changes. + +## Release flow + +After a push to `main` passes the complete CI workflow: + +1. Changesets creates or updates `ci: Version Packages` from the pending + changesets. +2. Merging that pull request updates all public package versions, consumes the + changesets, synchronizes the root changelog, and updates the lockfile. +3. CI validates the exact merge commit. The release workflow then creates an + annotated version tag when that version is not already released. +4. The tag-scoped workflow rebuilds and checks all ten package artifacts. +5. Only the publish job receives `id-token: write`. npm trusted publishing uses + that OIDC identity to publish with provenance; the repository does not use a + long-lived npm token. +6. A separate job installs the published packages and verifies their + integrity, signatures, and provenance before GitHub creates the release. + +Never move or reuse a release tag. If publishing succeeds but final +verification or GitHub release creation fails, rerun the release workflow +against the existing tag. + +Changing the repository name or +`.github/workflows/release.yml` requires updating the trusted-publisher +configuration for all ten packages on npm. diff --git a/README.md b/README.md index 764830a..fb013ad 100644 --- a/README.md +++ b/README.md @@ -272,10 +272,13 @@ tracked in [`API-FRICTION.md`](./API-FRICTION.md). ## Development -The workspace requires Node.js 22 or newer and pnpm 11. +The workspace requires Node.js 22 or newer and pnpm 11. Use the version in +`.nvmrc`. See [Contributing](./CONTRIBUTING.md) for the Nx CI graph, changeset +requirements, and automated OIDC release flow. ```sh pnpm install +pnpm run validate pnpm test pnpm typecheck pnpm build diff --git a/nx.json b/nx.json new file mode 100644 index 0000000..44671a4 --- /dev/null +++ b/nx.json @@ -0,0 +1,179 @@ +{ + "$schema": "./node_modules/nx/schemas/nx-schema.json", + "defaultBase": "main", + "useInferencePlugins": false, + "parallel": 5, + "tui": { + "enabled": false + }, + "namedInputs": { + "sharedGlobals": [ + "{workspaceRoot}/.nvmrc", + "{workspaceRoot}/package.json", + "{workspaceRoot}/pnpm-lock.yaml", + "{workspaceRoot}/pnpm-workspace.yaml", + "{workspaceRoot}/tsconfig.json", + { "env": "CI" } + ], + "source": [ + "sharedGlobals", + "{workspaceRoot}/packages/**/*", + "{workspaceRoot}/examples/**/*", + "{workspaceRoot}/benchmarks/**/*", + "{workspaceRoot}/scripts/**/*", + "{workspaceRoot}/vitest*.config.ts", + "!{workspaceRoot}/**/dist/**/*" + ], + "format": [ + "{workspaceRoot}/**/*", + "!{workspaceRoot}/.benchmark-output/**/*", + "!{workspaceRoot}/.bundle-output/**/*", + "!{workspaceRoot}/.catalog-artifact/**/*", + "!{workspaceRoot}/.nx/**/*", + "!{workspaceRoot}/.release-artifacts/**/*", + "!{workspaceRoot}/node_modules/**/*" + ], + "docs": [ + "sharedGlobals", + "{workspaceRoot}/README.md", + "{workspaceRoot}/CONTRIBUTING.md", + "{workspaceRoot}/CHANGELOG.md", + "{workspaceRoot}/llms.txt", + "{workspaceRoot}/docs/**/*", + "{workspaceRoot}/packages/*/README.md", + "{workspaceRoot}/packages/*/package.json", + "{workspaceRoot}/packages/*/src/**/*", + "{workspaceRoot}/packages/charts-core/docs/**/*", + "{workspaceRoot}/packages/charts-core/llms.txt", + "{workspaceRoot}/benchmarks/conformance/**/*", + "{workspaceRoot}/benchmarks/comparison/bundle-baseline.json", + "{workspaceRoot}/scripts/benchmark/chart-libraries.mjs", + "{workspaceRoot}/scripts/benchmark/comparison-capabilities.mjs", + "{workspaceRoot}/scripts/check-docs.mjs", + "{workspaceRoot}/scripts/docs-contract.mjs", + "{workspaceRoot}/scripts/packed-markdown-links.mjs", + "{workspaceRoot}/scripts/sync-package-docs.mjs" + ], + "release": [ + "sharedGlobals", + "{workspaceRoot}/LICENSE", + "{workspaceRoot}/README.md", + "{workspaceRoot}/docs/**/*", + "{workspaceRoot}/packages/charts-core/**/*", + "{workspaceRoot}/packages/*-charts/**/*", + "!{workspaceRoot}/packages/*/dist/**/*", + "{workspaceRoot}/scripts/*package*.mjs", + "{workspaceRoot}/scripts/*release*.mjs", + "{workspaceRoot}/scripts/check-framework-adapters.mjs", + "{workspaceRoot}/scripts/check-packed-consumers.mjs", + "{workspaceRoot}/scripts/packed-markdown-links.mjs" + ], + "testSolid": [ + "sharedGlobals", + "{workspaceRoot}/vitest.solid*.config.ts", + "{workspaceRoot}/packages/charts-core/**/*", + "{workspaceRoot}/packages/solid-charts/**/*", + "!{workspaceRoot}/packages/*/dist/**/*" + ], + "testSvelte": [ + "sharedGlobals", + "{workspaceRoot}/vitest.svelte*.config.ts", + "{workspaceRoot}/packages/charts-core/**/*", + "{workspaceRoot}/packages/svelte-charts/**/*", + "!{workspaceRoot}/packages/*/dist/**/*" + ], + "testOctane": [ + "sharedGlobals", + "{workspaceRoot}/vitest.octane*.config.ts", + "{workspaceRoot}/packages/charts-core/**/*", + "{workspaceRoot}/packages/octane/**/*", + "{workspaceRoot}/packages/octane-charts/**/*", + "!{workspaceRoot}/packages/*/dist/**/*" + ], + "comparisonRevision": [ + { + "runtime": "git log -1 --format=%H -- benchmarks/comparison/libraries/tanstack packages/charts-core/src benchmarks/comparison/libraries/tier.ts benchmarks/comparison/stress/operation.ts benchmarks/comparison/types.ts" + } + ] + }, + "targetDefaults": { + "format-check": { + "cache": true, + "inputs": ["format"] + }, + "docs-check": { + "cache": true, + "inputs": ["docs"] + }, + "typecheck": { + "cache": true, + "inputs": ["source"] + }, + "test-*": { + "cache": true, + "inputs": ["source"] + }, + "test-solid": { + "cache": true, + "inputs": ["testSolid"] + }, + "test-solid-ssr": { + "cache": true, + "inputs": ["testSolid"] + }, + "test-svelte": { + "cache": true, + "inputs": ["testSvelte"] + }, + "test-svelte-ssr": { + "cache": true, + "inputs": ["testSvelte"] + }, + "test-octane": { + "cache": true, + "inputs": ["testOctane"] + }, + "test-octane-client": { + "cache": true, + "inputs": ["testOctane"] + }, + "adapters-check": { + "cache": true, + "inputs": ["release"] + }, + "package-check": { + "cache": true, + "inputs": ["release"] + }, + "bundle": { + "cache": true, + "inputs": ["source"], + "outputs": ["{workspaceRoot}/.bundle-output"] + }, + "bundle-check": { + "cache": true, + "inputs": ["source"], + "outputs": ["{workspaceRoot}/.bundle-output"] + }, + "benchmark-check": { + "cache": true, + "inputs": ["source", "comparisonRevision"], + "outputs": ["{workspaceRoot}/.benchmark-output/results"] + }, + "catalog-check": { + "cache": true, + "inputs": ["source"] + }, + "catalog-build": { + "cache": false + }, + "catalog-loading-check": { + "cache": false + }, + "release-artifacts": { + "cache": true, + "inputs": ["release"], + "outputs": ["{workspaceRoot}/.release-artifacts"] + } + } +} diff --git a/package.json b/package.json index 7db1474..feab633 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,11 @@ "node": ">=22" }, "scripts": { + "validate": "nx run charts-workspace:ci --parallel=5", + "ci:all": "nx run-many --all --target=ci --parallel=5", + "ci:pr": "nx affected --target=ci --parallel=5", "benchmark": "node scripts/compare-chart-libraries.mjs", - "benchmark:check": "node scripts/compare-chart-libraries.mjs --check", + "benchmark:check": "nx run charts-workspace:benchmark-check", "benchmark:perf": "node scripts/compare-chart-libraries.mjs --perf-only", "benchmark:size": "node scripts/compare-chart-libraries.mjs --size-only", "benchmark:stress": "node scripts/stress-chart-libraries.mjs", @@ -19,14 +22,14 @@ "benchmark:update-baseline": "node scripts/compare-chart-libraries.mjs --update-baseline", "browser:install": "playwright install --only-shell chromium", "build": "pnpm --filter './examples/*' build", - "adapters:check": "node scripts/check-framework-adapters.mjs", - "bundle": "node scripts/measure-bundles.mjs", - "bundle:check": "node scripts/measure-bundles.mjs --check", + "adapters:check": "nx run charts-workspace:adapters-check", + "bundle": "nx run charts-workspace:bundle", + "bundle:check": "nx run charts-workspace:bundle-check", "bundle:update-baseline": "node scripts/measure-bundles.mjs --update-baseline", "canvas:check": "node scripts/check-canvas-renderer.mjs", - "catalog:build": "CATALOG_BASE_PATH=/charts/catalog/ pnpm --filter @charts-poc/conformance-example build", - "catalog:check": "pnpm --filter @charts-poc/conformance-example check:catalog", - "catalog:loading:check": "node scripts/check-catalog-loading.mjs", + "catalog:build": "nx run charts-workspace:catalog-build", + "catalog:check": "nx run charts-workspace:catalog-check", + "catalog:loading:check": "nx run charts-workspace:catalog-loading-check", "dev:charts-react": "pnpm --filter @charts-poc/react-example dev", "dev:charts-octane": "pnpm --filter @charts-poc/octane-example dev", "dev:conformance": "pnpm --filter @charts-poc/conformance-example dev", @@ -35,10 +38,10 @@ "dev:sandbox": "pnpm --filter @charts-poc/sandbox dev", "demo-data:check": "node scripts/sync-demo-data.mjs --check", "demo-data:sync": "node scripts/sync-demo-data.mjs", - "docs:check": "node scripts/check-docs.mjs", + "docs:check": "nx run charts-workspace:docs-check", "docs:sync": "node scripts/sync-package-docs.mjs", "format": "prettier --write .", - "format:check": "prettier --check .", + "format:check": "nx run charts-workspace:format-check", "conformance": "node scripts/compare-plot-catalog.mjs", "conformance:ai:prepare": "node scripts/evaluate-chart-authoring.mjs prepare", "conformance:ai:run": "node scripts/evaluate-chart-authoring.mjs run", @@ -46,14 +49,18 @@ "conformance:quick": "node scripts/compare-plot-catalog.mjs --profile=quick", "conformance:size": "node scripts/compare-plot-catalog.mjs --size-only", "performance": "node scripts/measure-rendering.mjs", - "package:check": "node scripts/check-packed-consumers.mjs && pnpm adapters:check", - "release:artifacts": "node scripts/build-release-artifacts.mjs", + "package:check": "nx run charts-workspace:package-check", + "release:artifacts": "nx run charts-workspace:release-artifacts", "release:check": "node scripts/publish-release.mjs --check", "release:publish": "node scripts/publish-release.mjs", - "test": "vitest run && vitest run --config vitest.solid.config.ts && vitest run --config vitest.solid-ssr.config.ts && vitest run --config vitest.svelte.config.ts && vitest run --config vitest.svelte-ssr.config.ts && vitest run --config vitest.octane.config.ts && vitest run --config vitest.octane-client.config.ts", - "typecheck": "tsc --noEmit -p tsconfig.json" + "test": "nx run charts-workspace:test-unit --parallel=5", + "typecheck": "nx run charts-workspace:typecheck", + "changeset": "changeset", + "changeset:version": "changeset version && node scripts/sync-release-changelog.mjs && pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile && pnpm format" }, "devDependencies": { + "@changesets/changelog-github": "0.7.0", + "@changesets/cli": "2.31.1", "@charts-poc/demo-data": "workspace:*", "@observablehq/plot": "0.6.17", "@observablehq/sample-datasets": "1.0.1", @@ -114,6 +121,7 @@ "echarts": "6.1.0", "esbuild": "^0.27.0", "jsdom": "^29.0.2", + "nx": "22.7.5", "octane": "0.1.13", "playwright": "1.62.0", "prettier": "^3.8.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7da4a9b..8cb263a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,12 @@ settings: importers: .: devDependencies: + '@changesets/changelog-github': + specifier: 0.7.0 + version: 0.7.0 + '@changesets/cli': + specifier: 2.31.1 + version: 2.31.1(@types/node@25.9.5) '@charts-poc/demo-data': specifier: workspace:* version: link:packages/charts-demo-data @@ -30,7 +36,7 @@ importers: version: link:packages/react '@sveltejs/vite-plugin-svelte': specifier: ^7.1.2 - version: 7.2.0(svelte@5.56.8)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 7.2.0(svelte@5.56.8)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) '@tanstack/charts': specifier: workspace:* version: link:packages/charts-core @@ -117,7 +123,7 @@ importers: version: 1.0.5 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.4(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 6.0.4(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) chart.js: specifier: 4.5.1 version: 4.5.1 @@ -187,9 +193,12 @@ importers: jsdom: specifier: ^29.0.2 version: 29.1.1(@noble/hashes@2.2.0) + nx: + specifier: 22.7.5 + version: 22.7.5(debug@4.4.3(supports-color@10.2.2)) octane: specifier: 0.1.13 - version: 0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) playwright: specifier: 1.62.0 version: 1.62.0 @@ -225,13 +234,13 @@ importers: version: 3.0.1 vite: specifier: ^8.0.16 - version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) vite-plugin-solid: specifier: ^2.11.12 - version: 2.11.14(solid-js@1.9.14)(supports-color@10.2.2)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 2.11.14(solid-js@1.9.14)(supports-color@10.2.2)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) vitest: specifier: ^4.1.9 - version: 4.1.10(@types/node@25.9.5)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 4.1.10(@types/node@25.9.5)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) world-atlas: specifier: 2.0.2 version: 2.0.2 @@ -261,7 +270,7 @@ importers: version: 3.2.0 octane: specifier: 0.1.13 - version: 0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) devDependencies: '@types/d3-array': specifier: ^3.2.2 @@ -274,7 +283,7 @@ importers: version: 3.1.8 vite: specifier: ^8.0.16 - version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) examples/charts-react: dependencies: @@ -314,10 +323,10 @@ importers: version: 3.1.8 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.4(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 6.0.4(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) vite: specifier: ^8.0.16 - version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) examples/conformance: dependencies: @@ -351,7 +360,7 @@ importers: version: 3.1.8 vite: specifier: ^8.0.16 - version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) examples/octane: dependencies: @@ -366,11 +375,11 @@ importers: version: link:../../packages/octane octane: specifier: 0.1.13 - version: 0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) devDependencies: vite: specifier: ^8.0.16 - version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) examples/react: dependencies: @@ -392,10 +401,10 @@ importers: devDependencies: '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.4(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 6.0.4(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) vite: specifier: ^8.0.16 - version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) examples/sandbox: dependencies: @@ -435,10 +444,10 @@ importers: version: 3.1.8 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.4(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 6.0.4(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) vite: specifier: ^8.0.16 - version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) packages/alpine-charts: dependencies: @@ -599,7 +608,7 @@ importers: version: link:../plot octane: specifier: ^0.1.13 - version: 0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) packages/octane-charts: dependencies: @@ -608,7 +617,7 @@ importers: version: link:../charts-core octane: specifier: ^0.1.13 - version: 0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) devDependencies: '@types/d3-scale': specifier: ^4.0.9 @@ -681,7 +690,7 @@ importers: version: 1.9.14 vite-plugin-solid: specifier: ^2.11.12 - version: 2.11.14(solid-js@1.9.14)(supports-color@10.2.2)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 2.11.14(solid-js@1.9.14)(supports-color@10.2.2)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) packages/svelte-charts: dependencies: @@ -694,7 +703,7 @@ importers: version: 2.5.8(svelte@5.56.8)(typescript@6.0.3) '@sveltejs/vite-plugin-svelte': specifier: ^7.1.2 - version: 7.2.0(svelte@5.56.8)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + version: 7.2.0(svelte@5.56.8)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) svelte: specifier: ^5.56.2 version: 5.56.8 @@ -941,6 +950,13 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.7': + resolution: + { + integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==, + } + engines: { node: '>=6.9.0' } + '@babel/template@7.29.7': resolution: { @@ -969,6 +985,127 @@ packages: } hasBin: true + '@changesets/apply-release-plan@7.1.1': + resolution: + { + integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==, + } + + '@changesets/assemble-release-plan@6.0.10': + resolution: + { + integrity: sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A==, + } + + '@changesets/changelog-git@0.2.1': + resolution: + { + integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==, + } + + '@changesets/changelog-github@0.7.0': + resolution: + { + integrity: sha512-rBsbRvc4TVn+FvFnOVM3LxlFJfTXXCp8gfVJ+0BubxWNSVnLuAzowi5j+IEraLLP52w8AAs9QfKbPS3MMiXQJA==, + } + + '@changesets/cli@2.31.1': + resolution: + { + integrity: sha512-uO05WTcRBwuVOJVSW8Cmpqw6q0WDL53ajGCMyszutvOe5toOnunbpM4jZzf+qxBOz7i0AzopZ8diBuewjmF40w==, + } + hasBin: true + + '@changesets/config@3.1.4': + resolution: + { + integrity: sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q==, + } + + '@changesets/errors@0.2.0': + resolution: + { + integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==, + } + + '@changesets/get-dependents-graph@2.1.4': + resolution: + { + integrity: sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg==, + } + + '@changesets/get-github-info@0.8.0': + resolution: + { + integrity: sha512-cRnC+xdF0JIik7coko3iUP9qbnfi1iJQ3sAa6dE+Tx3+ET8bjFEm63PA4WEohgjYcmsOikPHWzPsMWWiZmntOQ==, + } + + '@changesets/get-release-plan@4.0.16': + resolution: + { + integrity: sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g==, + } + + '@changesets/get-version-range-type@0.4.0': + resolution: + { + integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==, + } + + '@changesets/git@3.0.4': + resolution: + { + integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==, + } + + '@changesets/logger@0.1.1': + resolution: + { + integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==, + } + + '@changesets/parse@0.4.3': + resolution: + { + integrity: sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==, + } + + '@changesets/pre@2.0.2': + resolution: + { + integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==, + } + + '@changesets/read@0.6.7': + resolution: + { + integrity: sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==, + } + + '@changesets/should-skip-package@0.1.2': + resolution: + { + integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==, + } + + '@changesets/types@4.1.0': + resolution: + { + integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==, + } + + '@changesets/types@6.1.0': + resolution: + { + integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==, + } + + '@changesets/write@0.4.0': + resolution: + { + integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==, + } + '@cloudflare/kv-asset-handler@0.5.0': resolution: { @@ -1100,12 +1237,30 @@ packages: integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==, } + '@emnapi/core@1.4.5': + resolution: + { + integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==, + } + '@emnapi/runtime@1.11.1': resolution: { integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==, } + '@emnapi/runtime@1.4.5': + resolution: + { + integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==, + } + + '@emnapi/wasi-threads@1.0.4': + resolution: + { + integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==, + } + '@emnapi/wasi-threads@1.2.2': resolution: { @@ -1820,6 +1975,25 @@ packages: cpu: [x64] os: [win32] + '@inquirer/external-editor@1.0.3': + resolution: + { + integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==, + } + engines: { node: '>=18' } + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jest/diff-sequences@30.0.1': + resolution: + { + integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + '@jridgewell/gen-mapping@0.3.13': resolution: { @@ -1875,6 +2049,18 @@ packages: integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==, } + '@manypkg/find-root@1.1.0': + resolution: + { + integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==, + } + + '@manypkg/get-packages@1.1.3': + resolution: + { + integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==, + } + '@napi-rs/nice-android-arm-eabi@1.1.1': resolution: { @@ -2042,6 +2228,12 @@ packages: } engines: { node: '>= 10' } + '@napi-rs/wasm-runtime@0.2.4': + resolution: + { + integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==, + } + '@napi-rs/wasm-runtime@1.1.6': resolution: { @@ -2058,6 +2250,111 @@ packages: } engines: { node: '>= 20.19.0' } + '@nodelib/fs.scandir@2.1.5': + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, + } + engines: { node: '>= 8' } + + '@nodelib/fs.stat@2.0.5': + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, + } + engines: { node: '>= 8' } + + '@nodelib/fs.walk@1.2.8': + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, + } + engines: { node: '>= 8' } + + '@nx/nx-darwin-arm64@22.7.5': + resolution: + { + integrity: sha512-eoPtwx0qZqvRUD+VVOHm150AlSYwYoPxkDHBBGqKCn5nzPspb0lLWw8q83crM/L1M928YgK0WmGf3C++7eqsTA==, + } + cpu: [arm64] + os: [darwin] + + '@nx/nx-darwin-x64@22.7.5': + resolution: + { + integrity: sha512-VLOn/ZoEn3HfjSj+yIHLCM56/el79r+9I28CkZNHaSXJQWZ3edSkcgcfYjVxCurpN2VEwDQHLBeFCH8M+lQ7wQ==, + } + cpu: [x64] + os: [darwin] + + '@nx/nx-freebsd-x64@22.7.5': + resolution: + { + integrity: sha512-LEVer/E2xfGvK9Go+imMQoEninOoq/38Z2bhV1SD3AThXrp1xaLFVkW5jQ6juebeVkAeztEoMLFlr576egS0vw==, + } + cpu: [x64] + os: [freebsd] + + '@nx/nx-linux-arm-gnueabihf@22.7.5': + resolution: + { + integrity: sha512-NP27EFGpmFJM6RL1Ey/AFJ7gA2xuqtIHaw6jjSNGvfrnZRUNaway30GrVaGGeODf0DsvAty/unqoBMPy6kDHbw==, + } + cpu: [arm] + os: [linux] + + '@nx/nx-linux-arm64-gnu@22.7.5': + resolution: + { + integrity: sha512-QLnkJl3HkHsPfpLiNiAiMfpfAeFpic0U1diAxF8RqChOkCpQ7ulvyBVgE1UrQxvhd+gFQ3ed5RNDxtCRw8nTiw==, + } + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@nx/nx-linux-arm64-musl@22.7.5': + resolution: + { + integrity: sha512-cEP6KmwBgnb38+jTTaibWCjwXcHmigqhTfy0tN1be7WZr6bHxbqNLsXqKRN70PSNA3HouZcxw1cdRL8tqbPBBA==, + } + cpu: [arm64] + os: [linux] + libc: [musl] + + '@nx/nx-linux-x64-gnu@22.7.5': + resolution: + { + integrity: sha512-tbaX1tZCSpGifDNBfDdEZAMxVF3Yg4bhFP/bm1needc0diqb+Zflc0u5tM5/6BWDMITQDwenJVsNiQ8ZdtJURA==, + } + cpu: [x64] + os: [linux] + libc: [glibc] + + '@nx/nx-linux-x64-musl@22.7.5': + resolution: + { + integrity: sha512-H0M7csOZIgPT822LqjxSXzf4MXRND15vIkAQe3F3Jlr3Si8LC3tzbL52aVcRfgb8MF/xOB5U47mSwxWt1M2bPQ==, + } + cpu: [x64] + os: [linux] + libc: [musl] + + '@nx/nx-win32-arm64-msvc@22.7.5': + resolution: + { + integrity: sha512-JTcZch9YAnDL1gbhqePz3DZ4x7iYemLn1yJzrjbbXAmXju2eiiJiZvJJHbV06+SP9HKXDT8RjTKuAWTdVxnHug==, + } + cpu: [arm64] + os: [win32] + + '@nx/nx-win32-x64-msvc@22.7.5': + resolution: + { + integrity: sha512-ngcMyHdBJ9FSz2nHdbZ7gtJlFq0O2b05sPAsVMkZ18CKzdaA1qrBDJfsMO49hPCny505eiT766+CkKdaCDl5kA==, + } + cpu: [x64] + os: [win32] + '@observablehq/plot@0.6.17': resolution: { @@ -2693,6 +2990,12 @@ packages: integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==, } + '@tybys/wasm-util@0.9.0': + resolution: + { + integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==, + } + '@types/babel__core@7.20.5': resolution: { @@ -2879,6 +3182,12 @@ packages: integrity: sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==, } + '@types/node@12.20.55': + resolution: + { + integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==, + } + '@types/node@25.9.5': resolution: { @@ -3061,6 +3370,19 @@ packages: integrity: sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==, } + '@yarnpkg/lockfile@1.1.0': + resolution: + { + integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==, + } + + '@zkochan/js-yaml@0.0.7': + resolution: + { + integrity: sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==, + } + hasBin: true + acorn@8.17.0: resolution: { @@ -3081,6 +3403,20 @@ packages: integrity: sha512-nJvPAQVNPdZZ0NrExJ/kzQco3ijR8LwvCOadQecllESiqT4NyZ/57sN9V2XyvhlBGAbmlKYgeWZvYdKq99ij/Q==, } + ansi-colors@4.1.3: + resolution: + { + integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==, + } + engines: { node: '>=6' } + + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, + } + engines: { node: '>=8' } + ansi-regex@6.2.2: resolution: { @@ -3088,6 +3424,13 @@ packages: } engines: { node: '>=12' } + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: '>=8' } + ansi-styles@6.2.3: resolution: { @@ -3095,6 +3438,18 @@ packages: } engines: { node: '>=12' } + argparse@1.0.10: + resolution: + { + integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, + } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } + aria-query@5.3.1: resolution: { @@ -3102,6 +3457,13 @@ packages: } engines: { node: '>= 0.4' } + array-union@2.1.0: + resolution: + { + integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, + } + engines: { node: '>=8' } + assertion-error@2.0.1: resolution: { @@ -3109,6 +3471,18 @@ packages: } engines: { node: '>=12' } + asynckit@0.4.0: + resolution: + { + integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, + } + + axios@1.16.0: + resolution: + { + integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==, + } + axobject-query@4.1.0: resolution: { @@ -3136,13 +3510,33 @@ packages: solid-js: optional: true - baseline-browser-mapping@2.11.5: + balanced-match@4.0.3: resolution: { - integrity: sha512-xJo6a6YZnwZfnyGmQKWMbVOcii7XRibjOskRh+WJ9UHQoX16xrQrcIgAMQOzfvs8XiLMx6ih/fsLPF73iY2D1A==, + integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==, } - engines: { node: '>=6.0.0' } - hasBin: true + engines: { node: 20 || >=22 } + + base64-js@1.5.1: + resolution: + { + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, + } + + baseline-browser-mapping@2.11.5: + resolution: + { + integrity: sha512-xJo6a6YZnwZfnyGmQKWMbVOcii7XRibjOskRh+WJ9UHQoX16xrQrcIgAMQOzfvs8XiLMx6ih/fsLPF73iY2D1A==, + } + engines: { node: '>=6.0.0' } + hasBin: true + + better-path-resolve@1.0.0: + resolution: + { + integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==, + } + engines: { node: '>=4' } bidi-js@1.0.3: resolution: @@ -3156,12 +3550,39 @@ packages: integrity: sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==, } + bl@4.1.0: + resolution: + { + integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, + } + blake3-wasm@2.1.5: resolution: { integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==, } + brace-expansion@5.0.6: + resolution: + { + integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==, + } + engines: { node: 18 || 20 || >=22 } + + brace-expansion@5.0.8: + resolution: + { + integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==, + } + engines: { node: 20 || >=22 } + + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, + } + engines: { node: '>=8' } + browserslist@4.28.7: resolution: { @@ -3170,6 +3591,19 @@ packages: engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } hasBin: true + buffer@5.7.1: + resolution: + { + integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, + } + + call-bind-apply-helpers@1.0.2: + resolution: + { + integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, + } + engines: { node: '>= 0.4' } + caniuse-lite@1.0.30001806: resolution: { @@ -3183,6 +3617,13 @@ packages: } engines: { node: '>=18' } + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: '>=10' } + chalk@5.6.2: resolution: { @@ -3190,6 +3631,12 @@ packages: } engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + chardet@2.2.0: + resolution: + { + integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==, + } + chart.js@4.5.1: resolution: { @@ -3211,6 +3658,13 @@ packages: } engines: { node: '>= 20.19.0' } + cli-cursor@3.1.0: + resolution: + { + integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==, + } + engines: { node: '>=8' } + cli-cursor@5.0.0: resolution: { @@ -3218,6 +3672,13 @@ packages: } engines: { node: '>=18' } + cli-spinners@2.6.1: + resolution: + { + integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==, + } + engines: { node: '>=6' } + cli-spinners@3.4.0: resolution: { @@ -3225,6 +3686,13 @@ packages: } engines: { node: '>=18.20' } + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, + } + engines: { node: '>=12' } + cliui@9.0.1: resolution: { @@ -3232,6 +3700,13 @@ packages: } engines: { node: '>=20' } + clone@1.0.4: + resolution: + { + integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==, + } + engines: { node: '>=0.8' } + clsx@2.1.1: resolution: { @@ -3239,6 +3714,26 @@ packages: } engines: { node: '>=6' } + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: '>=7.0.0' } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } + + combined-stream@1.0.8: + resolution: + { + integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, + } + engines: { node: '>= 0.8' } + commander@14.0.3: resolution: { @@ -3291,6 +3786,13 @@ packages: } engines: { node: '>=12.13' } + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: '>= 8' } + css-tree@3.2.1: resolution: { @@ -3537,6 +4039,12 @@ packages: } engines: { node: ^20.19.0 || ^22.12.0 || >=24.0.0 } + dataloader@1.4.0: + resolution: + { + integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==, + } + debug@2.6.9: resolution: { @@ -3596,12 +4104,32 @@ packages: } engines: { node: '>=0.10.0' } + defaults@1.0.4: + resolution: + { + integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==, + } + + define-lazy-prop@2.0.0: + resolution: + { + integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==, + } + engines: { node: '>=8' } + delaunator@5.1.0: resolution: { integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==, } + delayed-stream@1.0.0: + resolution: + { + integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, + } + engines: { node: '>=0.4.0' } + dependency-graph@1.0.0: resolution: { @@ -3609,6 +4137,13 @@ packages: } engines: { node: '>=4' } + detect-indent@6.1.0: + resolution: + { + integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==, + } + engines: { node: '>=8' } + detect-libc@2.1.2: resolution: { @@ -3622,12 +4157,55 @@ packages: integrity: sha512-DObPPAfdtFbXjxLqK8s2Xk9ZuWz5+ZoFEhC7J76es4GU/rEiXwHTmbImoCdyoCOcBH1UF3+Cz6Z2sYD4hyl5TA==, } + dir-glob@3.0.1: + resolution: + { + integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, + } + engines: { node: '>=8' } + + dotenv-expand@12.0.3: + resolution: + { + integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==, + } + engines: { node: '>=12' } + + dotenv@16.4.7: + resolution: + { + integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==, + } + engines: { node: '>=12' } + + dotenv@8.6.0: + resolution: + { + integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==, + } + engines: { node: '>=10' } + + dunder-proto@1.0.1: + resolution: + { + integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, + } + engines: { node: '>= 0.4' } + echarts@6.1.0: resolution: { integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==, } + ejs@5.0.1: + resolution: + { + integrity: sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==, + } + engines: { node: '>=0.12.18' } + hasBin: true + electron-to-chromium@1.5.397: resolution: { @@ -3640,6 +4218,32 @@ packages: integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, } + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } + + end-of-stream@1.4.5: + resolution: + { + integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==, + } + + enquirer@2.3.6: + resolution: + { + integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==, + } + engines: { node: '>=8.6' } + + enquirer@2.4.1: + resolution: + { + integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==, + } + engines: { node: '>=8.6' } + entities@6.0.1: resolution: { @@ -3674,12 +4278,40 @@ packages: integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==, } + es-define-property@1.0.1: + resolution: + { + integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, + } + engines: { node: '>= 0.4' } + + es-errors@1.3.0: + resolution: + { + integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, + } + engines: { node: '>= 0.4' } + es-module-lexer@2.3.1: resolution: { integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==, } + es-object-atoms@1.1.1: + resolution: + { + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, + } + engines: { node: '>= 0.4' } + + es-set-tostringtag@2.1.0: + resolution: + { + integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, + } + engines: { node: '>= 0.4' } + es-toolkit@1.50.0: resolution: { @@ -3709,12 +4341,27 @@ packages: } engines: { node: '>=6' } + escape-string-regexp@1.0.5: + resolution: + { + integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==, + } + engines: { node: '>=0.8.0' } + esm-env@1.2.2: resolution: { integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==, } + esprima@4.0.1: + resolution: + { + integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, + } + engines: { node: '>=4' } + hasBin: true + esrap@2.3.0: resolution: { @@ -3751,18 +4398,37 @@ packages: } engines: { node: '>=12.0.0' } + extendable-error@0.1.7: + resolution: + { + integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==, + } + fast-deep-equal@3.1.3: resolution: { integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, } + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, + } + engines: { node: '>=8.6.0' } + fast-uri@3.1.4: resolution: { integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==, } + fastq@1.20.1: + resolution: + { + integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==, + } + fdir@6.5.0: resolution: { @@ -3775,6 +4441,20 @@ packages: picomatch: optional: true + figures@3.2.0: + resolution: + { + integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==, + } + engines: { node: '>=8' } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: '>=8' } + find-cache-directory@6.0.0: resolution: { @@ -3789,6 +4469,66 @@ packages: } engines: { node: '>=18' } + find-up@4.1.0: + resolution: + { + integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, + } + engines: { node: '>=8' } + + flat@5.0.2: + resolution: + { + integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==, + } + hasBin: true + + follow-redirects@1.16.0: + resolution: + { + integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==, + } + engines: { node: '>=4.0' } + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: + { + integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==, + } + engines: { node: '>= 6' } + + form-data@4.0.6: + resolution: + { + integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==, + } + engines: { node: '>= 6' } + + fs-constants@1.0.0: + resolution: + { + integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==, + } + + fs-extra@7.0.1: + resolution: + { + integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==, + } + engines: { node: '>=6 <7 || >=8' } + + fs-extra@8.1.0: + resolution: + { + integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==, + } + engines: { node: '>=6 <7 || >=8' } + fsevents@2.3.2: resolution: { @@ -3805,6 +4545,12 @@ packages: engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } os: [darwin] + function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, + } + gensync@1.0.0-beta.2: resolution: { @@ -3826,12 +4572,82 @@ packages: } engines: { node: '>=18' } + get-intrinsic@1.3.0: + resolution: + { + integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, + } + engines: { node: '>= 0.4' } + + get-proto@1.0.1: + resolution: + { + integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, + } + engines: { node: '>= 0.4' } + + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, + } + engines: { node: '>= 6' } + + globby@11.1.0: + resolution: + { + integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==, + } + engines: { node: '>=10' } + + gopd@1.2.0: + resolution: + { + integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, + } + engines: { node: '>= 0.4' } + graceful-fs@4.2.11: resolution: { integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, } + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: '>=8' } + + has-symbols@1.1.0: + resolution: + { + integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, + } + engines: { node: '>= 0.4' } + + has-tostringtag@1.0.2: + resolution: + { + integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, + } + engines: { node: '>= 0.4' } + + hasown@2.0.2: + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, + } + engines: { node: '>= 0.4' } + + hasown@2.0.4: + resolution: + { + integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==, + } + engines: { node: '>= 0.4' } + html-encoding-sniffer@6.0.0: resolution: { @@ -3845,6 +4661,13 @@ packages: integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==, } + human-id@4.2.0: + resolution: + { + integrity: sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==, + } + hasBin: true + iconv-lite@0.4.24: resolution: { @@ -3859,6 +4682,33 @@ packages: } engines: { node: '>=0.10.0' } + iconv-lite@0.7.3: + resolution: + { + integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==, + } + engines: { node: '>=0.10.0' } + + ieee754@1.2.1: + resolution: + { + integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, + } + + ignore@5.3.2: + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, + } + engines: { node: '>= 4' } + + ignore@7.0.5: + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, + } + engines: { node: '>= 4' } + immer@11.1.15: resolution: { @@ -3871,6 +4721,12 @@ packages: integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==, } + inherits@2.0.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, + } + injection-js@2.6.1: resolution: { @@ -3890,6 +4746,14 @@ packages: integrity: sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ==, } + is-docker@2.2.1: + resolution: + { + integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==, + } + engines: { node: '>=8' } + hasBin: true + is-extglob@2.1.1: resolution: { @@ -3897,6 +4761,13 @@ packages: } engines: { node: '>=0.10.0' } + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, + } + engines: { node: '>=8' } + is-glob@4.0.3: resolution: { @@ -3904,12 +4775,26 @@ packages: } engines: { node: '>=0.10.0' } + is-interactive@1.0.0: + resolution: + { + integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==, + } + engines: { node: '>=8' } + is-interactive@2.0.0: resolution: { integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==, } - engines: { node: '>=12' } + engines: { node: '>=12' } + + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, + } + engines: { node: '>=0.12.0' } is-potential-custom-element-name@1.0.1: resolution: @@ -3923,6 +4808,20 @@ packages: integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==, } + is-subdir@1.2.0: + resolution: + { + integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==, + } + engines: { node: '>=4' } + + is-unicode-supported@0.1.0: + resolution: + { + integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==, + } + engines: { node: '>=10' } + is-unicode-supported@2.1.0: resolution: { @@ -3937,6 +4836,26 @@ packages: } engines: { node: '>=12.13' } + is-windows@1.0.2: + resolution: + { + integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==, + } + engines: { node: '>=0.10.0' } + + is-wsl@2.2.0: + resolution: + { + integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==, + } + engines: { node: '>=8' } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + isoformat@0.2.1: resolution: { @@ -3949,6 +4868,20 @@ packages: integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, } + js-yaml@3.15.0: + resolution: + { + integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==, + } + hasBin: true + + js-yaml@4.3.0: + resolution: + { + integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==, + } + hasBin: true + jsdom@29.1.1: resolution: { @@ -3983,12 +4916,24 @@ packages: engines: { node: '>=6' } hasBin: true + jsonc-parser@3.2.0: + resolution: + { + integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==, + } + jsonc-parser@3.3.1: resolution: { integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==, } + jsonfile@4.0.0: + resolution: + { + integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==, + } + kleur@4.1.5: resolution: { @@ -4114,6 +5059,13 @@ packages: } engines: { node: '>= 12.0.0' } + lines-and-columns@2.0.3: + resolution: + { + integrity: sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + lit-element@4.2.2: resolution: { @@ -4138,12 +5090,32 @@ packages: integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==, } + locate-path@5.0.0: + resolution: + { + integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, + } + engines: { node: '>=8' } + lodash.merge@4.6.2: resolution: { integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, } + lodash.startcase@4.4.0: + resolution: + { + integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==, + } + + log-symbols@4.1.0: + resolution: + { + integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==, + } + engines: { node: '>=10' } + log-symbols@7.0.1: resolution: { @@ -4177,6 +5149,13 @@ packages: } engines: { node: '>=18' } + math-intrinsics@1.1.0: + resolution: + { + integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, + } + engines: { node: '>= 0.4' } + mdn-data@2.27.1: resolution: { @@ -4190,6 +5169,34 @@ packages: } engines: { node: '>=12.13' } + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, + } + engines: { node: '>= 8' } + + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, + } + engines: { node: '>=8.6' } + + mime-db@1.52.0: + resolution: + { + integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, + } + engines: { node: '>= 0.6' } + + mime-types@2.1.35: + resolution: + { + integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, + } + engines: { node: '>= 0.6' } + mime@1.6.0: resolution: { @@ -4198,6 +5205,13 @@ packages: engines: { node: '>=4' } hasBin: true + mimic-fn@2.1.0: + resolution: + { + integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, + } + engines: { node: '>=6' } + mimic-function@5.0.1: resolution: { @@ -4213,6 +5227,19 @@ packages: engines: { node: '>=22.0.0' } hasBin: true + minimatch@10.2.5: + resolution: + { + integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==, + } + engines: { node: 18 || 20 || >=22 } + + minimist@1.2.8: + resolution: + { + integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, + } + mri@1.2.0: resolution: { @@ -4278,6 +5305,18 @@ packages: integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==, } + node-fetch@2.7.0: + resolution: + { + integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==, + } + engines: { node: 4.x || >=6.0.0 } + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-releases@2.0.51: resolution: { @@ -4285,6 +5324,28 @@ packages: } engines: { node: '>=18' } + npm-run-path@4.0.1: + resolution: + { + integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, + } + engines: { node: '>=8' } + + nx@22.7.5: + resolution: + { + integrity: sha512-zoxsJabb33jl1QYnalDn0bicryrEBgSzdKp90d7VGGv/jDgzKrcLg/hw2ZxeYiOjWPIT/o8QNT9G9vTs4dv3AQ==, + } + hasBin: true + peerDependencies: + '@swc-node/register': ^1.11.1 + '@swc/core': ^1.15.8 + peerDependenciesMeta: + '@swc-node/register': + optional: true + '@swc/core': + optional: true + obug@2.1.4: resolution: { @@ -4310,6 +5371,19 @@ packages: vite: optional: true + once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, + } + + onetime@5.1.2: + resolution: + { + integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, + } + engines: { node: '>=6' } + onetime@7.0.0: resolution: { @@ -4317,6 +5391,20 @@ packages: } engines: { node: '>=18' } + open@8.4.2: + resolution: + { + integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==, + } + engines: { node: '>=12' } + + ora@5.3.0: + resolution: + { + integrity: sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==, + } + engines: { node: '>=10' } + ora@9.4.1: resolution: { @@ -4324,6 +5412,53 @@ packages: } engines: { node: '>=20' } + outdent@0.5.0: + resolution: + { + integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==, + } + + p-filter@2.1.0: + resolution: + { + integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==, + } + engines: { node: '>=8' } + + p-limit@2.3.0: + resolution: + { + integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, + } + engines: { node: '>=6' } + + p-locate@4.1.0: + resolution: + { + integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, + } + engines: { node: '>=8' } + + p-map@2.1.0: + resolution: + { + integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==, + } + engines: { node: '>=6' } + + p-try@2.2.0: + resolution: + { + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, + } + engines: { node: '>=6' } + + package-manager-detector@0.2.11: + resolution: + { + integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==, + } + parse-node-version@1.0.1: resolution: { @@ -4343,12 +5478,33 @@ packages: integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==, } + path-exists@4.0.0: + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, + } + engines: { node: '>=8' } + + path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, + } + engines: { node: '>=8' } + path-to-regexp@6.3.0: resolution: { integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==, } + path-type@4.0.0: + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, + } + engines: { node: '>=8' } + pathe@2.0.3: resolution: { @@ -4361,6 +5517,13 @@ packages: integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, } + picomatch@2.3.2: + resolution: + { + integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==, + } + engines: { node: '>=8.6' } + picomatch@4.0.5: resolution: { @@ -4368,6 +5531,13 @@ packages: } engines: { node: '>=12' } + pify@4.0.1: + resolution: + { + integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==, + } + engines: { node: '>=6' } + piscina@5.3.0: resolution: { @@ -4424,6 +5594,14 @@ packages: preact-render-to-string: optional: true + prettier@2.8.8: + resolution: + { + integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==, + } + engines: { node: '>=10.13.0' } + hasBin: true + prettier@3.9.6: resolution: { @@ -4438,6 +5616,13 @@ packages: integrity: sha512-7CaDeBwiAbh6ohXsvLbAZhO7wzsZAmaevfxe39qvCwRh8LyaZfDlBGGLU1CCTgrTLtCOdwBBhjOrIHaIIimHfQ==, } + proxy-from-env@2.1.0: + resolution: + { + integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==, + } + engines: { node: '>=10' } + prr@1.0.1: resolution: { @@ -4451,6 +5636,18 @@ packages: } engines: { node: '>=6' } + quansync@0.2.11: + resolution: + { + integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==, + } + + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, + } + react-dom@19.2.3: resolution: { @@ -4487,6 +5684,20 @@ packages: } engines: { node: '>=0.10.0' } + read-yaml-file@1.1.0: + resolution: + { + integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==, + } + engines: { node: '>=6' } + + readable-stream@3.6.2: + resolution: + { + integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, + } + engines: { node: '>= 6' } + readdirp@4.1.2: resolution: { @@ -4532,6 +5743,13 @@ packages: integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==, } + require-directory@2.1.1: + resolution: + { + integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, + } + engines: { node: '>=0.10.0' } + require-from-string@2.0.2: resolution: { @@ -4545,6 +5763,27 @@ packages: integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==, } + resolve-from@5.0.0: + resolution: + { + integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, + } + engines: { node: '>=8' } + + resolve.exports@2.0.3: + resolution: + { + integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==, + } + engines: { node: '>=10' } + + restore-cursor@3.1.0: + resolution: + { + integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==, + } + engines: { node: '>=8' } + restore-cursor@5.1.0: resolution: { @@ -4552,6 +5791,13 @@ packages: } engines: { node: '>=18' } + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, + } + engines: { iojs: '>=1.0.0', node: '>=0.10.0' } + robust-predicates@3.0.3: resolution: { @@ -4584,6 +5830,12 @@ packages: engines: { node: '>=18.0.0', npm: '>=8.0.0' } hasBin: true + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, + } + rw@1.3.3: resolution: { @@ -4603,6 +5855,12 @@ packages: } engines: { node: '>=6' } + safe-buffer@5.2.1: + resolution: + { + integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, + } + safer-buffer@2.1.2: resolution: { @@ -4650,6 +5908,14 @@ packages: } hasBin: true + semver@7.7.4: + resolution: + { + integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==, + } + engines: { node: '>=10' } + hasBin: true + semver@7.8.5: resolution: { @@ -4681,12 +5947,32 @@ packages: } engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, + } + engines: { node: '>=8' } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: '>=8' } + siginfo@2.0.0: resolution: { integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, } + signal-exit@3.0.7: + resolution: + { + integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, + } + signal-exit@4.1.0: resolution: { @@ -4694,6 +5980,20 @@ packages: } engines: { node: '>=14' } + slash@3.0.0: + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, + } + engines: { node: '>=8' } + + smol-toml@1.6.1: + resolution: + { + integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==, + } + engines: { node: '>= 18' } + solid-js@1.9.14: resolution: { @@ -4722,6 +6022,18 @@ packages: } engines: { node: '>=0.10.0' } + spawndamnit@3.0.1: + resolution: + { + integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==, + } + + sprintf-js@1.0.3: + resolution: + { + integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, + } + stackback@0.0.2: resolution: { @@ -4747,6 +6059,13 @@ packages: integrity: sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==, } + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, + } + engines: { node: '>=8' } + string-width@7.2.0: resolution: { @@ -4761,6 +6080,19 @@ packages: } engines: { node: '>=20' } + string_decoder@1.3.0: + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, + } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, + } + engines: { node: '>=8' } + strip-ansi@7.2.0: resolution: { @@ -4768,6 +6100,13 @@ packages: } engines: { node: '>=12' } + strip-bom@3.0.0: + resolution: + { + integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==, + } + engines: { node: '>=4' } + supports-color@10.2.2: resolution: { @@ -4775,6 +6114,13 @@ packages: } engines: { node: '>=18' } + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: '>=8' } + svelte-check@4.7.4: resolution: { @@ -4808,6 +6154,20 @@ packages: integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==, } + tar-stream@2.2.0: + resolution: + { + integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==, + } + engines: { node: '>=6' } + + term-size@2.2.1: + resolution: + { + integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==, + } + engines: { node: '>=8' } + tiny-invariant@1.3.3: resolution: { @@ -4854,6 +6214,20 @@ packages: } hasBin: true + tmp@0.2.6: + resolution: + { + integrity: sha512-5sJPdPjfI5Kx+qbrDesxkglRBxW//g7hCsqspEjwkewGvBMGIKMOTKzLt1hFVJzyadba3lDUN20O9qhvbQUSTA==, + } + engines: { node: '>=14.14' } + + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, + } + engines: { node: '>=8.0' } + topojson-client@3.1.0: resolution: { @@ -4868,6 +6242,12 @@ packages: } engines: { node: '>=16' } + tr46@0.0.3: + resolution: + { + integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, + } + tr46@6.0.0: resolution: { @@ -4875,6 +6255,20 @@ packages: } engines: { node: '>=20' } + tree-kill@1.2.2: + resolution: + { + integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==, + } + hasBin: true + + tsconfig-paths@4.2.0: + resolution: + { + integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==, + } + engines: { node: '>=6' } + tslib@2.3.0: resolution: { @@ -4921,6 +6315,13 @@ packages: integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==, } + universalify@0.1.2: + resolution: + { + integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==, + } + engines: { node: '>= 4.0.0' } + update-browserslist-db@1.2.3: resolution: { @@ -4944,6 +6345,12 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: + resolution: + { + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, + } + victory-vendor@37.3.6: resolution: { @@ -5082,6 +6489,18 @@ packages: } engines: { node: '>=18' } + wcwidth@1.0.1: + resolution: + { + integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, + } + + webidl-conversions@3.0.1: + resolution: + { + integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, + } + webidl-conversions@8.0.1: resolution: { @@ -5103,6 +6522,20 @@ packages: } engines: { node: ^20.19.0 || ^22.12.0 || >=24.0.0 } + whatwg-url@5.0.0: + resolution: + { + integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, + } + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: '>= 8' } + hasBin: true + why-is-node-running@2.3.0: resolution: { @@ -5138,6 +6571,13 @@ packages: '@cloudflare/workers-types': optional: true + wrap-ansi@7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, + } + engines: { node: '>=10' } + wrap-ansi@9.0.2: resolution: { @@ -5145,6 +6585,12 @@ packages: } engines: { node: '>=18' } + wrappy@1.0.2: + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, + } + ws@8.21.0: resolution: { @@ -5186,6 +6632,21 @@ packages: integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, } + yaml@2.9.0: + resolution: + { + integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==, + } + engines: { node: '>= 14.6' } + hasBin: true + + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, + } + engines: { node: '>=12' } + yargs-parser@22.0.0: resolution: { @@ -5193,6 +6654,13 @@ packages: } engines: { node: ^20.19.0 || ^22.12.0 || >=23 } + yargs@17.7.2: + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, + } + engines: { node: '>=12' } + yargs@18.1.0: resolution: { @@ -5392,6 +6860,8 @@ snapshots: '@babel/core': 7.29.7(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.29.7 + '@babel/runtime@7.29.7': {} + '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 @@ -5419,6 +6889,164 @@ snapshots: dependencies: css-tree: 3.2.1 + '@changesets/apply-release-plan@7.1.1': + dependencies: + '@changesets/config': 3.1.4 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.8.5 + + '@changesets/assemble-release-plan@6.0.10': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.8.5 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/changelog-github@0.7.0': + dependencies: + '@changesets/get-github-info': 0.8.0 + '@changesets/types': 6.1.0 + dotenv: 8.6.0 + transitivePeerDependencies: + - encoding + + '@changesets/cli@2.31.1(@types/node@25.9.5)': + dependencies: + '@changesets/apply-release-plan': 7.1.1 + '@changesets/assemble-release-plan': 6.0.10 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.4 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/get-release-plan': 4.0.16 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3(@types/node@25.9.5) + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.8.5 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.1.4': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/logger': 0.1.1 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.4': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.8.5 + + '@changesets/get-github-info@0.8.0': + dependencies: + dataloader: 1.4.0 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + '@changesets/get-release-plan@4.0.16': + dependencies: + '@changesets/assemble-release-plan': 6.0.10 + '@changesets/config': 3.1.4 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.7 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.3': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 4.3.0 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.7': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.3 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.2.0 + prettier: 2.8.8 + '@cloudflare/kv-asset-handler@0.5.0': {} '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260617.1)': @@ -5474,17 +7102,27 @@ snapshots: dependencies: '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 - optional: true + + '@emnapi/core@1.4.5': + dependencies: + '@emnapi/wasi-threads': 1.0.4 + tslib: 2.8.1 '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 - optional: true + + '@emnapi/runtime@1.4.5': + dependencies: + tslib: 2.8.1 + + '@emnapi/wasi-threads@1.0.4': + dependencies: + tslib: 2.8.1 '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 - optional: true '@esbuild/aix-ppc64@0.27.7': optional: true @@ -5742,6 +7380,15 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@inquirer/external-editor@1.0.3(@types/node@25.9.5)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.3 + optionalDependencies: + '@types/node': 25.9.5 + + '@jest/diff-sequences@30.0.1': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5774,6 +7421,22 @@ snapshots: dependencies: '@lit-labs/ssr-dom-shim': 1.6.0 + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.29.7 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.29.7 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + '@napi-rs/nice-android-arm-eabi@1.1.1': optional: true @@ -5846,6 +7509,12 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.1.1 optional: true + '@napi-rs/wasm-runtime@0.2.4': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.9.0 + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: '@emnapi/core': 1.11.1 @@ -5855,6 +7524,48 @@ snapshots: '@noble/hashes@2.2.0': {} + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nx/nx-darwin-arm64@22.7.5': + optional: true + + '@nx/nx-darwin-x64@22.7.5': + optional: true + + '@nx/nx-freebsd-x64@22.7.5': + optional: true + + '@nx/nx-linux-arm-gnueabihf@22.7.5': + optional: true + + '@nx/nx-linux-arm64-gnu@22.7.5': + optional: true + + '@nx/nx-linux-arm64-musl@22.7.5': + optional: true + + '@nx/nx-linux-x64-gnu@22.7.5': + optional: true + + '@nx/nx-linux-x64-musl@22.7.5': + optional: true + + '@nx/nx-win32-arm64-msvc@22.7.5': + optional: true + + '@nx/nx-win32-x64-msvc@22.7.5': + optional: true + '@observablehq/plot@0.6.17': dependencies: d3: 7.9.0 @@ -6117,23 +7828,23 @@ snapshots: transitivePeerDependencies: - typescript - '@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.8)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0))': + '@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.8)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.4 svelte: 5.56.8 - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) - vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) - '@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.8)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0))': + '@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.8)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.4 svelte: 5.56.8 - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) - vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) '@tsrx/core@0.1.48': dependencies: @@ -6155,6 +7866,10 @@ snapshots: tslib: 2.8.1 optional: true + '@tybys/wasm-util@0.9.0': + dependencies: + tslib: 2.8.1 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.7 @@ -6255,6 +7970,8 @@ snapshots: '@types/tough-cookie': 4.0.5 parse5: 7.3.0 + '@types/node@12.20.55': {} + '@types/node@25.9.5': dependencies: undici-types: 7.24.6 @@ -6282,10 +7999,10 @@ snapshots: '@types/use-sync-external-store@0.0.6': {} - '@vitejs/plugin-react@6.0.4(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0))': + '@vitejs/plugin-react@6.0.4(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) '@vitest/expect@4.1.10': dependencies: @@ -6296,13 +8013,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0))': + '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.10': dependencies: @@ -6388,6 +8105,12 @@ snapshots: '@vue/shared@3.5.40': {} + '@yarnpkg/lockfile@1.1.0': {} + + '@zkochan/js-yaml@0.0.7': + dependencies: + argparse: 2.0.1 + acorn@8.17.0: {} ajv@8.20.0: @@ -6401,14 +8124,40 @@ snapshots: dependencies: '@vue/reactivity': 3.1.5 + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + ansi-styles@6.2.3: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + aria-query@5.3.1: {} + array-union@2.1.0: {} + assertion-error@2.0.1: {} + asynckit@0.4.0: {} + + axios@1.16.0(debug@4.4.3(supports-color@10.2.2)): + dependencies: + follow-redirects: 1.16.0(debug@4.4.3(supports-color@10.2.2)) + form-data: 4.0.6 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + axobject-query@4.1.0: {} babel-plugin-jsx-dom-expressions@0.40.7(@babel/core@7.29.7(supports-color@10.2.2)): @@ -6427,16 +8176,42 @@ snapshots: optionalDependencies: solid-js: 1.9.14 + balanced-match@4.0.3: {} + + base64-js@1.5.1: {} + baseline-browser-mapping@2.11.5: {} + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 binary-search-bounds@2.0.5: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + blake3-wasm@2.1.5: {} + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.3 + + brace-expansion@5.0.8: + dependencies: + balanced-match: 4.0.3 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + browserslist@4.28.7: dependencies: baseline-browser-mapping: 2.11.5 @@ -6445,12 +8220,29 @@ snapshots: node-releases: 2.0.51 update-browserslist-db: 1.2.3(browserslist@4.28.7) + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + caniuse-lite@1.0.30001806: {} chai@6.2.2: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@5.6.2: {} + chardet@2.2.0: {} + chart.js@4.5.1: dependencies: '@kurkle/color': 0.3.4 @@ -6463,20 +8255,44 @@ snapshots: dependencies: readdirp: 5.0.0 + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 + cli-spinners@2.6.1: {} + cli-spinners@3.4.0: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + cliui@9.0.1: dependencies: string-width: 7.2.0 strip-ansi: 7.2.0 wrap-ansi: 9.0.2 + clone@1.0.4: {} + clsx@2.1.1: {} + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@14.0.3: {} commander@2.20.3: {} @@ -6495,6 +8311,12 @@ snapshots: dependencies: is-what: 4.1.16 + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + css-tree@3.2.1: dependencies: mdn-data: 2.27.1 @@ -6663,6 +8485,8 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + dataloader@1.4.0: {} + debug@2.6.9(supports-color@10.2.2): dependencies: ms: 2.0.0 @@ -6691,25 +8515,70 @@ snapshots: deepmerge@4.3.1: {} + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-lazy-prop@2.0.0: {} + delaunator@5.1.0: dependencies: robust-predicates: 3.0.3 + delayed-stream@1.0.0: {} + dependency-graph@1.0.0: {} + detect-indent@6.1.0: {} + detect-libc@2.1.2: {} devalue@5.8.2: {} + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dotenv-expand@12.0.3: + dependencies: + dotenv: 16.4.7 + + dotenv@16.4.7: {} + + dotenv@8.6.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + echarts@6.1.0: dependencies: tslib: 2.3.0 zrender: 6.1.0 + ejs@5.0.1: {} + electron-to-chromium@1.5.397: {} emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + enquirer@2.3.6: + dependencies: + ansi-colors: 4.1.3 + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + entities@6.0.1: {} entities@7.0.1: {} @@ -6723,8 +8592,23 @@ snapshots: error-stack-parser-es@1.0.5: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@2.3.1: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + es-toolkit@1.50.0: {} esbuild@0.27.7: @@ -6787,8 +8671,12 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@1.0.5: {} + esm-env@1.2.2: {} + esprima@4.0.1: {} + esrap@2.3.0: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -6803,14 +8691,36 @@ snapshots: expect-type@1.4.0: {} + extendable-error@0.1.7: {} + fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-uri@3.1.4: {} + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: picomatch: 4.0.5 + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + find-cache-directory@6.0.0: dependencies: common-path-prefix: 3.0.0 @@ -6818,20 +8728,111 @@ snapshots: find-up-simple@1.0.1: {} + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + flat@5.0.2: {} + + follow-redirects@1.16.0(debug@4.4.3(supports-color@10.2.2)): + optionalDependencies: + debug: 4.4.3(supports-color@10.2.2) + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fs-constants@1.0.0: {} + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + fsevents@2.3.2: optional: true fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} get-east-asian-width@1.6.0: {} - graceful-fs@4.2.11: - optional: true + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 html-encoding-sniffer@6.0.0(@noble/hashes@2.2.0): dependencies: @@ -6841,6 +8842,8 @@ snapshots: html-entities@2.3.3: {} + human-id@4.2.0: {} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -6850,10 +8853,22 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + immer@11.1.15: {} immutable@5.1.9: {} + inherits@2.0.4: {} + injection-js@2.6.1: dependencies: tslib: 2.8.1 @@ -6864,30 +8879,59 @@ snapshots: dependencies: binary-search-bounds: 2.0.5 - is-extglob@2.1.1: - optional: true + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - optional: true + + is-interactive@1.0.0: {} is-interactive@2.0.0: {} + is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} is-reference@3.0.3: dependencies: '@types/estree': 1.0.9 + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-unicode-supported@0.1.0: {} + is-unicode-supported@2.1.0: {} is-what@4.1.16: {} + is-windows@1.0.2: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + isoformat@0.2.1: {} js-tokens@4.0.0: {} + js-yaml@3.15.0: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.3.0: + dependencies: + argparse: 2.0.1 + jsdom@29.1.1(@noble/hashes@2.2.0): dependencies: '@asamuzakjp/css-color': 5.1.11 @@ -6920,8 +8964,14 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.2.0: {} + jsonc-parser@3.3.1: {} + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + kleur@4.1.5: {} less@4.8.1(supports-color@10.2.2): @@ -6988,6 +9038,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.33.0 lightningcss-win32-x64-msvc: 1.33.0 + lines-and-columns@2.0.3: {} + lit-element@4.2.2: dependencies: '@lit-labs/ssr-dom-shim': 1.6.0 @@ -7006,9 +9058,20 @@ snapshots: locate-character@3.0.0: {} + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + lodash.merge@4.6.2: optional: true + lodash.startcase@4.4.0: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + log-symbols@7.0.1: dependencies: is-unicode-supported: 2.1.0 @@ -7027,15 +9090,32 @@ snapshots: make-dir@5.1.0: optional: true + math-intrinsics@1.1.0: {} + mdn-data@2.27.1: {} merge-anything@5.1.7: dependencies: is-what: 4.1.16 + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mime@1.6.0: optional: true + mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} miniflare@4.20260617.1: @@ -7050,6 +9130,12 @@ snapshots: - bufferutil - utf-8-validate + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.8 + + minimist@1.2.8: {} + mri@1.2.0: {} ms@2.0.0: @@ -7107,11 +9193,145 @@ snapshots: node-addon-api@7.1.1: optional: true + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-releases@2.0.51: {} + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nx@22.7.5(debug@4.4.3(supports-color@10.2.2)): + dependencies: + '@emnapi/core': 1.4.5 + '@emnapi/runtime': 1.4.5 + '@emnapi/wasi-threads': 1.0.4 + '@jest/diff-sequences': 30.0.1 + '@napi-rs/wasm-runtime': 0.2.4 + '@tybys/wasm-util': 0.9.0 + '@yarnpkg/lockfile': 1.1.0 + '@zkochan/js-yaml': 0.0.7 + ansi-colors: 4.1.3 + ansi-regex: 5.0.1 + ansi-styles: 4.3.0 + argparse: 2.0.1 + asynckit: 0.4.0 + axios: 1.16.0(debug@4.4.3(supports-color@10.2.2)) + balanced-match: 4.0.3 + base64-js: 1.5.1 + bl: 4.1.0 + brace-expansion: 5.0.6 + buffer: 5.7.1 + call-bind-apply-helpers: 1.0.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.6.1 + cliui: 8.0.1 + clone: 1.0.4 + color-convert: 2.0.1 + color-name: 1.1.4 + combined-stream: 1.0.8 + defaults: 1.0.4 + define-lazy-prop: 2.0.0 + delayed-stream: 1.0.0 + dotenv: 16.4.7 + dotenv-expand: 12.0.3 + dunder-proto: 1.0.1 + ejs: 5.0.1 + emoji-regex: 8.0.0 + end-of-stream: 1.4.5 + enquirer: 2.3.6 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + escalade: 3.2.0 + escape-string-regexp: 1.0.5 + figures: 3.2.0 + flat: 5.0.2 + follow-redirects: 1.16.0(debug@4.4.3(supports-color@10.2.2)) + form-data: 4.0.5 + fs-constants: 1.0.0 + function-bind: 1.1.2 + get-caller-file: 2.0.5 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + has-flag: 4.0.0 + has-symbols: 1.1.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + ieee754: 1.2.1 + ignore: 7.0.5 + inherits: 2.0.4 + is-docker: 2.2.1 + is-fullwidth-code-point: 3.0.0 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + is-wsl: 2.2.0 + json5: 2.2.3 + jsonc-parser: 3.2.0 + lines-and-columns: 2.0.3 + log-symbols: 4.1.0 + math-intrinsics: 1.1.0 + mime-db: 1.52.0 + mime-types: 2.1.35 + mimic-fn: 2.1.0 + minimatch: 10.2.5 + minimist: 1.2.8 + npm-run-path: 4.0.1 + once: 1.4.0 + onetime: 5.1.2 + open: 8.4.2 + ora: 5.3.0 + path-key: 3.1.1 + picocolors: 1.1.1 + proxy-from-env: 2.1.0 + readable-stream: 3.6.2 + require-directory: 2.1.1 + resolve.exports: 2.0.3 + restore-cursor: 3.1.0 + safe-buffer: 5.2.1 + semver: 7.7.4 + signal-exit: 3.0.7 + smol-toml: 1.6.1 + string-width: 4.2.3 + string_decoder: 1.3.0 + strip-ansi: 6.0.1 + strip-bom: 3.0.0 + supports-color: 7.2.0 + tar-stream: 2.2.0 + tmp: 0.2.6 + tree-kill: 1.2.2 + tsconfig-paths: 4.2.0 + tslib: 2.8.1 + util-deprecate: 1.0.2 + wcwidth: 1.0.1 + wrap-ansi: 7.0.0 + wrappy: 1.0.2 + y18n: 5.0.8 + yaml: 2.9.0 + yargs: 17.7.2 + yargs-parser: 21.1.1 + optionalDependencies: + '@nx/nx-darwin-arm64': 22.7.5 + '@nx/nx-darwin-x64': 22.7.5 + '@nx/nx-freebsd-x64': 22.7.5 + '@nx/nx-linux-arm-gnueabihf': 22.7.5 + '@nx/nx-linux-arm64-gnu': 22.7.5 + '@nx/nx-linux-arm64-musl': 22.7.5 + '@nx/nx-linux-x64-gnu': 22.7.5 + '@nx/nx-linux-x64-musl': 22.7.5 + '@nx/nx-win32-arm64-msvc': 22.7.5 + '@nx/nx-win32-x64-msvc': 22.7.5 + transitivePeerDependencies: + - debug + obug@2.1.4: {} - octane@0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)): + octane@0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)): dependencies: '@tsrx/core': 0.1.48 '@types/react': 19.2.17 @@ -7120,11 +9340,11 @@ snapshots: optionalDependencies: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) transitivePeerDependencies: - '@typescript-eslint/types' - octane@0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)): + octane@0.1.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)): dependencies: '@tsrx/core': 0.1.48 '@types/react': 19.2.17 @@ -7133,14 +9353,39 @@ snapshots: optionalDependencies: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) transitivePeerDependencies: - '@typescript-eslint/types' + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + ora@5.3.0: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.6.1 + is-interactive: 1.0.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + ora@9.4.1: dependencies: chalk: 5.6.2 @@ -7152,6 +9397,28 @@ snapshots: stdin-discarder: 0.3.2 string-width: 8.2.2 + outdent@0.5.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-map@2.1.0: {} + + p-try@2.2.0: {} + + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + parse-node-version@1.0.1: {} parse5@7.3.0: @@ -7162,14 +9429,24 @@ snapshots: dependencies: entities: 8.0.0 + path-exists@4.0.0: {} + + path-key@3.1.1: {} + path-to-regexp@6.3.0: {} + path-type@4.0.0: {} + pathe@2.0.3: {} picocolors@1.1.1: {} + picomatch@2.3.2: {} + picomatch@4.0.5: {} + pify@4.0.1: {} + piscina@5.3.0: optionalDependencies: '@napi-rs/nice': 1.1.1 @@ -7200,6 +9477,8 @@ snapshots: optionalDependencies: preact-render-to-string: 6.7.0(preact@10.29.7) + prettier@2.8.8: {} + prettier@3.9.6: {} probe-image-size@7.3.0(supports-color@10.2.2): @@ -7211,11 +9490,17 @@ snapshots: - supports-color optional: true + proxy-from-env@2.1.0: {} + prr@1.0.1: optional: true punycode@2.3.1: {} + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + react-dom@19.2.3(react@19.2.3): dependencies: react: 19.2.3 @@ -7234,6 +9519,19 @@ snapshots: react@19.2.3: {} + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.15.0 + pify: 4.0.1 + strip-bom: 3.0.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@4.1.2: {} readdirp@5.0.0: {} @@ -7266,15 +9564,28 @@ snapshots: reflect-metadata@0.2.2: {} + require-directory@2.1.1: {} + require-from-string@2.0.2: {} reselect@5.2.0: {} + resolve-from@5.0.0: {} + + resolve.exports@2.0.3: {} + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 signal-exit: 4.1.0 + reusify@1.1.0: {} + robust-predicates@3.0.3: {} rolldown@1.1.5: @@ -7340,6 +9651,10 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.62.3 fsevents: 2.3.3 + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + rw@1.3.3: {} rxjs@7.8.2: @@ -7350,6 +9665,8 @@ snapshots: dependencies: mri: 1.2.0 + safe-buffer@5.2.1: {} + safer-buffer@2.1.2: {} sass@1.102.0: @@ -7373,6 +9690,8 @@ snapshots: semver@6.3.1: {} + semver@7.7.4: {} + semver@7.8.5: {} seroval-plugins@1.5.6(seroval@1.5.6): @@ -7412,10 +9731,22 @@ snapshots: '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + slash@3.0.0: {} + + smol-toml@1.6.1: {} + solid-js@1.9.14: dependencies: csstype: 3.2.3 @@ -7436,6 +9767,13 @@ snapshots: source-map@0.6.1: optional: true + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + sprintf-js@1.0.3: {} + stackback@0.0.2: {} std-env@4.2.0: {} @@ -7449,6 +9787,12 @@ snapshots: - supports-color optional: true + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -7460,12 +9804,26 @@ snapshots: get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 + strip-bom@3.0.0: {} + supports-color@10.2.2: {} + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + svelte-check@4.7.4(picomatch@4.0.5)(svelte@5.56.8)(typescript@6.0.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -7509,6 +9867,16 @@ snapshots: symbol-tree@3.2.4: {} + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + term-size@2.2.1: {} + tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -7528,6 +9896,12 @@ snapshots: dependencies: tldts-core: 7.4.9 + tmp@0.2.6: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + topojson-client@3.1.0: dependencies: commander: 2.20.3 @@ -7536,10 +9910,20 @@ snapshots: dependencies: tldts: 7.4.9 + tr46@0.0.3: {} + tr46@6.0.0: dependencies: punycode: 2.3.1 + tree-kill@1.2.2: {} + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.3.0: {} tslib@2.8.1: {} @@ -7556,6 +9940,8 @@ snapshots: dependencies: pathe: 2.0.3 + universalify@0.1.2: {} + update-browserslist-db@1.2.3(browserslist@4.28.7): dependencies: browserslist: 4.28.7 @@ -7568,6 +9954,8 @@ snapshots: dependencies: react: 19.2.3 + util-deprecate@1.0.2: {} + victory-vendor@37.3.6: dependencies: '@types/d3-array': 3.2.2 @@ -7585,7 +9973,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-plugin-solid@2.11.14(solid-js@1.9.14)(supports-color@10.2.2)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)): + vite-plugin-solid@2.11.14(solid-js@1.9.14)(supports-color@10.2.2)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7(supports-color@10.2.2) '@types/babel__core': 7.20.5 @@ -7593,12 +9981,12 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.14 solid-refresh: 0.6.3(solid-js@1.9.14)(supports-color@10.2.2) - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) - vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) transitivePeerDependencies: - supports-color - vite-plugin-solid@2.11.14(solid-js@1.9.14)(supports-color@10.2.2)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)): + vite-plugin-solid@2.11.14(solid-js@1.9.14)(supports-color@10.2.2)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7(supports-color@10.2.2) '@types/babel__core': 7.20.5 @@ -7606,12 +9994,12 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.14 solid-refresh: 0.6.3(solid-js@1.9.14)(supports-color@10.2.2) - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) - vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) transitivePeerDependencies: - supports-color - vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0): + vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -7624,8 +10012,9 @@ snapshots: fsevents: 2.3.3 less: 4.8.1(supports-color@10.2.2) sass: 1.102.0 + yaml: 2.9.0 - vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0): + vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -7638,19 +10027,20 @@ snapshots: fsevents: 2.3.3 less: 4.8.1(supports-color@10.2.2) sass: 1.102.0 + yaml: 2.9.0 - vitefu@1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)): + vitefu@1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)): optionalDependencies: - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) - vitefu@1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)): + vitefu@1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)): optionalDependencies: - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) - vitest@4.1.10(@types/node@25.9.5)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)): + vitest@4.1.10(@types/node@25.9.5)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)) + '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -7667,7 +10057,7 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.27.7)(less@4.8.1(supports-color@10.2.2))(sass@1.102.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.9.5 @@ -7689,6 +10079,12 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + webidl-conversions@3.0.1: {} + webidl-conversions@8.0.1: {} whatwg-mimetype@5.0.0: {} @@ -7701,6 +10097,15 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -7732,12 +10137,20 @@ snapshots: - bufferutil - utf-8-validate + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 strip-ansi: 7.2.0 + wrappy@1.0.2: {} + ws@8.21.0: {} xml-name-validator@5.0.0: {} @@ -7748,8 +10161,22 @@ snapshots: yallist@3.1.1: {} + yaml@2.9.0: {} + + yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yargs@18.1.0: dependencies: cliui: 9.0.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 050bade..5044664 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,8 +2,11 @@ packages: - packages/* - examples/* +verifyDepsBeforeRun: false + allowBuilds: '@parcel/watcher': false esbuild: true + nx: true sharp: false workerd: true diff --git a/project.json b/project.json new file mode 100644 index 0000000..a2617c0 --- /dev/null +++ b/project.json @@ -0,0 +1,169 @@ +{ + "$schema": "./node_modules/nx/schemas/project-schema.json", + "name": "charts-workspace", + "root": ".", + "targets": { + "format-check": { + "executor": "nx:run-commands", + "options": { + "command": "prettier --check ." + } + }, + "docs-check": { + "executor": "nx:run-commands", + "options": { + "command": "node scripts/check-docs.mjs" + } + }, + "typecheck": { + "executor": "nx:run-commands", + "options": { + "command": "tsc --noEmit -p tsconfig.json" + } + }, + "test-dom": { + "executor": "nx:run-commands", + "options": { + "command": "vitest run --config vitest.config.ts" + } + }, + "test-solid": { + "executor": "nx:run-commands", + "options": { + "command": "vitest run --config vitest.solid.config.ts" + } + }, + "test-solid-ssr": { + "executor": "nx:run-commands", + "options": { + "command": "vitest run --config vitest.solid-ssr.config.ts" + } + }, + "test-svelte": { + "executor": "nx:run-commands", + "options": { + "command": "vitest run --config vitest.svelte.config.ts" + } + }, + "test-svelte-ssr": { + "executor": "nx:run-commands", + "options": { + "command": "vitest run --config vitest.svelte-ssr.config.ts" + } + }, + "test-octane": { + "executor": "nx:run-commands", + "options": { + "command": "vitest run --config vitest.octane.config.ts" + } + }, + "test-octane-client": { + "executor": "nx:run-commands", + "options": { + "command": "vitest run --config vitest.octane-client.config.ts" + } + }, + "test-unit": { + "executor": "nx:run-commands", + "dependsOn": [ + "test-dom", + "test-solid", + "test-solid-ssr", + "test-svelte", + "test-svelte-ssr", + "test-octane", + "test-octane-client" + ], + "options": { + "command": "node -e \"console.log('Unit test targets passed.')\"" + }, + "cache": false + }, + "adapters-check": { + "executor": "nx:run-commands", + "options": { + "command": "node scripts/check-framework-adapters.mjs" + } + }, + "package-check": { + "executor": "nx:run-commands", + "options": { + "command": "node scripts/check-packed-consumers.mjs && node scripts/check-framework-adapters.mjs" + } + }, + "bundle": { + "executor": "nx:run-commands", + "options": { + "command": "node scripts/measure-bundles.mjs" + } + }, + "bundle-check": { + "executor": "nx:run-commands", + "options": { + "command": "node scripts/measure-bundles.mjs --check" + } + }, + "benchmark-check": { + "executor": "nx:run-commands", + "options": { + "command": "node scripts/compare-chart-libraries.mjs --check" + } + }, + "catalog-check": { + "executor": "nx:run-commands", + "options": { + "command": "pnpm --filter @charts-poc/conformance-example check:catalog" + } + }, + "catalog-build": { + "executor": "nx:run-commands", + "options": { + "command": "CATALOG_BASE_PATH=/charts/catalog/ pnpm --filter @charts-poc/conformance-example build" + } + }, + "catalog-loading-check": { + "executor": "nx:run-commands", + "dependsOn": ["catalog-build"], + "options": { + "command": "node scripts/check-catalog-loading.mjs" + } + }, + "workspace-diff-check": { + "executor": "nx:run-commands", + "options": { + "command": "git diff --check" + }, + "cache": false + }, + "release-artifacts": { + "executor": "nx:run-commands", + "options": { + "command": "node scripts/build-release-artifacts.mjs" + } + }, + "ci": { + "executor": "nx:run-commands", + "dependsOn": [ + "format-check", + "docs-check", + "typecheck", + "test-dom", + "test-solid", + "test-solid-ssr", + "test-svelte", + "test-svelte-ssr", + "test-octane", + "test-octane-client", + "package-check", + "bundle-check", + "catalog-check", + "catalog-loading-check", + "workspace-diff-check" + ], + "options": { + "command": "node -e \"console.log('CI targets passed.')\"" + }, + "cache": false + } + } +} diff --git a/scripts/benchmark/filters.mjs b/scripts/benchmark/filters.mjs index 36f87e0..97f1ad2 100644 --- a/scripts/benchmark/filters.mjs +++ b/scripts/benchmark/filters.mjs @@ -9,6 +9,29 @@ export function assertKnownFilterValues(filter, availableValues, label) { ) } +export function parseShard(value) { + if (value === undefined) return undefined + const match = /^(\d+)\/(\d+)$/.exec(value) + if (!match) { + throw new Error(`Invalid shard "${value}". Expected /.`) + } + const index = Number(match[1]) + const total = Number(match[2]) + if (index < 1 || total < 1 || index > total) { + throw new Error( + `Invalid shard "${value}". Index must be between 1 and ${total}.`, + ) + } + return { index, total } +} + +export function selectShard(values, shard) { + if (!shard) return values + return values.filter( + (_value, index) => index % shard.total === shard.index - 1, + ) +} + function pluralize(value, count) { return count === 1 ? value : `${value}s` } diff --git a/scripts/benchmark/filters.test.mjs b/scripts/benchmark/filters.test.mjs index 796cd68..e270924 100644 --- a/scripts/benchmark/filters.test.mjs +++ b/scripts/benchmark/filters.test.mjs @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { assertKnownFilterValues } from './filters.mjs' +import { assertKnownFilterValues, parseShard, selectShard } from './filters.mjs' describe('assertKnownFilterValues', () => { it('accepts an absent or completely known filter', () => { @@ -23,3 +23,26 @@ describe('assertKnownFilterValues', () => { ) }) }) + +describe('benchmark shards', () => { + it('parses one-based shards and partitions ordered values exactly once', () => { + expect(parseShard('2/3')).toEqual({ index: 2, total: 3 }) + const values = Array.from({ length: 10 }, (_, index) => index) + const shards = [1, 2, 3].map((index) => + selectShard(values, { index, total: 3 }), + ) + expect(shards).toEqual([ + [0, 3, 6, 9], + [1, 4, 7], + [2, 5, 8], + ]) + expect(shards.flat().sort((left, right) => left - right)).toEqual(values) + }) + + it.each(['0/3', '4/3', '1/0', 'one/two', '1'])( + 'rejects invalid shard %s', + (value) => { + expect(() => parseShard(value)).toThrow(/Invalid shard/) + }, + ) +}) diff --git a/scripts/check-framework-adapters.mjs b/scripts/check-framework-adapters.mjs index 6a51acc..f7cb438 100644 --- a/scripts/check-framework-adapters.mjs +++ b/scripts/check-framework-adapters.mjs @@ -40,19 +40,28 @@ const standardPackages = [ ['lit-charts'], ['alpine-charts'], ] +let pnpmQueue = Promise.resolve() try { await mkdir(tarballDirectory, { recursive: true }) - for (const [directory, jsxImportSource] of standardPackages) { - await buildStandardPackage(directory, jsxImportSource) - } - await buildSolidPackage() - await buildSveltePackage() - await buildAngularPackage() + await runWithConcurrency( + [ + ...standardPackages.map( + ([directory, jsxImportSource]) => + () => + buildStandardPackage(directory, jsxImportSource), + ), + buildSolidPackage, + buildSveltePackage, + buildAngularPackage, + ], + 4, + ) - for (const directory of packageNames) { - await verifyPackage(directory) - } + await runWithConcurrency( + packageNames.map((directory) => () => verifyPackage(directory)), + 4, + ) console.log( `Framework package gate passed for ${packageNames.length} adapters.`, @@ -61,6 +70,22 @@ try { await rm(temporaryDirectory, { recursive: true, force: true }) } +async function runWithConcurrency(operations, concurrency) { + let nextIndex = 0 + await Promise.all( + Array.from( + { length: Math.min(concurrency, operations.length) }, + async () => { + while (nextIndex < operations.length) { + const index = nextIndex + nextIndex += 1 + await operations[index]() + } + }, + ), + ) +} + async function buildStandardPackage(directory, jsxImportSource) { const packageRoot = resolve(root, 'packages', directory) const sourceRoot = resolve(packageRoot, 'src') @@ -114,8 +139,7 @@ async function buildSolidPackage() { } async function buildSveltePackage() { - await run( - 'pnpm', + await runPnpm( [ '--filter', '@tanstack/svelte-charts', @@ -126,8 +150,7 @@ async function buildSveltePackage() { ], root, ) - await run( - 'pnpm', + await runPnpm( [ '--filter', '@tanstack/svelte-charts', @@ -143,8 +166,7 @@ async function buildSveltePackage() { } async function buildAngularPackage() { - await run( - 'pnpm', + await runPnpm( [ '--filter', '@tanstack/angular-charts', @@ -219,8 +241,7 @@ async function verifyPackage(directory) { tarballDirectory, `${directory}-${manifest.version}.tgz`, ) - const { stdout } = await run( - 'pnpm', + const { stdout } = await runPnpm( ['pack', '--out', tarball, '--json'], packageRoot, ) @@ -333,6 +354,12 @@ async function run(command, args, cwd) { }) } +function runPnpm(args, cwd) { + const operation = pnpmQueue.then(() => run('pnpm', args, cwd)) + pnpmQueue = operation.catch(() => undefined) + return operation +} + function formatDiagnostics(diagnostics) { return ts.formatDiagnosticsWithColorAndContext(diagnostics, { getCanonicalFileName: (file) => file, diff --git a/scripts/ci-workflow.test.mjs b/scripts/ci-workflow.test.mjs new file mode 100644 index 0000000..b6c62f8 --- /dev/null +++ b/scripts/ci-workflow.test.mjs @@ -0,0 +1,307 @@ +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' +import { resolve } from 'node:path' + +const { describe, test } = process.env.VITEST + ? await import('vitest') + : await import('node:test') + +const workflow = await readFile( + resolve( + import.meta.dirname, + '../.github/workflows/chart-library-benchmarks.yml', + ), + 'utf8', +) + +describe('CI workflow contract', () => { + test('uses a read-only default and cancels stale validation runs', () => { + assert.match(workflow, /^permissions:\s*\n\s+contents:\s*read\s*$/m) + assert.equal((workflow.match(/contents:\s*write/g) ?? []).length, 1) + assert.doesNotMatch(workflow, /id-token:\s*write/) + assert.doesNotMatch(workflow, /pull-requests:\s*write/) + assert.doesNotMatch(workflow, /secrets\.|NX_CLOUD_ACCESS_TOKEN/) + assert.match(workflow, /concurrency:[\s\S]*cancel-in-progress:\s*true/) + assertPinnedExternalActions(workflow) + }) + + test('runs static, bundle, comparison, conformance, and stress partitions independently', () => { + for (const name of [ + 'static', + 'bundle-baseline', + 'compare', + 'conformance', + 'stress', + ]) { + assert.doesNotMatch( + job(name), + /^\s*needs:/m, + `${name} must not wait behind another CI partition`, + ) + } + + for (const name of ['compare', 'conformance', 'stress']) { + assert.match(job(name), /uses:\s*\.\/\.github\/actions\/setup/) + assert.match(job(name), /playwright:\s*['"]true['"]/) + } + assert.doesNotMatch(job('static'), /playwright:\s*['"]true['"]/) + assert.doesNotMatch(job('bundle-baseline'), /playwright:\s*['"]true['"]/) + }) + + test('uses affected checks only for pull requests and builds the main catalog fully', () => { + const staticChecks = job('static') + assert.match(staticChecks, /fetch-depth:\s*0/) + assert.match(staticChecks, /persist-credentials:\s*false/) + assert.match(staticChecks, /nrwl\/nx-set-shas@[0-9a-f]{40,64}/) + assert.match(staticChecks, /if:\s*github\.event_name == 'pull_request'/) + assert.match(staticChecks, /run:\s*pnpm ci:pr/) + assert.match(staticChecks, /if:\s*github\.event_name != 'pull_request'/) + assert.match(staticChecks, /run:\s*pnpm ci:all/) + assert.match( + staticChecks, + /if:\s*github\.event_name == 'push' && github\.ref == 'refs\/heads\/main'/, + ) + assert.match(staticChecks, /name:\s*charts-catalog-\${{ github\.sha }}/) + assert.match(staticChecks, /path:\s*\.catalog-artifact/) + }) + + test('shards comparison by chart and conformance into eight partitions', () => { + const comparison = job('compare') + assert.match(comparison, /fail-fast:\s*false/) + assert.deepEqual(scalarList(comparison, 'chart'), [ + 'line', + 'bar', + 'area', + 'scatter', + ]) + assert.match(comparison, /--profile=ci --chart=\${{ matrix\.chart }}/) + assert.match( + comparison, + /name:\s*chart-library-comparison-\${{ matrix\.chart }}-\${{ github\.run_id }}/, + ) + + const conformance = job('conformance') + assert.match(conformance, /fail-fast:\s*false/) + assert.deepEqual(scalarList(conformance, 'shard'), [ + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + ]) + assert.match(conformance, /Conformance \(\${{ matrix\.shard }}\/8\)/) + assert.match( + conformance, + /pnpm conformance:quick -- --shard=\${{ matrix\.shard }}\/8/, + ) + assert.match( + conformance, + /pnpm conformance -- --shard=\${{ matrix\.shard }}\/8/, + ) + assert.match( + conformance, + /name:\s*chart-library-conformance-\${{ matrix\.shard }}-\${{ github\.run_id }}/, + ) + }) + + test('covers every stress workload exactly once across named shards', () => { + const stress = job('stress') + assert.match(stress, /fail-fast:\s*false/) + assert.deepEqual(matrixIncludes(stress), [ + { + name: 'raw', + workloads: 'raw-line,raw-scatter,interactive-scatter', + }, + { + name: 'envelopes', + workloads: 'binned-density,pixel-envelope,viewport-envelope', + }, + { + name: 'transforms', + workloads: 'stats-multi-series-line,rolling-keyed-window,histogram-128', + }, + { + name: 'collections', + workloads: 'top-categories,dashboard-lines', + }, + ]) + assert.match( + stress, + /benchmark:stress:quick -- --workload=\${{ matrix\.workloads }}/, + ) + assert.match( + stress, + /benchmark:stress:standard -- --workload=\${{ matrix\.workloads }}/, + ) + assert.match( + stress, + /name:\s*chart-library-stress-\${{ matrix\.name }}-\${{ github\.run_id }}/, + ) + + const workloads = matrixIncludes(stress).flatMap((entry) => + entry.workloads.split(','), + ) + assert.equal(workloads.length, new Set(workloads).size) + }) + + test('exposes one stable CI gate that requires every partition', () => { + const aggregate = job('ci') + assert.match(aggregate, /if:\s*always\(\)/) + assert.deepEqual(needs(aggregate), [ + 'static', + 'bundle-baseline', + 'compare', + 'conformance', + 'stress', + ]) + for (const [variable, dependency] of [ + ['STATIC_RESULT', 'static'], + ['BUNDLE_RESULT', 'bundle-baseline'], + ['COMPARISON_RESULT', 'compare'], + ['CONFORMANCE_RESULT', 'conformance'], + ['STRESS_RESULT', 'stress'], + ]) { + assert.match( + aggregate, + new RegExp( + `${variable}:\\s*\\$\\{\\{\\s*needs\\.${escapeRegExp(dependency)}\\.result\\s*\\}\\}`, + ), + ) + } + assert.match(aggregate, /test "\$result" = success/) + assert.doesNotMatch(aggregate, /contents:\s*write/) + }) + + test('publishes the catalog only from a successful exact-main CI artifact', () => { + const publish = job('publish-catalog') + assert.match( + publish, + /if:\s*github\.event_name == 'push' && github\.ref == 'refs\/heads\/main'/, + ) + assert.deepEqual(needs(publish), ['static', 'ci']) + assert.match(publish, /contents:\s*write/) + assert.match(publish, /cancel-in-progress:\s*false/) + assert.match(publish, /name:\s*charts-catalog-\${{ github\.sha }}/) + assert.match(publish, /path:\s*\.catalog-artifact/) + assert.match(publish, /if \[ "\$source_revision" != "\$GITHUB_SHA" \]/) + assert.match( + publish, + /git merge-base --is-ancestor "\$source_revision" origin\/main/, + ) + assert.match( + publish, + /git(?: -C "\$publication_dir")? push origin "HEAD:\$CATALOG_BRANCH"/, + ) + assert.doesNotMatch(publish, /id-token:\s*write/) + }) +}) + +function job(name) { + const lines = workflow.split(/\r?\n/) + const jobsIndex = lines.findIndex((line) => /^\s*jobs:\s*$/.test(line)) + assert.notEqual(jobsIndex, -1, 'workflow must define jobs') + const jobsIndent = indentation(lines[jobsIndex]) + const start = lines.findIndex( + (line, index) => + index > jobsIndex && + indentation(line) > jobsIndent && + new RegExp(`^\\s*${escapeRegExp(name)}:\\s*$`).test(line), + ) + assert.notEqual(start, -1, `workflow must define job ${name}`) + const jobIndent = indentation(lines[start]) + let end = lines.length + for (let index = start + 1; index < lines.length; index += 1) { + if (!lines[index].trim() || lines[index].trimStart().startsWith('#')) { + continue + } + const indent = indentation(lines[index]) + if (indent < jobIndent) { + end = index + break + } + if (indent === jobIndent && /^\s*[A-Za-z0-9_-]+:\s*$/.test(lines[index])) { + end = index + break + } + } + return lines.slice(start, end).join('\n') +} + +function needs(block) { + const scalar = block.match(/^\s*needs:\s*([A-Za-z0-9_-]+)\s*$/m) + if (scalar) return [scalar[1]] + return scalarList(block, 'needs') +} + +function scalarList(block, key) { + const lines = block.split(/\r?\n/) + const start = lines.findIndex((line) => + new RegExp(`^\\s*${escapeRegExp(key)}:\\s*$`).test(line), + ) + assert.notEqual(start, -1, `expected ${key} list`) + const keyIndent = indentation(lines[start]) + const values = [] + for (let index = start + 1; index < lines.length; index += 1) { + if (!lines[index].trim() || lines[index].trimStart().startsWith('#')) { + continue + } + if (indentation(lines[index]) <= keyIndent) break + const item = lines[index].match(/^\s*-\s*([^#]+?)\s*$/) + if (item) values.push(item[1].replace(/^['"]|['"]$/g, '')) + } + return values +} + +function matrixIncludes(block) { + const lines = block.split(/\r?\n/) + const includeIndex = lines.findIndex((line) => /^\s*include:\s*$/.test(line)) + assert.notEqual(includeIndex, -1, 'stress matrix must use include entries') + const includeIndent = indentation(lines[includeIndex]) + const entries = [] + let current + for (let index = includeIndex + 1; index < lines.length; index += 1) { + if (!lines[index].trim() || lines[index].trimStart().startsWith('#')) { + continue + } + if (indentation(lines[index]) <= includeIndent) break + const name = lines[index].match(/^\s*-\s*name:\s*([^#]+?)\s*$/) + if (name) { + current = { name: unquote(name[1]) } + entries.push(current) + continue + } + const workloads = lines[index].match(/^\s*workloads:\s*([^#]+?)\s*$/) + if (workloads && current) current.workloads = unquote(workloads[1]) + } + return entries +} + +function assertPinnedExternalActions(source) { + const uses = [...source.matchAll(/^\s*uses:\s*([^\s#]+)\s*(?:#.*)?$/gm)].map( + (match) => match[1], + ) + assert.ok(uses.length > 0, 'workflow must use actions') + for (const action of uses) { + if (action.startsWith('./')) continue + assert.match( + action, + /@[0-9a-f]{40,64}$/, + `${action} must be pinned to an immutable commit`, + ) + } +} + +function unquote(value) { + return value.replace(/^['"]|['"]$/g, '') +} + +function indentation(line) { + return line.match(/^\s*/)[0].length +} + +function escapeRegExp(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} diff --git a/scripts/compare-chart-libraries.mjs b/scripts/compare-chart-libraries.mjs index 2ccac74..d2281cd 100644 --- a/scripts/compare-chart-libraries.mjs +++ b/scripts/compare-chart-libraries.mjs @@ -278,66 +278,76 @@ if (args.has('--check')) { } async function buildCases(benchmarkCases) { - const results = [] - - for (const benchmarkCase of benchmarkCases) { - const outfile = resolve(caseOutputDirectory, `${benchmarkCase.id}.js`) - const buildResult = await bundleCase(benchmarkCase, outfile) - const contents = await readFile(outfile) - const sharedExternals = benchmarkCase.library.sharedExternals ?? [] - let incrementalContents = contents - - if (sharedExternals.length) { - const incrementalOutfile = resolve( - caseOutputDirectory, - `${benchmarkCase.id}-incremental.js`, - ) - await bundleCase(benchmarkCase, incrementalOutfile, sharedExternals) - incrementalContents = await readFile(incrementalOutfile) - } + const results = new Array(benchmarkCases.length) + let nextIndex = 0 + const workerCount = Math.min(4, benchmarkCases.length) - results.push({ - id: benchmarkCase.id, - library: benchmarkCase.library.id, - libraryLabel: benchmarkCase.library.label, - chartType: benchmarkCase.chartType, - tier: benchmarkCase.tier, - minifiedBytes: contents.byteLength, - gzipBytes: gzipSync(contents).byteLength, - brotliBytes: brotliCompressSync(contents).byteLength, - incrementalBytes: incrementalContents.byteLength, - incrementalGzipBytes: gzipSync(incrementalContents).byteLength, - incrementalBrotliBytes: - brotliCompressSync(incrementalContents).byteLength, - bundledModuleCount: Object.values(buildResult.metafile.outputs).reduce( - (total, output) => - total + - Object.values(output.inputs).filter( - (input) => input.bytesInOutput > 0, - ).length, - 0, - ), - stressSupportBytes: Object.values(buildResult.metafile.outputs).reduce( - (total, output) => - total + - Object.entries(output.inputs) - .filter( - ([input]) => - input.includes('/comparison/stress/') || - input.includes('\\comparison\\stress\\'), - ) - .reduce( - (outputTotal, [, input]) => outputTotal + input.bytesInOutput, - 0, - ), - 0, - ), - }) - } + await Promise.all( + Array.from({ length: workerCount }, async () => { + while (nextIndex < benchmarkCases.length) { + const index = nextIndex + nextIndex += 1 + results[index] = await buildCase(benchmarkCases[index]) + } + }), + ) return results } +async function buildCase(benchmarkCase) { + const outfile = resolve(caseOutputDirectory, `${benchmarkCase.id}.js`) + const buildResult = await bundleCase(benchmarkCase, outfile) + const contents = await readFile(outfile) + const sharedExternals = benchmarkCase.library.sharedExternals ?? [] + let incrementalContents = contents + + if (sharedExternals.length) { + const incrementalOutfile = resolve( + caseOutputDirectory, + `${benchmarkCase.id}-incremental.js`, + ) + await bundleCase(benchmarkCase, incrementalOutfile, sharedExternals) + incrementalContents = await readFile(incrementalOutfile) + } + + return { + id: benchmarkCase.id, + library: benchmarkCase.library.id, + libraryLabel: benchmarkCase.library.label, + chartType: benchmarkCase.chartType, + tier: benchmarkCase.tier, + minifiedBytes: contents.byteLength, + gzipBytes: gzipSync(contents).byteLength, + brotliBytes: brotliCompressSync(contents).byteLength, + incrementalBytes: incrementalContents.byteLength, + incrementalGzipBytes: gzipSync(incrementalContents).byteLength, + incrementalBrotliBytes: brotliCompressSync(incrementalContents).byteLength, + bundledModuleCount: Object.values(buildResult.metafile.outputs).reduce( + (total, output) => + total + + Object.values(output.inputs).filter((input) => input.bytesInOutput > 0) + .length, + 0, + ), + stressSupportBytes: Object.values(buildResult.metafile.outputs).reduce( + (total, output) => + total + + Object.entries(output.inputs) + .filter( + ([input]) => + input.includes('/comparison/stress/') || + input.includes('\\comparison\\stress\\'), + ) + .reduce( + (outputTotal, [, input]) => outputTotal + input.bytesInOutput, + 0, + ), + 0, + ), + } +} + async function bundleCase(benchmarkCase, outfile, external = []) { return build({ stdin: { diff --git a/scripts/compare-plot-catalog.mjs b/scripts/compare-plot-catalog.mjs index 71ff13a..18bad87 100644 --- a/scripts/compare-plot-catalog.mjs +++ b/scripts/compare-plot-catalog.mjs @@ -16,7 +16,11 @@ import { createCatalogSourceModules, } from './catalog-source-files.mjs' import { conformanceArtifactStem } from './benchmark/conformance-artifacts.mjs' -import { assertKnownFilterValues } from './benchmark/filters.mjs' +import { + assertKnownFilterValues, + parseShard, + selectShard, +} from './benchmark/filters.mjs' const root = resolve(import.meta.dirname, '..') const conformanceDirectory = resolve(root, 'benchmarks/conformance') @@ -111,6 +115,7 @@ if (!profile) { } const sizeOnly = process.argv.includes('--size-only') const caseFilter = csvOption('--case') +const shard = parseShard(optionValue('--shard')) await Promise.all([ mkdir(bundleDirectory, { recursive: true }), @@ -126,8 +131,9 @@ assertKnownFilterValues( allCases.map((entry) => entry.id), 'case', ) -const selectedCases = allCases.filter( - (entry) => !caseFilter || caseFilter.has(entry.id), +const selectedCases = selectShard( + allCases.filter((entry) => !caseFilter || caseFilter.has(entry.id)), + shard, ) if (!selectedCases.length) { throw new Error('The case filter did not match a conformance case.') @@ -195,7 +201,9 @@ const result = { createdAt: new Date().toISOString(), profile: profileName, filters: { - cases: caseFilter ? selectedCases.map((entry) => entry.id).sort() : [], + cases: + caseFilter || shard ? selectedCases.map((entry) => entry.id).sort() : [], + shard: shard ? `${shard.index}/${shard.total}` : null, }, environment: { node: process.version, @@ -260,6 +268,19 @@ await Promise.all([ ]) console.log(markdown) +const failedVisuals = visualChecks.filter((check) => check.status === 'fail') +const failedBehaviors = behaviorChecks.filter( + (check) => check.status === 'fail', +) +if (failedVisuals.length || failedBehaviors.length) { + throw new Error( + `Conformance failed for ${failedVisuals.length} visual and ${failedBehaviors.length} interaction checks: ${[ + ...failedVisuals.map((check) => `visual:${check.caseId}`), + ...failedBehaviors.map((check) => `interaction:${check.caseId}`), + ].join(', ')}`, + ) +} + async function readCases() { const entries = await readdir(casesDirectory, { withFileTypes: true }) const cases = [] diff --git a/scripts/publish-release.mjs b/scripts/publish-release.mjs index 60bfde6..94aa350 100644 --- a/scripts/publish-release.mjs +++ b/scripts/publish-release.mjs @@ -51,20 +51,35 @@ assert.match( validateTrustedPublishingNpmVersion((await runNpm(['--version'])).stdout) const states = new Map() -for (const artifact of artifacts) { - const registry = await readRegistryPackage(artifact.name, version) - if (registry === null) { - states.set(artifact.name, 'missing') - continue - } - validateRegistryPackage(artifact, registry) - states.set(artifact.name, 'published') -} +await Promise.all( + artifacts.map(async (artifact) => { + const registry = await readRegistryPackage(artifact.name, version) + if (registry === null) { + states.set(artifact.name, 'missing') + return + } + validateRegistryPackage(artifact, registry) + states.set(artifact.name, 'published') + }), +) -for (const artifact of artifacts) { +const coreArtifact = artifacts.find( + (artifact) => artifact.name === '@tanstack/charts', +) +assert.ok(coreArtifact, 'Release artifacts must include @tanstack/charts') +await publishArtifact(coreArtifact) +await runWithConcurrency( + artifacts.filter((artifact) => artifact !== coreArtifact), + 3, + publishArtifact, +) + +console.log(`Published ${manifest.tag} with verified integrity and provenance.`) + +async function publishArtifact(artifact) { if (states.get(artifact.name) === 'published') { console.log(`Already published: ${artifact.name}@${version}`) - continue + return } await runNpm([ @@ -81,7 +96,18 @@ for (const artifact of artifacts) { console.log(`Published: ${artifact.name}@${version}`) } -console.log(`Published ${manifest.tag} with verified integrity and provenance.`) +async function runWithConcurrency(values, concurrency, operation) { + let nextIndex = 0 + await Promise.all( + Array.from({ length: Math.min(concurrency, values.length) }, async () => { + while (nextIndex < values.length) { + const index = nextIndex + nextIndex += 1 + await operation(values[index]) + } + }), + ) +} async function waitForRegistryPackage(artifact, releaseVersion) { let lastResult = null diff --git a/scripts/release-package-config.mjs b/scripts/release-package-config.mjs index 578694f..0ce2004 100644 --- a/scripts/release-package-config.mjs +++ b/scripts/release-package-config.mjs @@ -38,11 +38,7 @@ export async function readReleasePackages(repositoryRoot) { config.name, `${manifestPath} has the wrong name`, ) - assert.match( - manifest.version, - /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/, - `${manifest.name} requires a publishable version`, - ) + assertStableReleaseVersion(manifest.name, manifest.version) assert.equal( manifest.private, false, @@ -109,3 +105,11 @@ export async function readReleasePackages(repositoryRoot) { export function releaseTag(version) { return `v${version}` } + +export function assertStableReleaseVersion(packageName, version) { + assert.match( + version, + /^\d+\.\d+\.\d+$/, + `${packageName} requires a stable release version`, + ) +} diff --git a/scripts/release-package-config.test.mjs b/scripts/release-package-config.test.mjs new file mode 100644 index 0000000..a393467 --- /dev/null +++ b/scripts/release-package-config.test.mjs @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { + assertStableReleaseVersion, + releaseTag, +} from './release-package-config.mjs' + +describe('release package configuration', () => { + it('accepts stable releases and creates their tag', () => { + expect(() => + assertStableReleaseVersion('@tanstack/charts', '0.0.2'), + ).not.toThrow() + expect(releaseTag('0.0.2')).toBe('v0.0.2') + }) + + it('rejects prereleases before they can publish under latest', () => { + expect(() => + assertStableReleaseVersion('@tanstack/charts', '0.0.2-rc.0'), + ).toThrow('requires a stable release version') + }) +}) diff --git a/scripts/release-security.mjs b/scripts/release-security.mjs index 829243b..d704074 100644 --- a/scripts/release-security.mjs +++ b/scripts/release-security.mjs @@ -13,7 +13,11 @@ export function validateReleaseEnvironment({ expectedRevision = env.GITHUB_SHA, }) { assert.equal(env.GITHUB_ACTIONS, 'true', 'Release requires GitHub Actions') - assert.equal(env.GITHUB_EVENT_NAME, 'push', 'Release requires a push event') + assert.ok( + env.GITHUB_EVENT_NAME === 'push' || + env.GITHUB_EVENT_NAME === 'workflow_dispatch', + 'Release requires a tag push or explicit workflow dispatch', + ) assert.equal(env.GITHUB_REF_TYPE, 'tag', 'Release requires a tag ref') assert.equal( env.GITHUB_REF_NAME, diff --git a/scripts/release-security.test.mjs b/scripts/release-security.test.mjs index 4c6b419..238c66f 100644 --- a/scripts/release-security.test.mjs +++ b/scripts/release-security.test.mjs @@ -46,6 +46,16 @@ describe('release environment', () => { expectedRevision: revision, }), ).toThrow(/refs\/tags\/v0\.0\.1/) + expect(() => + validateReleaseEnvironment({ + env: { + ...releaseEnvironment(), + GITHUB_EVENT_NAME: 'workflow_dispatch', + }, + expectedTag: tag, + expectedRevision: revision, + }), + ).not.toThrow() }) it('requires an npm version with trusted publishing support', () => { diff --git a/scripts/release-status.mjs b/scripts/release-status.mjs new file mode 100644 index 0000000..50bb500 --- /dev/null +++ b/scripts/release-status.mjs @@ -0,0 +1,232 @@ +import assert from 'node:assert/strict' +import { execFile } from 'node:child_process' +import { appendFile, readFile, readdir } from 'node:fs/promises' +import { resolve } from 'node:path' +import { pathToFileURL } from 'node:url' +import { promisify } from 'node:util' +import semver from 'semver' +import { readReleasePackages, releaseTag } from './release-package-config.mjs' +import { releaseNotes } from './sync-release-changelog.mjs' + +const execFileAsync = promisify(execFile) +const registry = 'https://registry.npmjs.org' + +export function classifyReleaseStatus({ + expectedRevision, + hasPendingChangesets, + packageStates, + releaseExists, + tagRevision, +}) { + if (hasPendingChangesets) { + return { + createTag: false, + dispatch: false, + reason: 'pending-changesets', + } + } + + const published = packageStates.filter((state) => state === 'published') + const missing = packageStates.filter((state) => state === 'missing') + assert.equal( + published.length + missing.length, + packageStates.length, + 'Unknown package release state', + ) + + if (releaseExists) { + assert.ok(tagRevision, 'A GitHub release requires an annotated tag') + assert.equal( + missing.length, + 0, + 'A GitHub release exists while npm packages are missing', + ) + return { + createTag: false, + dispatch: false, + reason: 'released', + } + } + + if (tagRevision !== null && expectedRevision !== undefined) { + assert.equal( + tagRevision, + expectedRevision, + 'The existing release tag points to a different revision', + ) + } + + return { + createTag: tagRevision === null, + dispatch: true, + reason: missing.length ? 'publish' : 'finalize', + } +} + +export async function releaseStatus({ + env = process.env, + repositoryRoot = resolve(import.meta.dirname, '..'), +} = {}) { + const packages = await readReleasePackages(repositoryRoot) + const version = packages[0].manifest.version + const tag = releaseTag(version) + releaseNotes( + await readFile(resolve(repositoryRoot, 'CHANGELOG.md'), 'utf8'), + version, + ) + const pendingChangesets = ( + await readdir(resolve(repositoryRoot, '.changeset')) + ) + .filter((entry) => entry.endsWith('.md') && entry !== 'README.md') + .sort() + + const packageStates = await Promise.all( + packages.map(async (packageInfo) => { + const metadata = await readRegistryPackage( + packageInfo.name, + packageInfo.manifest.version, + ) + return metadata === null ? 'missing' : 'published' + }), + ) + + const latestVersion = await readLatestVersion(packages[0].name) + if ( + packageStates.includes('missing') && + latestVersion !== null && + semver.gt(latestVersion, version) + ) { + throw new Error( + `Refusing to release ${version}; npm already has newer ${packages[0].name}@${latestVersion}.`, + ) + } + + const tagRevision = await readTagRevision(repositoryRoot, tag) + const releaseExists = await githubReleaseExists(env, tag) + const status = classifyReleaseStatus({ + expectedRevision: env.RELEASE_REVISION, + hasPendingChangesets: pendingChangesets.length > 0, + packageStates, + releaseExists, + tagRevision, + }) + return { + ...status, + pendingChangesets, + publishedPackages: packageStates.filter((state) => state === 'published') + .length, + tag, + tagRevision, + totalPackages: packages.length, + version, + } +} + +async function readRegistryPackage(name, version) { + const response = await fetch( + `${registry}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`, + { headers: { accept: 'application/json' } }, + ) + if (response.status === 404) return null + assert.equal( + response.ok, + true, + `npm returned HTTP ${response.status} for ${name}@${version}`, + ) + return response.json() +} + +async function readLatestVersion(name) { + const response = await fetch( + `${registry}/${encodeURIComponent(name)}/latest`, + { headers: { accept: 'application/json' } }, + ) + if (response.status === 404) return null + assert.equal( + response.ok, + true, + `npm returned HTTP ${response.status} for ${name}@latest`, + ) + const metadata = await response.json() + assert.ok( + semver.valid(metadata.version), + `${name}@latest returned invalid version ${metadata.version}`, + ) + return metadata.version +} + +async function readTagRevision(repositoryRoot, tag) { + const ref = `refs/tags/${tag}` + let stdout + try { + ;({ stdout } = await execFileAsync( + 'git', + ['ls-remote', '--exit-code', 'origin', ref, `${ref}^{}`], + { + cwd: repositoryRoot, + env: { ...process.env }, + maxBuffer: 5 * 1024 * 1024, + }, + )) + } catch (error) { + if (error?.code === 2) return null + throw error + } + const revisions = new Map( + stdout + .trim() + .split('\n') + .filter(Boolean) + .map((line) => { + const match = /^([0-9a-f]{40})\t(.+)$/.exec(line) + assert.ok(match, `Invalid git ls-remote line: ${line}`) + return [match[2], match[1]] + }), + ) + assert.ok(revisions.has(ref), `Remote tag ${ref} is malformed`) + assert.ok(revisions.has(`${ref}^{}`), `Remote tag ${ref} must be annotated`) + return revisions.get(`${ref}^{}`) +} + +async function githubReleaseExists(env, tag) { + const repository = env.GITHUB_REPOSITORY ?? 'TanStack/charts' + const response = await fetch( + `https://api.github.com/repos/${repository}/releases/tags/${encodeURIComponent(tag)}`, + { + headers: { + accept: 'application/vnd.github+json', + ...(env.GITHUB_TOKEN + ? { authorization: `Bearer ${env.GITHUB_TOKEN}` } + : {}), + 'x-github-api-version': '2022-11-28', + }, + }, + ) + if (response.status === 404) return false + assert.equal( + response.ok, + true, + `GitHub returned HTTP ${response.status} for release ${tag}`, + ) + return true +} + +async function writeOutputs(status, outputPath) { + const values = { + create_tag: status.createTag, + dispatch: status.dispatch, + reason: status.reason, + tag: status.tag, + version: status.version, + } + const output = Object.entries(values) + .map(([key, value]) => `${key}=${value}`) + .join('\n') + console.log(output) + if (outputPath) await appendFile(outputPath, `${output}\n`) +} + +const entrypoint = process.argv[1] +if (entrypoint && import.meta.url === pathToFileURL(resolve(entrypoint)).href) { + await writeOutputs(await releaseStatus(), process.env.GITHUB_OUTPUT) +} diff --git a/scripts/release-status.test.mjs b/scripts/release-status.test.mjs new file mode 100644 index 0000000..9827241 --- /dev/null +++ b/scripts/release-status.test.mjs @@ -0,0 +1,90 @@ +import { describe, expect, it } from 'vitest' +import { classifyReleaseStatus } from './release-status.mjs' + +describe('automated release status', () => { + it('waits while changesets still need a version pull request', () => { + expect( + classifyReleaseStatus({ + hasPendingChangesets: true, + packageStates: ['missing'], + releaseExists: false, + tagRevision: null, + }), + ).toEqual({ + createTag: false, + dispatch: false, + reason: 'pending-changesets', + }) + }) + + it('creates and dispatches a new release tag when packages are missing', () => { + expect( + classifyReleaseStatus({ + hasPendingChangesets: false, + packageStates: ['missing', 'missing'], + releaseExists: false, + tagRevision: null, + }), + ).toEqual({ + createTag: true, + dispatch: true, + reason: 'publish', + }) + }) + + it('can resume publishing or GitHub release creation idempotently', () => { + expect( + classifyReleaseStatus({ + expectedRevision: 'a'.repeat(40), + hasPendingChangesets: false, + packageStates: ['published', 'missing'], + releaseExists: false, + tagRevision: 'a'.repeat(40), + }), + ).toEqual({ + createTag: false, + dispatch: true, + reason: 'publish', + }) + expect( + classifyReleaseStatus({ + expectedRevision: 'a'.repeat(40), + hasPendingChangesets: false, + packageStates: ['published', 'published'], + releaseExists: false, + tagRevision: 'a'.repeat(40), + }), + ).toEqual({ + createTag: false, + dispatch: true, + reason: 'finalize', + }) + }) + + it('rejects dispatching an existing tag for a different revision', () => { + expect(() => + classifyReleaseStatus({ + expectedRevision: 'b'.repeat(40), + hasPendingChangesets: false, + packageStates: ['missing'], + releaseExists: false, + tagRevision: 'a'.repeat(40), + }), + ).toThrow('existing release tag points to a different revision') + }) + + it('does nothing after npm, the tag, and GitHub release all exist', () => { + expect( + classifyReleaseStatus({ + hasPendingChangesets: false, + packageStates: ['published', 'published'], + releaseExists: true, + tagRevision: 'a'.repeat(40), + }), + ).toEqual({ + createTag: false, + dispatch: false, + reason: 'released', + }) + }) +}) diff --git a/scripts/release-workflow.test.mjs b/scripts/release-workflow.test.mjs index 0e29842..5196392 100644 --- a/scripts/release-workflow.test.mjs +++ b/scripts/release-workflow.test.mjs @@ -1,58 +1,239 @@ +import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' -import { describe, expect, it } from 'vitest' + +const { describe, test } = process.env.VITEST + ? await import('vitest') + : await import('node:test') const workflow = await readFile( resolve(import.meta.dirname, '../.github/workflows/release.yml'), 'utf8', ) -describe('release workflow security boundary', () => { - it('keeps validation and post-publish verification outside OIDC', () => { - expect(job('validate')).not.toContain('id-token: write') - expect(job('verify')).not.toContain('id-token: write') - expect(job('release')).not.toContain('id-token: write') - expect(job('publish')).toContain('id-token: write') - expect(workflow.match(/id-token: write/g)).toHaveLength(1) +describe('release workflow contract', () => { + test('orchestrates versions and tags only from successful main CI', () => { + assert.match( + workflow, + /workflow_run:[\s\S]*workflows:[\s\S]*Chart library benchmarks[\s\S]*types:[\s\S]*completed/, + ) + assert.match(workflow, /push:[\s\S]*tags:[\s\S]*['"]v\*['"]/) + assert.match(workflow, /workflow_dispatch:/) + assert.match(workflow, /group:\s*charts-release\s*$/m) + assert.match(workflow, /cancel-in-progress:\s*false/) + + const version = job('version') + assert.match(version, /github\.event_name == 'workflow_run'/) + assert.match( + version, + /github\.event\.workflow_run\.conclusion == 'success'/, + ) + assert.match(version, /github\.event\.workflow_run\.event == 'push'/) + assert.match(version, /github\.event\.workflow_run\.head_branch == 'main'/) + assert.match( + version, + /ref:\s*\${{ github\.event\.workflow_run\.head_sha }}/, + ) + assert.match(version, /persist-credentials:\s*true/) + assert.match(version, /changesets\/action@[0-9a-f]{40,64}/) + assert.match(version, /version:\s*pnpm changeset:version/) + assert.match(version, /createGithubReleases:\s*false/) + assert.match(version, /contents:\s*write/) + assert.match(version, /pull-requests:\s*write/) + assert.doesNotMatch(version, /id-token:\s*write/) + assert.doesNotMatch(version, /actions:\s*write/) }) - it('publishes only checked artifacts from an exact-SHA checkout', () => { - const publish = job('publish') - expect(publish).toContain('needs: validate') - expect(publish).toContain('ref: ${{ github.sha }}') - expect(publish).toContain('actions/download-artifact@') - expect(publish).toContain('path: .release-artifacts') - expect(publish).not.toMatch(/\b(?:npm|pnpm) install\b/) - expect(publish).not.toContain('corepack enable') - expect(publish).not.toMatch(/\bpnpm (?:test|typecheck|docs:check)\b/) - expect(publish).toMatch( - /node scripts\/verify-release-revision\.mjs\s+node scripts\/publish-release\.mjs/, + test('creates an annotated tag and explicitly dispatches its release', () => { + const tag = job('tag') + assert.deepEqual(needs(tag), ['version']) + assert.match(tag, /github\.event\.workflow_run\.conclusion == 'success'/) + assert.match(tag, /github\.event\.workflow_run\.head_branch == 'main'/) + assert.match(tag, /ref:\s*\${{ github\.event\.workflow_run\.head_sha }}/) + assert.match(tag, /node scripts\/release-status\.mjs/) + assert.ok( + tag.indexOf('release-status.mjs') < tag.indexOf('git tag -a'), + 'release notes and registry state must be valid before a tag is created', + ) + assert.match( + tag, + /RELEASE_REVISION:\s*\${{ github\.event\.workflow_run\.head_sha }}/, + ) + assert.match(tag, /git tag -a "\$RELEASE_TAG" "\$RELEASE_REVISION"/) + assert.match(tag, /git push origin "refs\/tags\/\$RELEASE_TAG"/) + assert.match(tag, /gh workflow run release\.yml --ref "\$RELEASE_TAG"/) + assert.ok( + tag.indexOf('git tag -a') < tag.indexOf('gh workflow run release.yml'), + 'the annotated tag must exist before its tag-scoped release is dispatched', ) + assert.match(tag, /actions:\s*write/) + assert.match(tag, /contents:\s*write/) + assert.doesNotMatch(tag, /id-token:\s*write/) + assert.doesNotMatch(tag, /pull-requests:\s*write/) }) - it('gates the GitHub release on independent package verification and fresh refs', () => { - expect(job('verify')).toMatch( - /needs: publish[\s\S]*node scripts\/verify-published-release\.mjs/, + test('builds fresh artifacts only after tag and exact-SHA CI verification', () => { + const validate = job('validate') + assert.match( + validate, + /if:\s*github\.event_name == 'push' \|\| github\.event_name == 'workflow_dispatch'/, ) + assert.match(validate, /ref:\s*\${{ github\.sha }}/) + assert.match(validate, /persist-credentials:\s*false/) + assert.match(validate, /actions:\s*read/) + assert.match(validate, /contents:\s*read/) + assert.doesNotMatch(validate, /(?:actions|contents):\s*write/) + assert.doesNotMatch(validate, /id-token:\s*write/) + assert.match(validate, /node scripts\/verify-release-revision\.mjs/) + assert.match(validate, /node scripts\/verify-ci-success\.mjs/) + assert.match(validate, /node scripts\/build-release-artifacts\.mjs/) + assert.match(validate, /node scripts\/publish-release\.mjs --check/) + assert.match(validate, /actions\/upload-artifact@[0-9a-f]{40,64}/) + assert.match(validate, /name:\s*charts-release-\${{ github\.ref_name }}/) + assert.match(validate, /path:\s*\.release-artifacts/) + assert.doesNotMatch(validate, /actions\/download-artifact@/) + assert.ok( + validate.indexOf('verify-ci-success.mjs') < + validate.indexOf('build-release-artifacts.mjs'), + 'the exact revision must be CI-approved before package artifacts are built', + ) + }) + + test('isolates npm OIDC to artifact-only publication', () => { + const publish = job('publish') + assert.equal((workflow.match(/id-token:\s*write/g) ?? []).length, 1) + assert.match(publish, /id-token:\s*write/) + assert.match(publish, /contents:\s*read/) + assert.doesNotMatch(publish, /(?:actions|contents):\s*write/) + assert.deepEqual(needs(publish), ['validate']) + assert.match(publish, /if:\s*needs\.validate\.result == 'success'/) + assert.match(publish, /ref:\s*\${{ github\.sha }}/) + assert.match(publish, /persist-credentials:\s*false/) + assert.match(publish, /actions\/download-artifact@[0-9a-f]{40,64}/) + assert.match(publish, /name:\s*charts-release-\${{ github\.ref_name }}/) + assert.match(publish, /path:\s*\.release-artifacts/) + assert.match(publish, /node scripts\/verify-release-revision\.mjs/) + assert.match(publish, /node scripts\/publish-release\.mjs/) + assert.doesNotMatch(publish, /\.\/\.github\/actions\/setup/) + assert.doesNotMatch(publish, /\b(?:npm|pnpm) install\b/) + assert.doesNotMatch(publish, /\bcorepack enable\b/) + assert.doesNotMatch(publish, /\bpnpm (?:test|typecheck|docs:check)\b/) + assert.doesNotMatch(publish, /\bNPM_TOKEN\b|\bNODE_AUTH_TOKEN\b/) + assert.ok( + publish.indexOf('verify-release-revision.mjs') < + publish.indexOf('publish-release.mjs'), + 'publication must revalidate the tag before invoking npm', + ) + }) + + test('gates the GitHub release on independent registry verification', () => { + const verify = job('verify') + assert.deepEqual(needs(verify), ['publish']) + assert.match(verify, /ref:\s*\${{ github\.sha }}/) + assert.match(verify, /persist-credentials:\s*false/) + assert.match(verify, /actions\/download-artifact@[0-9a-f]{40,64}/) + assert.match(verify, /node scripts\/verify-published-release\.mjs/) + assert.doesNotMatch(verify, /id-token:\s*write/) + assert.doesNotMatch(verify, /contents:\s*write/) + const release = job('release') - expect(release).toContain('needs: verify') - expect(release).toContain('ref: ${{ github.sha }}') - expect(release).toMatch( - /node scripts\/verify-release-revision\.mjs[\s\S]*gh release create/, + assert.deepEqual(needs(release), ['verify']) + assert.match(release, /ref:\s*\${{ github\.sha }}/) + assert.match(release, /persist-credentials:\s*false/) + assert.match(release, /node scripts\/verify-release-revision\.mjs/) + assert.match(release, /node scripts\/write-release-notes\.mjs/) + assert.match(release, /gh release create "\$GITHUB_REF_NAME"/) + assert.match(release, /--verify-tag/) + assert.match(release, /--notes-file \/tmp\/charts-release-notes\.md/) + assert.doesNotMatch(release, /--notes-file CHANGELOG\.md/) + assert.doesNotMatch(release, /id-token:\s*write/) + assert.ok( + release.indexOf('verify-release-revision.mjs') < + release.indexOf('gh release create'), + 'the GitHub release must use freshly validated refs', ) }) + + test('pins every external action to an immutable revision', () => { + assertPinnedExternalActions(workflow) + }) }) function job(name) { - const lines = workflow.split('\n') - const start = lines.findIndex((line) => line === ` ${name}:`) - expect(start, `job ${name}`).toBeGreaterThan(-1) + const lines = workflow.split(/\r?\n/) + const jobsIndex = lines.findIndex((line) => /^\s*jobs:\s*$/.test(line)) + assert.notEqual(jobsIndex, -1, 'workflow must define jobs') + const jobsIndent = indentation(lines[jobsIndex]) + const start = lines.findIndex( + (line, index) => + index > jobsIndex && + indentation(line) > jobsIndent && + new RegExp(`^\\s*${escapeRegExp(name)}:\\s*$`).test(line), + ) + assert.notEqual(start, -1, `workflow must define job ${name}`) + const jobIndent = indentation(lines[start]) let end = lines.length for (let index = start + 1; index < lines.length; index += 1) { - if (/^ [a-z][a-z0-9_-]*:$/.test(lines[index])) { + if (!lines[index].trim() || lines[index].trimStart().startsWith('#')) { + continue + } + const indent = indentation(lines[index]) + if (indent < jobIndent) { + end = index + break + } + if (indent === jobIndent && /^\s*[A-Za-z0-9_-]+:\s*$/.test(lines[index])) { end = index break } } return lines.slice(start, end).join('\n') } + +function needs(block) { + const scalar = block.match(/^\s*needs:\s*([A-Za-z0-9_-]+)\s*$/m) + if (scalar) return [scalar[1]] + return scalarList(block, 'needs') +} + +function scalarList(block, key) { + const lines = block.split(/\r?\n/) + const start = lines.findIndex((line) => + new RegExp(`^\\s*${escapeRegExp(key)}:\\s*$`).test(line), + ) + assert.notEqual(start, -1, `expected ${key} list`) + const keyIndent = indentation(lines[start]) + const values = [] + for (let index = start + 1; index < lines.length; index += 1) { + if (!lines[index].trim() || lines[index].trimStart().startsWith('#')) { + continue + } + if (indentation(lines[index]) <= keyIndent) break + const item = lines[index].match(/^\s*-\s*([^#]+?)\s*$/) + if (item) values.push(item[1].replace(/^['"]|['"]$/g, '')) + } + return values +} + +function assertPinnedExternalActions(source) { + const uses = [...source.matchAll(/^\s*uses:\s*([^\s#]+)\s*(?:#.*)?$/gm)].map( + (match) => match[1], + ) + assert.ok(uses.length > 0, 'workflow must use actions') + for (const action of uses) { + if (action.startsWith('./')) continue + assert.match( + action, + /@[0-9a-f]{40,64}$/, + `${action} must be pinned to an immutable commit`, + ) + } +} + +function indentation(line) { + return line.match(/^\s*/)[0].length +} + +function escapeRegExp(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} diff --git a/scripts/sync-release-changelog.mjs b/scripts/sync-release-changelog.mjs new file mode 100644 index 0000000..2a2c3ec --- /dev/null +++ b/scripts/sync-release-changelog.mjs @@ -0,0 +1,99 @@ +import assert from 'node:assert/strict' +import { readFile, writeFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import { pathToFileURL } from 'node:url' +import { readReleasePackages } from './release-package-config.mjs' + +export function versionSection(source, version) { + const heading = `## ${version}` + const lines = source.split('\n') + const start = source + .split('\n') + .findIndex((line) => line === heading || line.startsWith(`${heading} `)) + if (start === -1) return null + let end = lines.length + for (let index = start + 1; index < lines.length; index += 1) { + if (isVersionHeading(lines[index])) { + end = index + break + } + } + return lines.slice(start, end).join('\n').trim() +} + +export function releaseNotes(source, version) { + const section = versionSection(source, version) + assert.ok(section, `Root changelog has no ${version} section`) + assert.ok( + section.split('\n').slice(1).join('\n').trim(), + `Root changelog ${version} section is empty`, + ) + return section +} + +export function combinedReleaseSection(packageChangelogs, version) { + const packageSections = packageChangelogs + .map(({ name, source }) => { + const section = versionSection(source, version) + assert.ok(section, `${name} changelog has no ${version} section`) + const body = section.split('\n').slice(1).join('\n').trim() + if (!body) return null + return `### ${name}\n\n${demoteHeadings(body)}` + }) + .filter(Boolean) + assert.ok( + packageSections.length, + `Release ${version} has no package changelog content`, + ) + return `## ${version}\n\n${packageSections.join('\n\n')}` +} + +export function syncRootChangelog(rootChangelog, packageChangelogs, version) { + if (versionSection(rootChangelog, version)) return rootChangelog + assert.ok( + rootChangelog.startsWith('# Changelog\n'), + 'Root changelog must start with # Changelog', + ) + const section = combinedReleaseSection(packageChangelogs, version) + return rootChangelog.replace('# Changelog\n', `# Changelog\n\n${section}\n`) +} + +export async function syncReleaseChangelog({ + repositoryRoot = resolve(import.meta.dirname, '..'), +} = {}) { + const packages = await readReleasePackages(repositoryRoot) + const version = packages[0].manifest.version + const rootPath = resolve(repositoryRoot, 'CHANGELOG.md') + const [rootChangelog, packageChangelogs] = await Promise.all([ + readFile(rootPath, 'utf8'), + Promise.all( + packages.map(async (packageInfo) => ({ + name: packageInfo.name, + source: await readFile( + resolve( + repositoryRoot, + 'packages', + packageInfo.directory, + 'CHANGELOG.md', + ), + 'utf8', + ), + })), + ), + ]) + const next = syncRootChangelog(rootChangelog, packageChangelogs, version) + if (next !== rootChangelog) await writeFile(rootPath, next) +} + +function demoteHeadings(source) { + return source.replace(/^(#{3,5})(?= )/gm, '#$1') +} + +function isVersionHeading(line) { + return /^## \d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\s|$)/.test(line) +} + +const entrypoint = process.argv[1] +if (entrypoint && import.meta.url === pathToFileURL(resolve(entrypoint)).href) { + await syncReleaseChangelog() +} diff --git a/scripts/sync-release-changelog.test.mjs b/scripts/sync-release-changelog.test.mjs new file mode 100644 index 0000000..ff7eac6 --- /dev/null +++ b/scripts/sync-release-changelog.test.mjs @@ -0,0 +1,117 @@ +import { describe, expect, it } from 'vitest' +import { + combinedReleaseSection, + releaseNotes, + syncRootChangelog, + versionSection, +} from './sync-release-changelog.mjs' + +describe('release changelog synchronization', () => { + const packageChangelog = `# @tanstack/charts + +## 0.0.2 + +### Patch Changes + +- Add cached CI. + +## 0.0.1 + +- Initial release. +` + + it('extracts one exact version section', () => { + expect(versionSection(packageChangelog, '0.0.2')).toBe(`## 0.0.2 + +### Patch Changes + +- Add cached CI.`) + }) + + it('retains H2 subsections until the next semver release heading', () => { + const detailedChangelog = `# Changelog + +## 0.0.2 (2026-07-30) + +Release summary. + +## Breaking changes and migrations + +Migration details. + +## Verification + +Verification details. + +## 0.0.1 + +Previous release. +` + expect(versionSection(detailedChangelog, '0.0.2')) + .toBe(`## 0.0.2 (2026-07-30) + +Release summary. + +## Breaking changes and migrations + +Migration details. + +## Verification + +Verification details.`) + }) + + it('requires nonempty notes for the release version', () => { + expect(() => releaseNotes('# Changelog\n', '0.0.2')).toThrow( + 'Root changelog has no 0.0.2 section', + ) + expect(() => + releaseNotes('# Changelog\n\n## 0.0.2\n\n## 0.0.1\n\nPrevious.', '0.0.2'), + ).toThrow('Root changelog 0.0.2 section is empty') + }) + + const adapterChangelog = `# @tanstack/react-charts + +## 0.0.2 + +### Patch Changes + +- Fix adapter hydration. +` + + it('combines every package entry under one release heading', () => { + expect( + combinedReleaseSection( + [ + { name: '@tanstack/charts', source: packageChangelog }, + { name: '@tanstack/react-charts', source: adapterChangelog }, + ], + '0.0.2', + ), + ).toBe(`## 0.0.2 + +### @tanstack/charts + +#### Patch Changes + +- Add cached CI. + +### @tanstack/react-charts + +#### Patch Changes + +- Fix adapter hydration.`) + }) + + it('prepends the combined package entries to the root changelog once', () => { + const root = '# Changelog\n\n## 0.0.1\n\n- Initial release.\n' + const packages = [ + { name: '@tanstack/charts', source: packageChangelog }, + { name: '@tanstack/react-charts', source: adapterChangelog }, + ] + const synced = syncRootChangelog(root, packages, '0.0.2') + expect(synced).toContain('# Changelog\n\n## 0.0.2\n\n### @tanstack/charts') + expect(synced).toContain('### @tanstack/react-charts') + expect(syncRootChangelog(synced, packages, '0.0.2')).toBe(synced) + }) +}) diff --git a/scripts/verify-ci-success.mjs b/scripts/verify-ci-success.mjs new file mode 100644 index 0000000..a29251f --- /dev/null +++ b/scripts/verify-ci-success.mjs @@ -0,0 +1,57 @@ +import assert from 'node:assert/strict' +import { resolve } from 'node:path' +import { pathToFileURL } from 'node:url' + +export function findSuccessfulMainRun(runs, revision) { + return runs.find( + (run) => + run?.head_sha === revision && + run?.head_branch === 'main' && + run?.event === 'push' && + run?.status === 'completed' && + run?.conclusion === 'success', + ) +} + +export async function verifyCiSuccess({ env = process.env } = {}) { + const revision = env.GITHUB_SHA + const repository = env.GITHUB_REPOSITORY + assert.match(revision ?? '', /^[0-9a-f]{40}$/, 'CI gate requires a SHA') + assert.equal( + repository, + 'TanStack/charts', + 'CI gate requires TanStack/charts', + ) + assert.ok(env.GITHUB_TOKEN, 'CI gate requires GITHUB_TOKEN') + + const query = new URLSearchParams({ + head_sha: revision, + per_page: '100', + status: 'success', + }) + const response = await fetch( + `https://api.github.com/repos/${repository}/actions/workflows/chart-library-benchmarks.yml/runs?${query}`, + { + headers: { + accept: 'application/vnd.github+json', + authorization: `Bearer ${env.GITHUB_TOKEN}`, + 'x-github-api-version': '2022-11-28', + }, + }, + ) + assert.equal( + response.ok, + true, + `GitHub returned HTTP ${response.status} while checking CI`, + ) + const payload = await response.json() + const run = findSuccessfulMainRun(payload.workflow_runs ?? [], revision) + assert.ok(run, `No successful main CI run exists for ${revision}`) + console.log(`Verified successful main CI run ${run.id} for ${revision}.`) + return run +} + +const entrypoint = process.argv[1] +if (entrypoint && import.meta.url === pathToFileURL(resolve(entrypoint)).href) { + await verifyCiSuccess() +} diff --git a/scripts/verify-ci-success.test.mjs b/scripts/verify-ci-success.test.mjs new file mode 100644 index 0000000..fb99485 --- /dev/null +++ b/scripts/verify-ci-success.test.mjs @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest' +import { findSuccessfulMainRun } from './verify-ci-success.mjs' + +const revision = 'a'.repeat(40) + +describe('release CI gate', () => { + it('accepts only a completed successful main push for the exact revision', () => { + const valid = { + id: 1, + conclusion: 'success', + event: 'push', + head_branch: 'main', + head_sha: revision, + status: 'completed', + } + expect( + findSuccessfulMainRun( + [ + { ...valid, id: 2, head_sha: 'b'.repeat(40) }, + { ...valid, id: 3, event: 'pull_request' }, + { ...valid, id: 4, conclusion: 'failure' }, + valid, + ], + revision, + ), + ).toEqual(valid) + }) +}) diff --git a/scripts/vitest-config.test.mjs b/scripts/vitest-config.test.mjs new file mode 100644 index 0000000..46a76d4 --- /dev/null +++ b/scripts/vitest-config.test.mjs @@ -0,0 +1,11 @@ +import { describe, expect, it } from 'vitest' +import { configDefaults } from 'vitest/config' +import config from '../vitest.config.ts' + +describe('root Vitest configuration', () => { + it('retains every default exclusion before adding framework exclusions', () => { + expect(config.test.exclude).toEqual( + expect.arrayContaining(configDefaults.exclude), + ) + }) +}) diff --git a/scripts/write-release-notes.mjs b/scripts/write-release-notes.mjs new file mode 100644 index 0000000..1cce0b5 --- /dev/null +++ b/scripts/write-release-notes.mjs @@ -0,0 +1,18 @@ +import assert from 'node:assert/strict' +import { readFile, writeFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import { readReleasePackages } from './release-package-config.mjs' +import { releaseNotes } from './sync-release-changelog.mjs' + +const repositoryRoot = resolve(import.meta.dirname, '..') +const outputPath = process.argv[2] +assert.ok(outputPath, 'Usage: node scripts/write-release-notes.mjs ') + +const packages = await readReleasePackages(repositoryRoot) +const version = packages[0].manifest.version +const changelog = await readFile( + resolve(repositoryRoot, 'CHANGELOG.md'), + 'utf8', +) +const notes = releaseNotes(changelog, version) +await writeFile(resolve(outputPath), `${notes}\n`) diff --git a/vitest.config.ts b/vitest.config.ts index e61ff85..70428ac 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'vitest/config' +import { configDefaults, defineConfig } from 'vitest/config' export default defineConfig({ test: { @@ -13,6 +13,7 @@ export default defineConfig({ 'scripts/**/*.test.mjs', ], exclude: [ + ...configDefaults.exclude, 'packages/solid-charts/**/*.test.tsx', 'packages/svelte-charts/**/*.test.ts', ], From a2a44cbfa949d8f4a87e1bc18d8c557511acc08f Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Thu, 30 Jul 2026 23:19:59 -0600 Subject: [PATCH 2/7] Fix immutable action pins --- .github/actions/setup/action.yml | 4 ++-- API-FRICTION.md | 19 +++++++++++++++++++ scripts/ci-workflow.test.mjs | 12 ++++++++++-- scripts/release-workflow.test.mjs | 10 +++++----- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 166675c..f78cc93 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -14,7 +14,7 @@ runs: uses: TanStack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 - name: Restore Nx cache - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7d # v5.0.4 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: .nx/cache key: ${{ runner.os }}-nx-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.job }}-${{ github.sha }} @@ -25,7 +25,7 @@ runs: - name: Restore Playwright if: inputs.playwright == 'true' id: playwright-cache - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7d # v5.0.4 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-1.62.0 diff --git a/API-FRICTION.md b/API-FRICTION.md index 152beb7..d3a9d03 100644 --- a/API-FRICTION.md +++ b/API-FRICTION.md @@ -179,6 +179,7 @@ Each entry records: | F-141 | Vitest followed pnpm workspace symlinks | Tooling | resolved | | F-142 | Package verification reinstalled during release builds | Tooling/Release | resolved | | F-143 | The `ci` script name collided with pnpm's clean install | Tooling/Docs | resolved | +| F-144 | Action pin checks accepted invalid commit lengths | Tooling | resolved | ## Findings @@ -3456,3 +3457,21 @@ Each entry records: - Verification: `pnpm run validate` resolves the `charts-workspace:ci` Nx target, while contributor docs no longer instruct maintainers to invoke pnpm's clean-install command. + +### F-144 — Action pin checks accepted invalid commit lengths + +- Status: resolved +- Severity: high +- Owner: Tooling +- Observed in: the first pull-request run of the split Nx workflow +- Friction: the action-pin contract accepted hashes between 40 and 64 + characters but GitHub required the repository's SHA-1 action revisions to + contain exactly 40. A 41-character `actions/cache` revision therefore + passed local validation and caused every CI partition to fail during shared + setup before project commands ran. The test also ignored external actions + nested in the local composite setup action. +- Decision: pin `actions/cache@v5.0.4` to its exact 40-character commit and + validate both workflows and the shared composite action with an exact-length + contract. +- Verification: the focused CI and release workflow contracts reject the + previous 41-character revision and accept every current action pin. diff --git a/scripts/ci-workflow.test.mjs b/scripts/ci-workflow.test.mjs index b6c62f8..e0692a7 100644 --- a/scripts/ci-workflow.test.mjs +++ b/scripts/ci-workflow.test.mjs @@ -13,6 +13,10 @@ const workflow = await readFile( ), 'utf8', ) +const setupAction = await readFile( + resolve(import.meta.dirname, '../.github/actions/setup/action.yml'), + 'utf8', +) describe('CI workflow contract', () => { test('uses a read-only default and cancels stale validation runs', () => { @@ -25,6 +29,10 @@ describe('CI workflow contract', () => { assertPinnedExternalActions(workflow) }) + test('pins every external action used by shared setup', () => { + assertPinnedExternalActions(setupAction) + }) + test('runs static, bundle, comparison, conformance, and stress partitions independently', () => { for (const name of [ 'static', @@ -52,7 +60,7 @@ describe('CI workflow contract', () => { const staticChecks = job('static') assert.match(staticChecks, /fetch-depth:\s*0/) assert.match(staticChecks, /persist-credentials:\s*false/) - assert.match(staticChecks, /nrwl\/nx-set-shas@[0-9a-f]{40,64}/) + assert.match(staticChecks, /nrwl\/nx-set-shas@[0-9a-f]{40}/) assert.match(staticChecks, /if:\s*github\.event_name == 'pull_request'/) assert.match(staticChecks, /run:\s*pnpm ci:pr/) assert.match(staticChecks, /if:\s*github\.event_name != 'pull_request'/) @@ -288,7 +296,7 @@ function assertPinnedExternalActions(source) { if (action.startsWith('./')) continue assert.match( action, - /@[0-9a-f]{40,64}$/, + /@[0-9a-f]{40}$/, `${action} must be pinned to an immutable commit`, ) } diff --git a/scripts/release-workflow.test.mjs b/scripts/release-workflow.test.mjs index 5196392..a18fa7f 100644 --- a/scripts/release-workflow.test.mjs +++ b/scripts/release-workflow.test.mjs @@ -35,7 +35,7 @@ describe('release workflow contract', () => { /ref:\s*\${{ github\.event\.workflow_run\.head_sha }}/, ) assert.match(version, /persist-credentials:\s*true/) - assert.match(version, /changesets\/action@[0-9a-f]{40,64}/) + assert.match(version, /changesets\/action@[0-9a-f]{40}/) assert.match(version, /version:\s*pnpm changeset:version/) assert.match(version, /createGithubReleases:\s*false/) assert.match(version, /contents:\s*write/) @@ -88,7 +88,7 @@ describe('release workflow contract', () => { assert.match(validate, /node scripts\/verify-ci-success\.mjs/) assert.match(validate, /node scripts\/build-release-artifacts\.mjs/) assert.match(validate, /node scripts\/publish-release\.mjs --check/) - assert.match(validate, /actions\/upload-artifact@[0-9a-f]{40,64}/) + assert.match(validate, /actions\/upload-artifact@[0-9a-f]{40}/) assert.match(validate, /name:\s*charts-release-\${{ github\.ref_name }}/) assert.match(validate, /path:\s*\.release-artifacts/) assert.doesNotMatch(validate, /actions\/download-artifact@/) @@ -109,7 +109,7 @@ describe('release workflow contract', () => { assert.match(publish, /if:\s*needs\.validate\.result == 'success'/) assert.match(publish, /ref:\s*\${{ github\.sha }}/) assert.match(publish, /persist-credentials:\s*false/) - assert.match(publish, /actions\/download-artifact@[0-9a-f]{40,64}/) + assert.match(publish, /actions\/download-artifact@[0-9a-f]{40}/) assert.match(publish, /name:\s*charts-release-\${{ github\.ref_name }}/) assert.match(publish, /path:\s*\.release-artifacts/) assert.match(publish, /node scripts\/verify-release-revision\.mjs/) @@ -131,7 +131,7 @@ describe('release workflow contract', () => { assert.deepEqual(needs(verify), ['publish']) assert.match(verify, /ref:\s*\${{ github\.sha }}/) assert.match(verify, /persist-credentials:\s*false/) - assert.match(verify, /actions\/download-artifact@[0-9a-f]{40,64}/) + assert.match(verify, /actions\/download-artifact@[0-9a-f]{40}/) assert.match(verify, /node scripts\/verify-published-release\.mjs/) assert.doesNotMatch(verify, /id-token:\s*write/) assert.doesNotMatch(verify, /contents:\s*write/) @@ -224,7 +224,7 @@ function assertPinnedExternalActions(source) { if (action.startsWith('./')) continue assert.match( action, - /@[0-9a-f]{40,64}$/, + /@[0-9a-f]{40}$/, `${action} must be pinned to an immutable commit`, ) } From 5c36a3866d71ad94cbcc934272e2cd7a868065f3 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Thu, 30 Jul 2026 23:35:58 -0600 Subject: [PATCH 3/7] Fix strict conformance and release plans --- .changeset/compact-axis-titles.md | 5 +++ .changeset/config.json | 1 + API-FRICTION.md | 39 +++++++++++++++++-- .../bundle-size/universal-baseline.json | 32 +++++++-------- .../cases/04-stacked-time-area/tanstack.ts | 1 - .../cases/21-streamgraph/tanstack.ts | 1 - .../cases/54-bump-ranking/tanstack.ts | 1 - packages/charts-core/src/scene-layout.test.ts | 36 +++++++++++++++++ packages/charts-core/src/scene.ts | 2 +- scripts/release-workflow.test.mjs | 24 ++++++++++++ 10 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 .changeset/compact-axis-titles.md diff --git a/.changeset/compact-axis-titles.md b/.changeset/compact-axis-titles.md new file mode 100644 index 0000000..74087ab --- /dev/null +++ b/.changeset/compact-axis-titles.md @@ -0,0 +1,5 @@ +--- +'@tanstack/charts': patch +--- + +Keep long Cartesian axis titles contained on compact charts. diff --git a/.changeset/config.json b/.changeset/config.json index d5a0f07..78c1b4e 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -11,6 +11,7 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", + "privatePackages": false, "fixed": [ [ "@tanstack/charts", diff --git a/API-FRICTION.md b/API-FRICTION.md index d3a9d03..3c36082 100644 --- a/API-FRICTION.md +++ b/API-FRICTION.md @@ -180,6 +180,7 @@ Each entry records: | F-142 | Package verification reinstalled during release builds | Tooling/Release | resolved | | F-143 | The `ci` script name collided with pnpm's clean install | Tooling/Docs | resolved | | F-144 | Action pin checks accepted invalid commit lengths | Tooling | resolved | +| F-145 | Changesets included private workspaces in version plans | Tooling/Release | resolved | ## Findings @@ -646,11 +647,17 @@ Each entry records: - Friction: core previously resolved fixed margin heuristics before scales, formatted ticks, titles, and rotations existed. Stats compensated with duplicated character-width estimates and large manual margins, yet labels - could still escape a clipped SVG. + could still escape a clipped SVG. The strict zero-failure CI gate later made + the same existing mistake blocking in two conformance examples: a locked + 64px left margin left the rotated y-axis title 1.35px outside the surface + under Linux font metrics. A long 11px x-axis title was also wider than its + entire 320px surface. - Decision: make omitted margin sides automatic. Solve the minimum guide bounds from formatted text, anchors, and rotations; treat numeric sides as hard overrides; expose resolved bounds for aligned application UI. Keep label - containment separate from tick collision and tiny-container degradation. + containment separate from tick collision. Content-dependent examples omit + margin locks, and axis titles use the same compact 10px typography as ticks + below 360px. - Verification: six guide-bound tests cover deterministic measurement, anchors, baselines, rotation, translated groups, and all four sides. Five scene-layout tests cover long labels and titles, rotated endpoints, narrow-to-wide @@ -659,7 +666,9 @@ Each entry records: bounds, measurer replacement, coalesced font completion, and cleanup. Stats supplies neither Charts margins nor title offsets; its timeline consumes the resolved scene margin. TypeScript, focused lint, browser containment across - every Stats shape, and bundle ceilings pass. + every Stats shape, and bundle ceilings pass. The stacked-area, streamgraph, + and narrow Likert cases pass the same 320px and 640px containment contract in + local Chromium. Pull-request CI remains the cross-platform release gate. ### F-024 — Co-located benchmark cases defeated tree shaking @@ -1318,11 +1327,16 @@ Each entry records: - Friction: removing redundant numeric domains exposed that automatic layout measured guides but not data-bound text. Labels at an inferred maximum escaped the top of the surface even though that margin side was unlocked. + The bump-ranking conformance example later locked its right margin to the + reference renderer's 160px heuristic, which left its longest direct label + 8.64px outside the surface under Ubuntu font metrics. - Decision: built-in text marks expose their positioned labels to the existing monotonic margin solver. It measures anchors, `dx`/`dy`, rotation, font metrics, and responsive scale positions without calling mark render. Explicit margin sides and `margin: 0` remain locks; `clip: true` keeps clipped plots authoritative. Custom marks can opt in with `layoutLabels`. + Content-dependent examples leave the relevant side unlocked instead of + copying another renderer's fixed margin. - Verification: focused core layout/text tests pass, mark render remains single-pass, and all six affected cases pass containment at 320 and 640 px across initial and updated data. The grouped/stacked ordering checks in the @@ -1330,6 +1344,9 @@ Each entry records: keeps the isolated automatic text-margin cost to 219 minified bytes / 85 gzip bytes for the locked line scene and 524 minified bytes / 178 gzip bytes for the representative-mark entry, with no new dependency. + The bump-ranking case passes 320px and 640px containment in local Chromium + with its right margin resolved from the direct labels. Pull-request CI + remains the cross-platform release gate. ### F-054 — D3 reducer output needs empty-safe narrowing @@ -3475,3 +3492,19 @@ Each entry records: contract. - Verification: the focused CI and release workflow contracts reject the previous 41-character revision and accept every current action pin. + +### F-145 — Changesets included private workspaces in version plans + +- Status: resolved +- Severity: high +- Owner: Tooling/Release +- Observed in: preparing the first automated patch after the CI migration +- Friction: Changesets defaults to versioning private packages. A core-only + patch plan therefore included private examples, fixtures, and the sandbox + alongside the ten public Charts packages, even though the artifact publisher + intentionally releases only the public fixed group. +- Decision: disable private-package versioning explicitly and keep the ten + public packages in one exact fixed group. +- Verification: the release workflow contract locks both settings, and + `changeset status` reports only the ten public packages for the compact-axis + patch. diff --git a/benchmarks/bundle-size/universal-baseline.json b/benchmarks/bundle-size/universal-baseline.json index 12c0ac0..d45f226 100644 --- a/benchmarks/bundle-size/universal-baseline.json +++ b/benchmarks/bundle-size/universal-baseline.json @@ -3,36 +3,36 @@ "policy": "Exact minified and gzip output for entries that optional features must not affect. Review every change before updating.", "bundles": { "D3-scale line scene": { - "bytes": 36311, - "gzip": 14118 + "bytes": 36320, + "gzip": 14119 }, "D3-scale line + static SVG": { - "bytes": 39363, - "gzip": 15237 + "bytes": 39372, + "gzip": 15238 }, "Representative marks": { - "bytes": 49125, - "gzip": 17885 + "bytes": 49134, + "gzip": 17884 }, "TanStack DOM host": { - "bytes": 42894, - "gzip": 15700 + "bytes": 42903, + "gzip": 15701 }, "React adapter": { - "bytes": 46334, - "gzip": 16905 + "bytes": 46343, + "gzip": 16906 }, "React line consumer": { - "bytes": 68655, - "gzip": 25708 + "bytes": 68664, + "gzip": 25712 }, "Custom-scale line scene": { - "bytes": 17087, - "gzip": 6431 + "bytes": 17096, + "gzip": 6432 }, "D3 linear-scale line scene": { - "bytes": 36243, - "gzip": 14083 + "bytes": 36252, + "gzip": 14084 } } } diff --git a/benchmarks/conformance/cases/04-stacked-time-area/tanstack.ts b/benchmarks/conformance/cases/04-stacked-time-area/tanstack.ts index e14d344..528f409 100644 --- a/benchmarks/conformance/cases/04-stacked-time-area/tanstack.ts +++ b/benchmarks/conformance/cases/04-stacked-time-area/tanstack.ts @@ -57,7 +57,6 @@ const definition = (_input: ConformanceInput) => { range: colors, legend: colorLegend({ label: 'Industry' }), }, - margin: { left: 64 }, }) } diff --git a/benchmarks/conformance/cases/21-streamgraph/tanstack.ts b/benchmarks/conformance/cases/21-streamgraph/tanstack.ts index 9c52cc9..2084280 100644 --- a/benchmarks/conformance/cases/21-streamgraph/tanstack.ts +++ b/benchmarks/conformance/cases/21-streamgraph/tanstack.ts @@ -56,7 +56,6 @@ const definition = (_input: ConformanceInput) => { range: colors, legend: colorLegend({ label: 'Industry' }), }, - margin: { left: 64 }, }) } diff --git a/benchmarks/conformance/cases/54-bump-ranking/tanstack.ts b/benchmarks/conformance/cases/54-bump-ranking/tanstack.ts index d4a4e87..c497da4 100644 --- a/benchmarks/conformance/cases/54-bump-ranking/tanstack.ts +++ b/benchmarks/conformance/cases/54-bump-ranking/tanstack.ts @@ -71,7 +71,6 @@ const definition = () => { domain: includedIndustries, range: colors, }, - margin: { right: 160 }, }) } diff --git a/packages/charts-core/src/scene-layout.test.ts b/packages/charts-core/src/scene-layout.test.ts index 337282a..dcaf664 100644 --- a/packages/charts-core/src/scene-layout.test.ts +++ b/packages/charts-core/src/scene-layout.test.ts @@ -121,6 +121,42 @@ describe('automatic scene guide layout', () => { ) }) + it('keeps long axis titles inside narrow surfaces', () => { + const definition = defineChart({ + marks: [lineY([1, 2, 3])], + x: { + scale: scaleLinear().domain([0, 2]), + label: '← more disagree · Number of responses · more agree →', + }, + y: { scale: scaleLinear().domain([0, 3]) }, + }) + const scene = createChartScene( + definition, + { width: 320, height: 260 }, + { measureText }, + ) + const regular = createChartScene( + definition, + { width: 360, height: 260 }, + { measureText }, + ) + const title = flatten(scene.nodes).find( + (node): node is SceneLabel => + node.kind === 'label' && node.key === 'x-label', + ) + const regularTitle = flatten(regular.nodes).find( + (node): node is SceneLabel => + node.kind === 'label' && node.key === 'x-label', + ) + if (!title || !regularTitle) throw new Error('Expected x-axis titles') + + const bounds = measureSceneLabelBounds(title, measureText) + expect(title.fontSize).toBe(10) + expect(regularTitle.fontSize).toBe(11) + expect(bounds.x).toBeGreaterThanOrEqual(3.99) + expect(bounds.x + bounds.width).toBeLessThanOrEqual(scene.width - 3.99) + }) + it('contains text-mark labels with unlocked margins', () => { const rows = [ { id: 'top-right', x: 10, y: 10, label: 'Top right' }, diff --git a/packages/charts-core/src/scene.ts b/packages/charts-core/src/scene.ts index cd34942..d99c1b6 100644 --- a/packages/charts-core/src/scene.ts +++ b/packages/charts-core/src/scene.ts @@ -686,7 +686,7 @@ function createAxes( text: definition.x.label, anchor: 'middle', baseline: hasOffset ? 'auto' : 'hanging', - fontSize: 11, + fontSize: width < 360 ? 10 : 11, fontWeight: 600, style: { fill: theme.foreground, fillOpacity: 0.76 }, } diff --git a/scripts/release-workflow.test.mjs b/scripts/release-workflow.test.mjs index a18fa7f..8df32a4 100644 --- a/scripts/release-workflow.test.mjs +++ b/scripts/release-workflow.test.mjs @@ -10,8 +10,32 @@ const workflow = await readFile( resolve(import.meta.dirname, '../.github/workflows/release.yml'), 'utf8', ) +const changesetConfig = JSON.parse( + await readFile( + resolve(import.meta.dirname, '../.changeset/config.json'), + 'utf8', + ), +) describe('release workflow contract', () => { + test('versions only the fixed public package set', () => { + assert.equal(changesetConfig.privatePackages, false) + assert.deepEqual(changesetConfig.fixed, [ + [ + '@tanstack/charts', + '@tanstack/react-charts', + '@tanstack/octane-charts', + '@tanstack/preact-charts', + '@tanstack/vue-charts', + '@tanstack/solid-charts', + '@tanstack/svelte-charts', + '@tanstack/angular-charts', + '@tanstack/lit-charts', + '@tanstack/alpine-charts', + ], + ]) + }) + test('orchestrates versions and tags only from successful main CI', () => { assert.match( workflow, From 4abb95ed8af75b20cdd79ffe26a751a3684b4d2e Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Thu, 30 Jul 2026 23:41:22 -0600 Subject: [PATCH 4/7] Refresh comparison bundle provenance --- API-FRICTION.md | 5 +- benchmarks/comparison/bundle-baseline.json | 414 ++++++++++----------- 2 files changed, 210 insertions(+), 209 deletions(-) diff --git a/API-FRICTION.md b/API-FRICTION.md index 3c36082..f9f2ffb 100644 --- a/API-FRICTION.md +++ b/API-FRICTION.md @@ -3315,9 +3315,10 @@ Each entry records: stale measured evidence. - Verification: the public comparison names the measured TanStack revision, the tracked baseline records the `0.0.1` manifest version separately from - exact `99c08eb` comparison-input revision, and the deterministic comparison + exact `5c36a38` comparison-input revision, and the deterministic comparison check rejects missing, malformed, or mismatched provenance. Its CI checkout - retains the history required to resolve that revision. + retains the history required to resolve that revision. The reviewed 60-case + baseline matches the candidate produced by Ubuntu PR run `30607255311`. ### F-137 — Latest docs installed an incompatible published API diff --git a/benchmarks/comparison/bundle-baseline.json b/benchmarks/comparison/bundle-baseline.json index a210b4d..d5ba33b 100644 --- a/benchmarks/comparison/bundle-baseline.json +++ b/benchmarks/comparison/bundle-baseline.json @@ -1,6 +1,6 @@ { "schemaVersion": 3, - "generatedAt": "2026-07-30T21:26:09.124Z", + "generatedAt": "2026-07-31T05:39:15.459Z", "packageVersions": { "tanstack": "0.0.1", "chartjs": "4.5.1", @@ -11,7 +11,7 @@ "sources": { "tanstack": { "kind": "workspace", - "revision": "99c08ebd320a50a869796905e2f8f34d44bb1586" + "revision": "5c36a3866d71ad94cbcc934272e2cd7a868065f3" }, "chartjs": { "kind": "package", @@ -44,172 +44,172 @@ }, "bundles": { "tanstack-line-basic": { - "minifiedBytes": 65857, - "gzipBytes": 24843, - "brotliBytes": 22148, - "incrementalGzipBytes": 24843, - "incrementalBrotliBytes": 22148 + "minifiedBytes": 65886, + "gzipBytes": 24861, + "brotliBytes": 22188, + "incrementalGzipBytes": 24861, + "incrementalBrotliBytes": 22188 }, "tanstack-line-interactive": { - "minifiedBytes": 69898, - "gzipBytes": 26059, - "brotliBytes": 23190, - "incrementalGzipBytes": 26059, - "incrementalBrotliBytes": 23190 + "minifiedBytes": 69927, + "gzipBytes": 26070, + "brotliBytes": 23217, + "incrementalGzipBytes": 26070, + "incrementalBrotliBytes": 23217 }, "tanstack-line-advanced": { - "minifiedBytes": 77078, - "gzipBytes": 28401, - "brotliBytes": 25173, - "incrementalGzipBytes": 28401, - "incrementalBrotliBytes": 25173 + "minifiedBytes": 77107, + "gzipBytes": 28419, + "brotliBytes": 25167, + "incrementalGzipBytes": 28419, + "incrementalBrotliBytes": 25167 }, "tanstack-bar-basic": { - "minifiedBytes": 67451, - "gzipBytes": 25404, - "brotliBytes": 22627, - "incrementalGzipBytes": 25404, - "incrementalBrotliBytes": 22627 + "minifiedBytes": 67480, + "gzipBytes": 25415, + "brotliBytes": 22635, + "incrementalGzipBytes": 25415, + "incrementalBrotliBytes": 22635 }, "tanstack-bar-interactive": { - "minifiedBytes": 71488, - "gzipBytes": 26608, - "brotliBytes": 23709, - "incrementalGzipBytes": 26608, - "incrementalBrotliBytes": 23709 + "minifiedBytes": 71517, + "gzipBytes": 26628, + "brotliBytes": 23703, + "incrementalGzipBytes": 26628, + "incrementalBrotliBytes": 23703 }, "tanstack-bar-advanced": { - "minifiedBytes": 72878, - "gzipBytes": 27177, - "brotliBytes": 24111, - "incrementalGzipBytes": 27177, - "incrementalBrotliBytes": 24111 + "minifiedBytes": 72907, + "gzipBytes": 27190, + "brotliBytes": 24112, + "incrementalGzipBytes": 27190, + "incrementalBrotliBytes": 24112 }, "tanstack-area-basic": { - "minifiedBytes": 65736, - "gzipBytes": 24839, - "brotliBytes": 22153, - "incrementalGzipBytes": 24839, - "incrementalBrotliBytes": 22153 + "minifiedBytes": 65765, + "gzipBytes": 24849, + "brotliBytes": 22138, + "incrementalGzipBytes": 24849, + "incrementalBrotliBytes": 22138 }, "tanstack-area-interactive": { - "minifiedBytes": 69777, - "gzipBytes": 26055, - "brotliBytes": 23227, - "incrementalGzipBytes": 26055, - "incrementalBrotliBytes": 23227 + "minifiedBytes": 69806, + "gzipBytes": 26069, + "brotliBytes": 23238, + "incrementalGzipBytes": 26069, + "incrementalBrotliBytes": 23238 }, "tanstack-area-advanced": { - "minifiedBytes": 78190, - "gzipBytes": 28878, - "brotliBytes": 25570, - "incrementalGzipBytes": 28878, - "incrementalBrotliBytes": 25570 + "minifiedBytes": 78219, + "gzipBytes": 28894, + "brotliBytes": 25616, + "incrementalGzipBytes": 28894, + "incrementalBrotliBytes": 25616 }, "tanstack-scatter-basic": { - "minifiedBytes": 65609, - "gzipBytes": 24769, - "brotliBytes": 22029, - "incrementalGzipBytes": 24769, - "incrementalBrotliBytes": 22029 + "minifiedBytes": 65638, + "gzipBytes": 24781, + "brotliBytes": 22078, + "incrementalGzipBytes": 24781, + "incrementalBrotliBytes": 22078 }, "tanstack-scatter-interactive": { - "minifiedBytes": 69650, - "gzipBytes": 25991, - "brotliBytes": 23177, - "incrementalGzipBytes": 25991, - "incrementalBrotliBytes": 23177 + "minifiedBytes": 69679, + "gzipBytes": 26007, + "brotliBytes": 23120, + "incrementalGzipBytes": 26007, + "incrementalBrotliBytes": 23120 }, "tanstack-scatter-advanced": { - "minifiedBytes": 69666, - "gzipBytes": 25997, - "brotliBytes": 23121, - "incrementalGzipBytes": 25997, - "incrementalBrotliBytes": 23121 + "minifiedBytes": 69695, + "gzipBytes": 26012, + "brotliBytes": 23107, + "incrementalGzipBytes": 26012, + "incrementalBrotliBytes": 23107 }, "chartjs-line-basic": { "minifiedBytes": 137909, - "gzipBytes": 49196, - "brotliBytes": 43112, - "incrementalGzipBytes": 49196, - "incrementalBrotliBytes": 43112 + "gzipBytes": 49198, + "brotliBytes": 43162, + "incrementalGzipBytes": 49198, + "incrementalBrotliBytes": 43162 }, "chartjs-line-interactive": { "minifiedBytes": 162217, - "gzipBytes": 56988, - "brotliBytes": 49741, - "incrementalGzipBytes": 56988, - "incrementalBrotliBytes": 49741 + "gzipBytes": 56989, + "brotliBytes": 49793, + "incrementalGzipBytes": 56989, + "incrementalBrotliBytes": 49793 }, "chartjs-line-advanced": { "minifiedBytes": 162332, - "gzipBytes": 57015, - "brotliBytes": 49797, - "incrementalGzipBytes": 57015, - "incrementalBrotliBytes": 49797 + "gzipBytes": 57013, + "brotliBytes": 49850, + "incrementalGzipBytes": 57013, + "incrementalBrotliBytes": 49850 }, "chartjs-bar-basic": { "minifiedBytes": 134665, - "gzipBytes": 47822, - "brotliBytes": 41818, - "incrementalGzipBytes": 47822, - "incrementalBrotliBytes": 41818 + "gzipBytes": 47821, + "brotliBytes": 41871, + "incrementalGzipBytes": 47821, + "incrementalBrotliBytes": 41871 }, "chartjs-bar-interactive": { "minifiedBytes": 160971, "gzipBytes": 56127, - "brotliBytes": 49022, + "brotliBytes": 49004, "incrementalGzipBytes": 56127, - "incrementalBrotliBytes": 49022 + "incrementalBrotliBytes": 49004 }, "chartjs-bar-advanced": { "minifiedBytes": 161080, "gzipBytes": 56143, - "brotliBytes": 48999, + "brotliBytes": 48983, "incrementalGzipBytes": 56143, - "incrementalBrotliBytes": 48999 + "incrementalBrotliBytes": 48983 }, "chartjs-area-basic": { "minifiedBytes": 144954, - "gzipBytes": 51616, - "brotliBytes": 45198, - "incrementalGzipBytes": 51616, - "incrementalBrotliBytes": 45198 + "gzipBytes": 51617, + "brotliBytes": 45221, + "incrementalGzipBytes": 51617, + "incrementalBrotliBytes": 45221 }, "chartjs-area-interactive": { "minifiedBytes": 169258, - "gzipBytes": 59554, - "brotliBytes": 51889, - "incrementalGzipBytes": 59554, - "incrementalBrotliBytes": 51889 + "gzipBytes": 59553, + "brotliBytes": 51912, + "incrementalGzipBytes": 59553, + "incrementalBrotliBytes": 51912 }, "chartjs-area-advanced": { "minifiedBytes": 169451, - "gzipBytes": 59610, - "brotliBytes": 51971, - "incrementalGzipBytes": 59610, - "incrementalBrotliBytes": 51971 + "gzipBytes": 59608, + "brotliBytes": 51964, + "incrementalGzipBytes": 59608, + "incrementalBrotliBytes": 51964 }, "chartjs-scatter-basic": { "minifiedBytes": 129054, "gzipBytes": 45776, - "brotliBytes": 40169, + "brotliBytes": 40186, "incrementalGzipBytes": 45776, - "incrementalBrotliBytes": 40169 + "incrementalBrotliBytes": 40186 }, "chartjs-scatter-interactive": { "minifiedBytes": 153508, "gzipBytes": 53639, - "brotliBytes": 46940, + "brotliBytes": 46923, "incrementalGzipBytes": 53639, - "incrementalBrotliBytes": 46940 + "incrementalBrotliBytes": 46923 }, "chartjs-scatter-advanced": { "minifiedBytes": 153648, "gzipBytes": 53663, - "brotliBytes": 46904, + "brotliBytes": 46948, "incrementalGzipBytes": 53663, - "incrementalBrotliBytes": 46904 + "incrementalBrotliBytes": 46948 }, "echarts-line-basic": { "minifiedBytes": 476626, @@ -241,17 +241,17 @@ }, "echarts-bar-interactive": { "minifiedBytes": 514879, - "gzipBytes": 175082, - "brotliBytes": 148889, - "incrementalGzipBytes": 175082, - "incrementalBrotliBytes": 148889 + "gzipBytes": 175077, + "brotliBytes": 148895, + "incrementalGzipBytes": 175077, + "incrementalBrotliBytes": 148895 }, "echarts-bar-advanced": { "minifiedBytes": 514986, - "gzipBytes": 175094, - "brotliBytes": 148838, - "incrementalGzipBytes": 175094, - "incrementalBrotliBytes": 148838 + "gzipBytes": 175090, + "brotliBytes": 148981, + "incrementalGzipBytes": 175090, + "incrementalBrotliBytes": 148981 }, "echarts-area-basic": { "minifiedBytes": 476678, @@ -296,130 +296,130 @@ "incrementalBrotliBytes": 145298 }, "recharts-line-basic": { - "minifiedBytes": 515905, - "gzipBytes": 157576, - "brotliBytes": 132863, - "incrementalGzipBytes": 97915, - "incrementalBrotliBytes": 82676 + "minifiedBytes": 516124, + "gzipBytes": 157666, + "brotliBytes": 132967, + "incrementalGzipBytes": 98002, + "incrementalBrotliBytes": 82770 }, "recharts-line-interactive": { - "minifiedBytes": 569059, - "gzipBytes": 170799, - "brotliBytes": 143045, - "incrementalGzipBytes": 110992, - "incrementalBrotliBytes": 92786 + "minifiedBytes": 569293, + "gzipBytes": 170891, + "brotliBytes": 142940, + "incrementalGzipBytes": 111089, + "incrementalBrotliBytes": 92758 }, "recharts-line-advanced": { - "minifiedBytes": 569250, - "gzipBytes": 170868, - "brotliBytes": 143049, - "incrementalGzipBytes": 111030, - "incrementalBrotliBytes": 92593 + "minifiedBytes": 569484, + "gzipBytes": 170961, + "brotliBytes": 143105, + "incrementalGzipBytes": 111132, + "incrementalBrotliBytes": 92799 }, "recharts-bar-basic": { - "minifiedBytes": 513036, - "gzipBytes": 156675, - "brotliBytes": 132240, - "incrementalGzipBytes": 97155, - "incrementalBrotliBytes": 81984 + "minifiedBytes": 513246, + "gzipBytes": 156758, + "brotliBytes": 132293, + "incrementalGzipBytes": 97244, + "incrementalBrotliBytes": 82104 }, "recharts-bar-interactive": { - "minifiedBytes": 575434, - "gzipBytes": 172177, - "brotliBytes": 144521, - "incrementalGzipBytes": 112415, - "incrementalBrotliBytes": 94426 + "minifiedBytes": 575659, + "gzipBytes": 172257, + "brotliBytes": 144455, + "incrementalGzipBytes": 112520, + "incrementalBrotliBytes": 94481 }, "recharts-bar-advanced": { - "minifiedBytes": 575607, - "gzipBytes": 172220, - "brotliBytes": 144566, - "incrementalGzipBytes": 112503, - "incrementalBrotliBytes": 94410 + "minifiedBytes": 575832, + "gzipBytes": 172304, + "brotliBytes": 144611, + "incrementalGzipBytes": 112603, + "incrementalBrotliBytes": 94513 }, "recharts-area-basic": { - "minifiedBytes": 518062, - "gzipBytes": 158245, - "brotliBytes": 133359, - "incrementalGzipBytes": 98544, - "incrementalBrotliBytes": 83101 + "minifiedBytes": 518281, + "gzipBytes": 158330, + "brotliBytes": 133402, + "incrementalGzipBytes": 98628, + "incrementalBrotliBytes": 83217 }, "recharts-area-interactive": { - "minifiedBytes": 571196, - "gzipBytes": 171435, - "brotliBytes": 143559, - "incrementalGzipBytes": 111625, + "minifiedBytes": 571430, + "gzipBytes": 171532, + "brotliBytes": 143560, + "incrementalGzipBytes": 111719, "incrementalBrotliBytes": 93273 }, "recharts-area-advanced": { - "minifiedBytes": 571416, - "gzipBytes": 171490, - "brotliBytes": 143456, - "incrementalGzipBytes": 111693, - "incrementalBrotliBytes": 93309 + "minifiedBytes": 571650, + "gzipBytes": 171596, + "brotliBytes": 143626, + "incrementalGzipBytes": 111792, + "incrementalBrotliBytes": 93454 }, "recharts-scatter-basic": { - "minifiedBytes": 517302, - "gzipBytes": 158344, - "brotliBytes": 133613, - "incrementalGzipBytes": 98643, - "incrementalBrotliBytes": 83499 + "minifiedBytes": 517521, + "gzipBytes": 158424, + "brotliBytes": 133726, + "incrementalGzipBytes": 98731, + "incrementalBrotliBytes": 83564 }, "recharts-scatter-interactive": { - "minifiedBytes": 566367, - "gzipBytes": 170470, - "brotliBytes": 142821, - "incrementalGzipBytes": 110722, - "incrementalBrotliBytes": 92599 + "minifiedBytes": 566601, + "gzipBytes": 170556, + "brotliBytes": 142846, + "incrementalGzipBytes": 110817, + "incrementalBrotliBytes": 92693 }, "recharts-scatter-advanced": { - "minifiedBytes": 567195, - "gzipBytes": 170672, - "brotliBytes": 142896, - "incrementalGzipBytes": 110907, - "incrementalBrotliBytes": 92773 + "minifiedBytes": 567429, + "gzipBytes": 170764, + "brotliBytes": 143128, + "incrementalGzipBytes": 111001, + "incrementalBrotliBytes": 92816 }, "observable-plot-line-basic": { "minifiedBytes": 268405, - "gzipBytes": 91643, - "brotliBytes": 78153, - "incrementalGzipBytes": 91643, - "incrementalBrotliBytes": 78153 + "gzipBytes": 91644, + "brotliBytes": 78197, + "incrementalGzipBytes": 91644, + "incrementalBrotliBytes": 78197 }, "observable-plot-line-interactive": { "minifiedBytes": 268479, - "gzipBytes": 91684, - "brotliBytes": 78195, - "incrementalGzipBytes": 91684, - "incrementalBrotliBytes": 78195 + "gzipBytes": 91685, + "brotliBytes": 78194, + "incrementalGzipBytes": 91685, + "incrementalBrotliBytes": 78194 }, "observable-plot-line-advanced": { "minifiedBytes": 268495, - "gzipBytes": 91688, - "brotliBytes": 78197, - "incrementalGzipBytes": 91688, - "incrementalBrotliBytes": 78197 + "gzipBytes": 91689, + "brotliBytes": 78163, + "incrementalGzipBytes": 91689, + "incrementalBrotliBytes": 78163 }, "observable-plot-bar-basic": { "minifiedBytes": 247693, - "gzipBytes": 87263, - "brotliBytes": 74597, - "incrementalGzipBytes": 87263, - "incrementalBrotliBytes": 74597 + "gzipBytes": 87275, + "brotliBytes": 74598, + "incrementalGzipBytes": 87275, + "incrementalBrotliBytes": 74598 }, "observable-plot-bar-interactive": { "minifiedBytes": 247765, - "gzipBytes": 87319, - "brotliBytes": 74558, - "incrementalGzipBytes": 87319, - "incrementalBrotliBytes": 74558 + "gzipBytes": 87328, + "brotliBytes": 74559, + "incrementalGzipBytes": 87328, + "incrementalBrotliBytes": 74559 }, "observable-plot-bar-advanced": { "minifiedBytes": 247780, - "gzipBytes": 87309, - "brotliBytes": 74606, - "incrementalGzipBytes": 87309, - "incrementalBrotliBytes": 74606 + "gzipBytes": 87319, + "brotliBytes": 74611, + "incrementalGzipBytes": 87319, + "incrementalBrotliBytes": 74611 }, "observable-plot-area-basic": { "minifiedBytes": 275460, @@ -437,31 +437,31 @@ }, "observable-plot-area-advanced": { "minifiedBytes": 274338, - "gzipBytes": 93851, - "brotliBytes": 80132, - "incrementalGzipBytes": 93851, - "incrementalBrotliBytes": 80132 + "gzipBytes": 93850, + "brotliBytes": 79998, + "incrementalGzipBytes": 93850, + "incrementalBrotliBytes": 79998 }, "observable-plot-scatter-basic": { "minifiedBytes": 242656, - "gzipBytes": 85338, - "brotliBytes": 72763, - "incrementalGzipBytes": 85338, - "incrementalBrotliBytes": 72763 + "gzipBytes": 85340, + "brotliBytes": 72769, + "incrementalGzipBytes": 85340, + "incrementalBrotliBytes": 72769 }, "observable-plot-scatter-interactive": { "minifiedBytes": 242728, - "gzipBytes": 85394, - "brotliBytes": 72851, - "incrementalGzipBytes": 85394, - "incrementalBrotliBytes": 72851 + "gzipBytes": 85396, + "brotliBytes": 72831, + "incrementalGzipBytes": 85396, + "incrementalBrotliBytes": 72831 }, "observable-plot-scatter-advanced": { "minifiedBytes": 242744, - "gzipBytes": 85401, - "brotliBytes": 72871, - "incrementalGzipBytes": 85401, - "incrementalBrotliBytes": 72871 + "gzipBytes": 85403, + "brotliBytes": 72875, + "incrementalGzipBytes": 85403, + "incrementalBrotliBytes": 72875 } } } From dd9bee4534ce90fe881af062e101214a6aaaca3c Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Thu, 30 Jul 2026 23:46:50 -0600 Subject: [PATCH 5/7] Sync comparison evidence and stabilize hydration CI --- API-FRICTION.md | 16 +++++++ docs/comparison.md | 16 +++---- packages/charts-core/docs/comparison.md | 16 +++---- .../src/CanvasChart.client.test.tsrx | 48 ++++++++++--------- 4 files changed, 56 insertions(+), 40 deletions(-) diff --git a/API-FRICTION.md b/API-FRICTION.md index f9f2ffb..94ba4fe 100644 --- a/API-FRICTION.md +++ b/API-FRICTION.md @@ -181,6 +181,7 @@ Each entry records: | F-143 | The `ci` script name collided with pnpm's clean install | Tooling/Docs | resolved | | F-144 | Action pin checks accepted invalid commit lengths | Tooling | resolved | | F-145 | Changesets included private workspaces in version plans | Tooling/Release | resolved | +| F-146 | Octane hydration used a unit-test timeout | Tooling | resolved | ## Findings @@ -3509,3 +3510,18 @@ Each entry records: - Verification: the release workflow contract locks both settings, and `changeset status` reports only the ten public packages for the compact-axis patch. + +### F-146 — Octane hydration used a unit-test timeout + +- Status: resolved +- Severity: medium +- Owner: Tooling +- Observed in: parallel Ubuntu static checks +- Friction: the Octane Canvas hydration regression creates a Vite SSR server, + compiles the server fixture, renders it, hydrates it, and closes the server. + Under the full parallel CI graph that integration path took 5.4 seconds and + exceeded Vitest's generic five-second unit-test limit. +- Decision: give only that cold SSR integration test a 15-second timeout. + Ordinary Octane client tests retain the five-second default. +- Verification: the focused Octane client suite covers all seven client tests; + pull-request static checks remain the parallel Linux gate. diff --git a/docs/comparison.md b/docs/comparison.md index 7b66f43..f5dd108 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -4,24 +4,22 @@ description: Compare current TanStack Charts workspace source with pinned Chart. --- TanStack Charts `0.0.1` is a pre-alpha release. Its results on this page measure -the workspace implementation prepared for `0.0.1`, not the earlier published -`0.0.0` artifact. This comparison records architectural differences and +the workspace implementation prepared for the next patch, not the published +`0.0.1` artifact. This comparison records architectural differences and reproducible evidence without turning untested behavior into a checkmark. ## Tested versions | Library | Package | Measured source | | -------------------------------------------------------------------------------------- | -------------------- | ------------------- | -| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `99c08eb` | +| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `5c36a38` | | [Chart.js](https://www.chartjs.org/docs/latest/) | `chart.js` | npm `4.5.1` | | [Apache ECharts](https://echarts.apache.org/handbook/en/best-practices/canvas-vs-svg/) | `echarts` | npm `6.1.0` | | [Recharts](https://recharts.github.io/en-US/) | `recharts` | npm `3.10.1` | | [Observable Plot](https://observablehq.com/plot/features/plots) | `@observablehq/plot` | npm `0.6.17` | The competitor versions are exact package pins, not latest versions inferred -at page render time. The TanStack product implementation ends at commit -`a91106c`; the measured workspace revision is `99c08eb`, which adds the -comparison fixture correction and tracked baseline for `0.0.1`. +at page render time. The measured TanStack workspace revision is `5c36a38`. ## Capability matrix @@ -52,7 +50,7 @@ output model. ## Bundle snapshot -Baseline date: `2026-07-30`. +Baseline date: `2026-07-31`. Each range covers 12 independently built, minified browser consumers: line, bar, area, and scatter at basic, interactive, and advanced tiers. Full size is @@ -61,10 +59,10 @@ that lane externalizes React and React DOM. | Library | Full cold-page gzip | React externalized | | --------------- | ------------------: | -----------------: | -| TanStack Charts | 24.19–28.20 KiB | — | +| TanStack Charts | 24.20–28.22 KiB | — | | Chart.js | 44.70–58.21 KiB | — | | Apache ECharts | 153.10–173.18 KiB | — | -| Recharts | 153.00–168.18 KiB | 94.88–109.87 KiB | +| Recharts | 153.08–168.27 KiB | 94.96–109.96 KiB | | Observable Plot | 83.34–91.94 KiB | — | The tracked baseline distinguishes the TanStack workspace revision from diff --git a/packages/charts-core/docs/comparison.md b/packages/charts-core/docs/comparison.md index 7b66f43..f5dd108 100644 --- a/packages/charts-core/docs/comparison.md +++ b/packages/charts-core/docs/comparison.md @@ -4,24 +4,22 @@ description: Compare current TanStack Charts workspace source with pinned Chart. --- TanStack Charts `0.0.1` is a pre-alpha release. Its results on this page measure -the workspace implementation prepared for `0.0.1`, not the earlier published -`0.0.0` artifact. This comparison records architectural differences and +the workspace implementation prepared for the next patch, not the published +`0.0.1` artifact. This comparison records architectural differences and reproducible evidence without turning untested behavior into a checkmark. ## Tested versions | Library | Package | Measured source | | -------------------------------------------------------------------------------------- | -------------------- | ------------------- | -| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `99c08eb` | +| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `5c36a38` | | [Chart.js](https://www.chartjs.org/docs/latest/) | `chart.js` | npm `4.5.1` | | [Apache ECharts](https://echarts.apache.org/handbook/en/best-practices/canvas-vs-svg/) | `echarts` | npm `6.1.0` | | [Recharts](https://recharts.github.io/en-US/) | `recharts` | npm `3.10.1` | | [Observable Plot](https://observablehq.com/plot/features/plots) | `@observablehq/plot` | npm `0.6.17` | The competitor versions are exact package pins, not latest versions inferred -at page render time. The TanStack product implementation ends at commit -`a91106c`; the measured workspace revision is `99c08eb`, which adds the -comparison fixture correction and tracked baseline for `0.0.1`. +at page render time. The measured TanStack workspace revision is `5c36a38`. ## Capability matrix @@ -52,7 +50,7 @@ output model. ## Bundle snapshot -Baseline date: `2026-07-30`. +Baseline date: `2026-07-31`. Each range covers 12 independently built, minified browser consumers: line, bar, area, and scatter at basic, interactive, and advanced tiers. Full size is @@ -61,10 +59,10 @@ that lane externalizes React and React DOM. | Library | Full cold-page gzip | React externalized | | --------------- | ------------------: | -----------------: | -| TanStack Charts | 24.19–28.20 KiB | — | +| TanStack Charts | 24.20–28.22 KiB | — | | Chart.js | 44.70–58.21 KiB | — | | Apache ECharts | 153.10–173.18 KiB | — | -| Recharts | 153.00–168.18 KiB | 94.88–109.87 KiB | +| Recharts | 153.08–168.27 KiB | 94.96–109.96 KiB | | Observable Plot | 83.34–91.94 KiB | — | The tracked baseline distinguishes the TanStack workspace revision from diff --git a/packages/octane-charts/src/CanvasChart.client.test.tsrx b/packages/octane-charts/src/CanvasChart.client.test.tsrx index 646462f..a60a768 100644 --- a/packages/octane-charts/src/CanvasChart.client.test.tsrx +++ b/packages/octane-charts/src/CanvasChart.client.test.tsrx @@ -50,31 +50,35 @@ afterEach(() => { }) describe('Octane Canvas adapter client', () => { - it('hydrates the server-rendered Canvas shell in place', async () => { - const target = document.createElement('div') - target.innerHTML = await renderServerHydrationFixture() - const host = target.querySelector('.ts-chart-host') - const surface = target.querySelector('.ts-chart-canvas') - const sceneCanvas = target.querySelector('.ts-chart-canvas__scene') - const focusCanvas = target.querySelector('.ts-chart-canvas__focus') - if (!host || !surface || !sceneCanvas || !focusCanvas) { - throw new Error('Expected the server-rendered Canvas chart shell') - } + it( + 'hydrates the server-rendered Canvas shell in place', + async () => { + const target = document.createElement('div') + target.innerHTML = await renderServerHydrationFixture() + const host = target.querySelector('.ts-chart-host') + const surface = target.querySelector('.ts-chart-canvas') + const sceneCanvas = target.querySelector('.ts-chart-canvas__scene') + const focusCanvas = target.querySelector('.ts-chart-canvas__focus') + if (!host || !surface || !sceneCanvas || !focusCanvas) { + throw new Error('Expected the server-rendered Canvas chart shell') + } - let root: ReturnType | undefined - await act(() => { - root = hydrateRoot(target, HydrationCanvasChart) - }) + let root: ReturnType | undefined + await act(() => { + root = hydrateRoot(target, HydrationCanvasChart) + }) - expect(target.querySelector('.ts-chart-host')).toBe(host) - expect(target.querySelector('.ts-chart-canvas')).toBe(surface) - expect(target.querySelector('.ts-chart-canvas__scene')).toBe(sceneCanvas) - expect(target.querySelector('.ts-chart-canvas__focus')).toBe(focusCanvas) - expect(target.querySelector('svg')).toBeNull() + expect(target.querySelector('.ts-chart-host')).toBe(host) + expect(target.querySelector('.ts-chart-canvas')).toBe(surface) + expect(target.querySelector('.ts-chart-canvas__scene')).toBe(sceneCanvas) + expect(target.querySelector('.ts-chart-canvas__focus')).toBe(focusCanvas) + expect(target.querySelector('svg')).toBeNull() - await act(() => root?.unmount()) - expect(target.childElementCount).toBe(0) - }) + await act(() => root?.unmount()) + expect(target.childElementCount).toBe(0) + }, + 15_000, + ) it('adopts its rendered Canvas shell and preserves interaction callbacks', async () => { const target = document.createElement('div') From b33a7af804481d4fc4faf244fec3e47a52350fae Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 31 Jul 2026 00:09:32 -0600 Subject: [PATCH 6/7] Harden cached CI and automated releases --- .github/actions/setup/action.yml | 2 +- .../workflows/chart-library-benchmarks.yml | 43 +++++----- .github/workflows/release.yml | 30 +++++-- API-FRICTION.md | 83 ++++++++++++++++++- package.json | 2 - scripts/check-framework-adapters.mjs | 19 +---- scripts/ci-workflow.test.mjs | 39 +++++++-- scripts/compare-chart-libraries.mjs | 16 +--- scripts/publish-release.mjs | 14 +--- scripts/release-package-config.mjs | 2 +- scripts/release-package-config.test.mjs | 8 ++ scripts/release-status.mjs | 19 ++++- scripts/release-status.test.mjs | 14 ++++ scripts/release-workflow.test.mjs | 59 ++++++++++++- scripts/run-with-concurrency.mjs | 41 +++++++++ scripts/run-with-concurrency.test.mjs | 31 +++++++ scripts/verify-ci-success.mjs | 8 +- scripts/verify-ci-success.test.mjs | 2 + scripts/verify-published-release.mjs | 2 + 19 files changed, 348 insertions(+), 86 deletions(-) create mode 100644 scripts/run-with-concurrency.mjs create mode 100644 scripts/run-with-concurrency.test.mjs diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index f78cc93..ac3a06e 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -28,7 +28,7 @@ runs: uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.cache/ms-playwright - key: ${{ runner.os }}-playwright-1.62.0 + key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} - name: Install Chromium if: inputs.playwright == 'true' && steps.playwright-cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/chart-library-benchmarks.yml b/.github/workflows/chart-library-benchmarks.yml index b40c6a8..6a1cc58 100644 --- a/.github/workflows/chart-library-benchmarks.yml +++ b/.github/workflows/chart-library-benchmarks.yml @@ -9,6 +9,11 @@ on: - cron: '17 7 * * 1' workflow_dispatch: inputs: + version_pr: + description: Run the pull-request validation profile + required: false + type: boolean + default: false upload_bundle_baseline_candidate: description: Upload exact comparison bundle measurements required: false @@ -38,19 +43,8 @@ jobs: - name: Setup uses: ./.github/actions/setup - - name: Set affected revisions - if: github.event_name == 'pull_request' - uses: nrwl/nx-set-shas@3e9ad7370203c1e93d109be57f3b72eb0eb511b1 # v4.4.0 - with: - main-branch-name: main - - - name: Run affected checks - if: github.event_name == 'pull_request' - run: pnpm ci:pr - - - name: Run all checks - if: github.event_name != 'pull_request' - run: pnpm ci:all + - name: Run cached validation graph + run: pnpm run validate - name: Upload production catalog if: github.event_name == 'push' && github.ref == 'refs/heads/main' @@ -117,7 +111,10 @@ jobs: - name: Publish comparison summary if: success() - run: cat .benchmark-output/results/*.md >> "$GITHUB_STEP_SUMMARY" + run: | + summaries=(.benchmark-output/results/*.md) + test -e "${summaries[0]}" + cat "${summaries[@]}" >> "$GITHUB_STEP_SUMMARY" - name: Upload comparison if: always() @@ -156,16 +153,19 @@ jobs: playwright: 'true' - name: Run pull-request conformance shard - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' || inputs.version_pr run: pnpm conformance:quick -- --shard=${{ matrix.shard }}/8 - name: Run production conformance shard - if: github.event_name != 'pull_request' + if: github.event_name != 'pull_request' && !inputs.version_pr run: pnpm conformance -- --shard=${{ matrix.shard }}/8 - name: Publish conformance summary if: success() - run: cat .benchmark-output/conformance/results/*.md >> "$GITHUB_STEP_SUMMARY" + run: | + summaries=(.benchmark-output/conformance/results/*.md) + test -e "${summaries[0]}" + cat "${summaries[@]}" >> "$GITHUB_STEP_SUMMARY" - name: Upload conformance if: always() @@ -209,16 +209,19 @@ jobs: run: pnpm exec vitest run benchmarks/comparison/stress - name: Run quick stress shard - if: github.event_name == 'pull_request' || github.event_name == 'push' + if: github.event_name == 'pull_request' || github.event_name == 'push' || inputs.version_pr run: pnpm benchmark:stress:quick -- --workload=${{ matrix.workloads }} - name: Run standard stress shard - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && !inputs.version_pr) run: pnpm benchmark:stress:standard -- --workload=${{ matrix.workloads }} - name: Publish stress summary if: success() - run: cat .benchmark-output/stress/results/*.md >> "$GITHUB_STEP_SUMMARY" + run: | + summaries=(.benchmark-output/stress/results/*.md) + test -e "${summaries[0]}" + cat "${summaries[@]}" >> "$GITHUB_STEP_SUMMARY" - name: Upload stress results if: always() diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7852a7e..55b90bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 10 permissions: + actions: write contents: write pull-requests: write @@ -44,6 +45,7 @@ jobs: uses: ./.github/actions/setup - name: Create or update version pull request + id: changesets uses: changesets/action@63a615b9cd06ba9a3e6d13796c7fbcb080a60a0b # v1.8.0 with: branch: main @@ -54,6 +56,22 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Validate version pull request + if: steps.changesets.outputs.pullRequestNumber != '' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION_PR_NUMBER: ${{ steps.changesets.outputs.pullRequestNumber }} + run: | + version_ref=$(gh pr view "$VERSION_PR_NUMBER" \ + --repo "$GITHUB_REPOSITORY" \ + --json headRefName \ + --jq .headRefName) + test -n "$version_ref" + gh workflow run chart-library-benchmarks.yml \ + --repo "$GITHUB_REPOSITORY" \ + --ref "$version_ref" \ + -f version_pr=true + tag: name: Dispatch validated release needs: version @@ -164,11 +182,11 @@ jobs: persist-credentials: false - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 24.18.0 registry-url: https://registry.npmjs.org - cache: '' + package-manager-cache: false - name: Download checked package artifacts uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 @@ -197,11 +215,11 @@ jobs: persist-credentials: false - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 24.18.0 registry-url: https://registry.npmjs.org - cache: '' + package-manager-cache: false - name: Download checked package artifacts uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 @@ -229,10 +247,10 @@ jobs: persist-credentials: false - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: 24.18.0 - cache: '' + package-manager-cache: false - name: Revalidate refs and create GitHub release env: diff --git a/API-FRICTION.md b/API-FRICTION.md index 94ba4fe..0a95275 100644 --- a/API-FRICTION.md +++ b/API-FRICTION.md @@ -182,6 +182,10 @@ Each entry records: | F-144 | Action pin checks accepted invalid commit lengths | Tooling | resolved | | F-145 | Changesets included private workspaces in version plans | Tooling/Release | resolved | | F-146 | Octane hydration used a unit-test timeout | Tooling | resolved | +| F-147 | Bot-authored version PRs lacked an automatic CI trigger | Tooling/Release | resolved | +| F-148 | Publisher failure returned before its workers settled | Tooling/Release | resolved | +| F-149 | Release checks could stall or accept an unbound tag | Tooling/Release | resolved | +| F-150 | Nx worktree caches followed the common Git directory | Tooling | monitoring | ## Findings @@ -3472,10 +3476,14 @@ Each entry records: shorthand. Running the command removed `node_modules` instead of executing the Nx validation graph. - Decision: expose the local graph as `pnpm run validate`. Keep the unambiguous - `ci:pr` and `ci:all` scripts for GitHub Actions. + command in GitHub Actions as well. - Verification: `pnpm run validate` resolves the `charts-workspace:ci` Nx - target, while contributor docs no longer instruct maintainers to invoke - pnpm's clean-install command. + target locally and in CI, while contributor docs no longer instruct + maintainers to invoke pnpm's clean-install command. The workspace contains + 25 inferred Nx projects but only the root owns the current aggregate CI + target, so removing the misleading `nx affected --target=ci` alias avoids + claiming task pruning that does not yet exist; target-level cache replay + still skips unchanged work. ### F-144 — Action pin checks accepted invalid commit lengths @@ -3525,3 +3533,72 @@ Each entry records: Ordinary Octane client tests retain the five-second default. - Verification: the focused Octane client suite covers all seven client tests; pull-request static checks remain the parallel Linux gate. + +### F-147 — Bot-authored version PRs lacked an automatic CI trigger + +- Status: resolved +- Severity: high +- Owner: Tooling/Release +- Observed in: hardening the automated Changesets release path +- Friction: the built-in workflow token can create and update the Changesets + version pull request, but relying on that mutation to start pull-request CI + leaves validation subject to GitHub's bot-event suppression and approval + behavior. +- Decision: use the action's `pullRequestNumber` output to resolve the exact + version branch, then explicitly dispatch the full chart workflow on that + ref with the job's narrowly scoped `actions: write` permission. A typed + dispatch input selects the same quick browser profile as ordinary pull + requests. +- Verification: the release workflow contract requires the Changesets output, + exact PR-head lookup, and explicit branch-scoped workflow dispatch. + +### F-148 — Publisher failure returned before its workers settled + +- Status: resolved +- Severity: high +- Owner: Tooling/Release +- Observed in: release-safety review of parallel package work +- Friction: the adapter publisher, framework package gate, and comparison + builder used `Promise.all` over long-lived workers. One rejected operation + could end its caller while other workers were still running; publication + could hide later failures, and package cleanup could remove a temporary + directory beneath active builds. +- Decision: drain a bounded worker pool, continue independent queued + publications, collect failures in source order, and throw one + `AggregateError` only after every worker has settled. +- Verification: all three paths use the shared worker pool. Its regression + forces an early failure, confirms all four operations start, all successful + operations finish, active work is zero when the error surfaces, and + concurrency never exceeds two. + +### F-149 — Release checks could stall or accept an unbound tag + +- Status: resolved +- Severity: high +- Owner: Tooling/Release +- Observed in: release-safety review of idempotent tag dispatch +- Friction: an existing remote release tag bypassed the revision comparison + when `RELEASE_REVISION` was absent, and direct npm, GitHub, and attestation + requests had no request deadline below the job-level timeout. +- Decision: require an exact 40-character expected revision whenever a tag + already exists, require equality before dispatch, and give every direct + release fetch a 30-second deadline. +- Verification: focused tests reject missing, empty, and mismatched revisions, + accept the exact match, and the release workflow contract counts a bounded + signal on every direct release request. + +### F-150 — Nx worktree caches followed the common Git directory + +- Status: monitoring +- Severity: low +- Owner: Tooling +- Observed in: validating Nx from a sandboxed Git worktree +- Friction: Nx resolved its relative cache and workspace-data directories + through the worktree's common Git checkout. The sandbox could execute every + target but could not write task metadata outside the active worktree. +- Current decision: keep the portable repository defaults. In restricted + worktrees, set `NX_CACHE_DIRECTORY` and `NX_WORKSPACE_DATA_DIRECTORY` to + absolute paths inside that worktree. +- Verification: the full 17-target validation graph passes with both + directories scoped to the active worktree. Ordinary clones and GitHub + Actions retain `.nx/cache` and `.nx/workspace-data`. diff --git a/package.json b/package.json index feab633..4678854 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,6 @@ }, "scripts": { "validate": "nx run charts-workspace:ci --parallel=5", - "ci:all": "nx run-many --all --target=ci --parallel=5", - "ci:pr": "nx affected --target=ci --parallel=5", "benchmark": "node scripts/compare-chart-libraries.mjs", "benchmark:check": "nx run charts-workspace:benchmark-check", "benchmark:perf": "node scripts/compare-chart-libraries.mjs --perf-only", diff --git a/scripts/check-framework-adapters.mjs b/scripts/check-framework-adapters.mjs index f7cb438..6eda294 100644 --- a/scripts/check-framework-adapters.mjs +++ b/scripts/check-framework-adapters.mjs @@ -17,6 +17,7 @@ import ts from 'typescript' import { build as viteBuild } from 'vite' import solid from 'vite-plugin-solid' import { validatePackedMarkdownLinks } from './packed-markdown-links.mjs' +import { runWithConcurrency } from './run-with-concurrency.mjs' const execFileAsync = promisify(execFile) const root = resolve(import.meta.dirname, '..') @@ -56,11 +57,13 @@ try { buildAngularPackage, ], 4, + (operation) => operation(), ) await runWithConcurrency( packageNames.map((directory) => () => verifyPackage(directory)), 4, + (operation) => operation(), ) console.log( @@ -70,22 +73,6 @@ try { await rm(temporaryDirectory, { recursive: true, force: true }) } -async function runWithConcurrency(operations, concurrency) { - let nextIndex = 0 - await Promise.all( - Array.from( - { length: Math.min(concurrency, operations.length) }, - async () => { - while (nextIndex < operations.length) { - const index = nextIndex - nextIndex += 1 - await operations[index]() - } - }, - ), - ) -} - async function buildStandardPackage(directory, jsxImportSource) { const packageRoot = resolve(root, 'packages', directory) const sourceRoot = resolve(packageRoot, 'src') diff --git a/scripts/ci-workflow.test.mjs b/scripts/ci-workflow.test.mjs index e0692a7..1c504a9 100644 --- a/scripts/ci-workflow.test.mjs +++ b/scripts/ci-workflow.test.mjs @@ -31,6 +31,11 @@ describe('CI workflow contract', () => { test('pins every external action used by shared setup', () => { assertPinnedExternalActions(setupAction) + assert.match( + setupAction, + /key:\s*\${{ runner\.os }}-playwright-\${{ hashFiles\('pnpm-lock\.yaml'\) }}/, + ) + assert.doesNotMatch(setupAction, /playwright-\d+\.\d+\.\d+/) }) test('runs static, bundle, comparison, conformance, and stress partitions independently', () => { @@ -56,15 +61,12 @@ describe('CI workflow contract', () => { assert.doesNotMatch(job('bundle-baseline'), /playwright:\s*['"]true['"]/) }) - test('uses affected checks only for pull requests and builds the main catalog fully', () => { + test('runs the cached workspace graph and builds the main catalog fully', () => { const staticChecks = job('static') assert.match(staticChecks, /fetch-depth:\s*0/) assert.match(staticChecks, /persist-credentials:\s*false/) - assert.match(staticChecks, /nrwl\/nx-set-shas@[0-9a-f]{40}/) - assert.match(staticChecks, /if:\s*github\.event_name == 'pull_request'/) - assert.match(staticChecks, /run:\s*pnpm ci:pr/) - assert.match(staticChecks, /if:\s*github\.event_name != 'pull_request'/) - assert.match(staticChecks, /run:\s*pnpm ci:all/) + assert.match(staticChecks, /run:\s*pnpm run validate/) + assert.doesNotMatch(staticChecks, /nx-set-shas|nx affected|pnpm ci:/) assert.match( staticChecks, /if:\s*github\.event_name == 'push' && github\.ref == 'refs\/heads\/main'/, @@ -87,6 +89,10 @@ describe('CI workflow contract', () => { comparison, /name:\s*chart-library-comparison-\${{ matrix\.chart }}-\${{ github\.run_id }}/, ) + assert.match( + comparison, + /summaries=\(\.benchmark-output\/results\/\*\.md\)[\s\S]*test -e "\${summaries\[0\]}"[\s\S]*cat "\${summaries\[@\]}"/, + ) const conformance = job('conformance') assert.match(conformance, /fail-fast:\s*false/) @@ -105,14 +111,26 @@ describe('CI workflow contract', () => { conformance, /pnpm conformance:quick -- --shard=\${{ matrix\.shard }}\/8/, ) + assert.match( + conformance, + /if:\s*github\.event_name == 'pull_request' \|\| inputs\.version_pr/, + ) assert.match( conformance, /pnpm conformance -- --shard=\${{ matrix\.shard }}\/8/, ) + assert.match( + conformance, + /if:\s*github\.event_name != 'pull_request' && !inputs\.version_pr/, + ) assert.match( conformance, /name:\s*chart-library-conformance-\${{ matrix\.shard }}-\${{ github\.run_id }}/, ) + assert.match( + conformance, + /summaries=\(\.benchmark-output\/conformance\/results\/\*\.md\)[\s\S]*test -e "\${summaries\[0\]}"[\s\S]*cat "\${summaries\[@\]}"/, + ) }) test('covers every stress workload exactly once across named shards', () => { @@ -144,10 +162,19 @@ describe('CI workflow contract', () => { stress, /benchmark:stress:standard -- --workload=\${{ matrix\.workloads }}/, ) + assert.match(stress, /github\.event_name == 'push' \|\| inputs\.version_pr/) + assert.match( + stress, + /github\.event_name == 'workflow_dispatch' && !inputs\.version_pr/, + ) assert.match( stress, /name:\s*chart-library-stress-\${{ matrix\.name }}-\${{ github\.run_id }}/, ) + assert.match( + stress, + /summaries=\(\.benchmark-output\/stress\/results\/\*\.md\)[\s\S]*test -e "\${summaries\[0\]}"[\s\S]*cat "\${summaries\[@\]}"/, + ) const workloads = matrixIncludes(stress).flatMap((entry) => entry.workloads.split(','), diff --git a/scripts/compare-chart-libraries.mjs b/scripts/compare-chart-libraries.mjs index d2281cd..1506262 100644 --- a/scripts/compare-chart-libraries.mjs +++ b/scripts/compare-chart-libraries.mjs @@ -19,6 +19,7 @@ import { tanstackComparisonRevision, tanstackComparisonSourceFailure, } from './comparison-source-revision.mjs' +import { runWithConcurrency } from './run-with-concurrency.mjs' const root = resolve(import.meta.dirname, '..') const comparisonDirectory = resolve(root, 'benchmarks/comparison') @@ -279,18 +280,9 @@ if (args.has('--check')) { async function buildCases(benchmarkCases) { const results = new Array(benchmarkCases.length) - let nextIndex = 0 - const workerCount = Math.min(4, benchmarkCases.length) - - await Promise.all( - Array.from({ length: workerCount }, async () => { - while (nextIndex < benchmarkCases.length) { - const index = nextIndex - nextIndex += 1 - results[index] = await buildCase(benchmarkCases[index]) - } - }), - ) + await runWithConcurrency(benchmarkCases, 4, async (benchmarkCase, index) => { + results[index] = await buildCase(benchmarkCase) + }) return results } diff --git a/scripts/publish-release.mjs b/scripts/publish-release.mjs index 94aa350..9d23013 100644 --- a/scripts/publish-release.mjs +++ b/scripts/publish-release.mjs @@ -7,6 +7,7 @@ import { validateReleaseArtifacts, } from './release-artifacts.mjs' import { validateTrustedPublishingNpmVersion } from './release-security.mjs' +import { runWithConcurrency } from './run-with-concurrency.mjs' const execFileAsync = promisify(execFile) const repositoryRoot = resolve(import.meta.dirname, '..') @@ -96,19 +97,6 @@ async function publishArtifact(artifact) { console.log(`Published: ${artifact.name}@${version}`) } -async function runWithConcurrency(values, concurrency, operation) { - let nextIndex = 0 - await Promise.all( - Array.from({ length: Math.min(concurrency, values.length) }, async () => { - while (nextIndex < values.length) { - const index = nextIndex - nextIndex += 1 - await operation(values[index]) - } - }), - ) -} - async function waitForRegistryPackage(artifact, releaseVersion) { let lastResult = null for (let attempt = 0; attempt < 60; attempt += 1) { diff --git a/scripts/release-package-config.mjs b/scripts/release-package-config.mjs index 0ce2004..68012cf 100644 --- a/scripts/release-package-config.mjs +++ b/scripts/release-package-config.mjs @@ -109,7 +109,7 @@ export function releaseTag(version) { export function assertStableReleaseVersion(packageName, version) { assert.match( version, - /^\d+\.\d+\.\d+$/, + /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/, `${packageName} requires a stable release version`, ) } diff --git a/scripts/release-package-config.test.mjs b/scripts/release-package-config.test.mjs index a393467..2de4c08 100644 --- a/scripts/release-package-config.test.mjs +++ b/scripts/release-package-config.test.mjs @@ -17,4 +17,12 @@ describe('release package configuration', () => { assertStableReleaseVersion('@tanstack/charts', '0.0.2-rc.0'), ).toThrow('requires a stable release version') }) + + it('rejects non-canonical versions with leading zeroes', () => { + for (const version of ['01.2.3', '1.02.3', '1.2.03']) { + expect(() => + assertStableReleaseVersion('@tanstack/charts', version), + ).toThrow('requires a stable release version') + } + }) }) diff --git a/scripts/release-status.mjs b/scripts/release-status.mjs index 50bb500..c96548c 100644 --- a/scripts/release-status.mjs +++ b/scripts/release-status.mjs @@ -10,6 +10,7 @@ import { releaseNotes } from './sync-release-changelog.mjs' const execFileAsync = promisify(execFile) const registry = 'https://registry.npmjs.org' +const requestTimeout = 30_000 export function classifyReleaseStatus({ expectedRevision, @@ -48,7 +49,12 @@ export function classifyReleaseStatus({ } } - if (tagRevision !== null && expectedRevision !== undefined) { + if (tagRevision !== null) { + assert.match( + expectedRevision ?? '', + /^[0-9a-f]{40}$/, + 'An existing release tag requires the expected revision', + ) assert.equal( tagRevision, expectedRevision, @@ -125,7 +131,10 @@ export async function releaseStatus({ async function readRegistryPackage(name, version) { const response = await fetch( `${registry}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`, - { headers: { accept: 'application/json' } }, + { + headers: { accept: 'application/json' }, + signal: AbortSignal.timeout(requestTimeout), + }, ) if (response.status === 404) return null assert.equal( @@ -139,7 +148,10 @@ async function readRegistryPackage(name, version) { async function readLatestVersion(name) { const response = await fetch( `${registry}/${encodeURIComponent(name)}/latest`, - { headers: { accept: 'application/json' } }, + { + headers: { accept: 'application/json' }, + signal: AbortSignal.timeout(requestTimeout), + }, ) if (response.status === 404) return null assert.equal( @@ -200,6 +212,7 @@ async function githubReleaseExists(env, tag) { : {}), 'x-github-api-version': '2022-11-28', }, + signal: AbortSignal.timeout(requestTimeout), }, ) if (response.status === 404) return false diff --git a/scripts/release-status.test.mjs b/scripts/release-status.test.mjs index 9827241..9127f11 100644 --- a/scripts/release-status.test.mjs +++ b/scripts/release-status.test.mjs @@ -73,6 +73,20 @@ describe('automated release status', () => { ).toThrow('existing release tag points to a different revision') }) + it('rejects an existing tag when the expected revision is missing', () => { + for (const expectedRevision of [undefined, '']) { + expect(() => + classifyReleaseStatus({ + expectedRevision, + hasPendingChangesets: false, + packageStates: ['missing'], + releaseExists: false, + tagRevision: 'a'.repeat(40), + }), + ).toThrow('existing release tag requires the expected revision') + } + }) + it('does nothing after npm, the tag, and GitHub release all exist', () => { expect( classifyReleaseStatus({ diff --git a/scripts/release-workflow.test.mjs b/scripts/release-workflow.test.mjs index 8df32a4..ff6c544 100644 --- a/scripts/release-workflow.test.mjs +++ b/scripts/release-workflow.test.mjs @@ -10,6 +10,18 @@ const workflow = await readFile( resolve(import.meta.dirname, '../.github/workflows/release.yml'), 'utf8', ) +const releaseStatusScript = await readFile( + resolve(import.meta.dirname, './release-status.mjs'), + 'utf8', +) +const verifyCiScript = await readFile( + resolve(import.meta.dirname, './verify-ci-success.mjs'), + 'utf8', +) +const verifyPublishedScript = await readFile( + resolve(import.meta.dirname, './verify-published-release.mjs'), + 'utf8', +) const changesetConfig = JSON.parse( await readFile( resolve(import.meta.dirname, '../.changeset/config.json'), @@ -60,12 +72,22 @@ describe('release workflow contract', () => { ) assert.match(version, /persist-credentials:\s*true/) assert.match(version, /changesets\/action@[0-9a-f]{40}/) + assert.match(version, /id:\s*changesets/) assert.match(version, /version:\s*pnpm changeset:version/) assert.match(version, /createGithubReleases:\s*false/) + assert.match( + version, + /if:\s*steps\.changesets\.outputs\.pullRequestNumber != ''/, + ) + assert.match(version, /gh pr view "\$VERSION_PR_NUMBER"/) + assert.match( + version, + /gh workflow run chart-library-benchmarks\.yml[\s\S]*--ref "\$version_ref"[\s\S]*-f version_pr=true/, + ) + assert.match(version, /actions:\s*write/) assert.match(version, /contents:\s*write/) assert.match(version, /pull-requests:\s*write/) assert.doesNotMatch(version, /id-token:\s*write/) - assert.doesNotMatch(version, /actions:\s*write/) }) test('creates an annotated tag and explicitly dispatches its release', () => { @@ -143,6 +165,7 @@ describe('release workflow contract', () => { assert.doesNotMatch(publish, /\bcorepack enable\b/) assert.doesNotMatch(publish, /\bpnpm (?:test|typecheck|docs:check)\b/) assert.doesNotMatch(publish, /\bNPM_TOKEN\b|\bNODE_AUTH_TOKEN\b/) + assert.match(publish, /package-manager-cache:\s*false/) assert.ok( publish.indexOf('verify-release-revision.mjs') < publish.indexOf('publish-release.mjs'), @@ -157,6 +180,7 @@ describe('release workflow contract', () => { assert.match(verify, /persist-credentials:\s*false/) assert.match(verify, /actions\/download-artifact@[0-9a-f]{40}/) assert.match(verify, /node scripts\/verify-published-release\.mjs/) + assert.match(verify, /package-manager-cache:\s*false/) assert.doesNotMatch(verify, /id-token:\s*write/) assert.doesNotMatch(verify, /contents:\s*write/) @@ -169,6 +193,7 @@ describe('release workflow contract', () => { assert.match(release, /gh release create "\$GITHUB_REF_NAME"/) assert.match(release, /--verify-tag/) assert.match(release, /--notes-file \/tmp\/charts-release-notes\.md/) + assert.match(release, /package-manager-cache:\s*false/) assert.doesNotMatch(release, /--notes-file CHANGELOG\.md/) assert.doesNotMatch(release, /id-token:\s*write/) assert.ok( @@ -180,6 +205,38 @@ describe('release workflow contract', () => { test('pins every external action to an immutable revision', () => { assertPinnedExternalActions(workflow) + assert.equal( + (workflow.match(/package-manager-cache:\s*false/g) ?? []).length, + 3, + ) + assert.doesNotMatch(workflow, /^\s*cache:\s*['"]{2}\s*$/m) + }) + + test('bounds every direct release network request', () => { + assert.equal( + ( + releaseStatusScript.match( + /signal:\s*AbortSignal\.timeout\(requestTimeout\)/g, + ) ?? [] + ).length, + 3, + ) + assert.equal( + ( + verifyCiScript.match( + /signal:\s*AbortSignal\.timeout\(requestTimeout\)/g, + ) ?? [] + ).length, + 1, + ) + assert.equal( + ( + verifyPublishedScript.match( + /signal:\s*AbortSignal\.timeout\(requestTimeout\)/g, + ) ?? [] + ).length, + 1, + ) }) }) diff --git a/scripts/run-with-concurrency.mjs b/scripts/run-with-concurrency.mjs new file mode 100644 index 0000000..aeb744b --- /dev/null +++ b/scripts/run-with-concurrency.mjs @@ -0,0 +1,41 @@ +import assert from 'node:assert/strict' + +export async function runWithConcurrency(values, concurrency, operation) { + assert.ok( + Number.isSafeInteger(concurrency) && concurrency > 0, + 'Concurrency must be a positive integer', + ) + assert.equal(typeof operation, 'function', 'Operation must be a function') + + let nextIndex = 0 + const failures = [] + const workers = Array.from( + { length: Math.min(concurrency, values.length) }, + async () => { + while (nextIndex < values.length) { + const index = nextIndex + nextIndex += 1 + try { + await operation(values[index], index) + } catch (error) { + failures.push({ error, index }) + } + } + }, + ) + + const workerResults = await Promise.allSettled(workers) + workerResults.forEach((result, index) => { + if (result.status === 'rejected') { + failures.push({ error: result.reason, index: values.length + index }) + } + }) + + if (failures.length > 0) { + failures.sort((left, right) => left.index - right.index) + throw new AggregateError( + failures.map((failure) => failure.error), + `${failures.length} concurrent operation${failures.length === 1 ? '' : 's'} failed`, + ) + } +} diff --git a/scripts/run-with-concurrency.test.mjs b/scripts/run-with-concurrency.test.mjs new file mode 100644 index 0000000..fd2b7d4 --- /dev/null +++ b/scripts/run-with-concurrency.test.mjs @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest' +import { runWithConcurrency } from './run-with-concurrency.mjs' + +describe('concurrent worker pool', () => { + it('waits for every operation and reports failures after the pool drains', async () => { + let active = 0 + let maximumActive = 0 + const completed = [] + const started = [] + const failure = new Error('publish failed') + + const result = runWithConcurrency([0, 1, 2, 3], 2, async (value) => { + active += 1 + maximumActive = Math.max(maximumActive, active) + started.push(value) + await new Promise((resolvePromise) => setTimeout(resolvePromise, 5)) + active -= 1 + if (value === 0) throw failure + completed.push(value) + }) + + await expect(result).rejects.toMatchObject({ + errors: [failure], + name: 'AggregateError', + }) + expect(started.sort()).toEqual([0, 1, 2, 3]) + expect(completed.sort()).toEqual([1, 2, 3]) + expect(active).toBe(0) + expect(maximumActive).toBe(2) + }) +}) diff --git a/scripts/verify-ci-success.mjs b/scripts/verify-ci-success.mjs index a29251f..0916bc3 100644 --- a/scripts/verify-ci-success.mjs +++ b/scripts/verify-ci-success.mjs @@ -1,6 +1,9 @@ import assert from 'node:assert/strict' import { resolve } from 'node:path' import { pathToFileURL } from 'node:url' +import { releaseRepositorySlug } from './release-security.mjs' + +const requestTimeout = 30_000 export function findSuccessfulMainRun(runs, revision) { return runs.find( @@ -19,8 +22,8 @@ export async function verifyCiSuccess({ env = process.env } = {}) { assert.match(revision ?? '', /^[0-9a-f]{40}$/, 'CI gate requires a SHA') assert.equal( repository, - 'TanStack/charts', - 'CI gate requires TanStack/charts', + releaseRepositorySlug, + `CI gate requires ${releaseRepositorySlug}`, ) assert.ok(env.GITHUB_TOKEN, 'CI gate requires GITHUB_TOKEN') @@ -37,6 +40,7 @@ export async function verifyCiSuccess({ env = process.env } = {}) { authorization: `Bearer ${env.GITHUB_TOKEN}`, 'x-github-api-version': '2022-11-28', }, + signal: AbortSignal.timeout(requestTimeout), }, ) assert.equal( diff --git a/scripts/verify-ci-success.test.mjs b/scripts/verify-ci-success.test.mjs index fb99485..a0736ae 100644 --- a/scripts/verify-ci-success.test.mjs +++ b/scripts/verify-ci-success.test.mjs @@ -19,6 +19,8 @@ describe('release CI gate', () => { { ...valid, id: 2, head_sha: 'b'.repeat(40) }, { ...valid, id: 3, event: 'pull_request' }, { ...valid, id: 4, conclusion: 'failure' }, + { ...valid, id: 5, head_branch: 'release' }, + { ...valid, id: 6, status: 'in_progress' }, valid, ], revision, diff --git a/scripts/verify-published-release.mjs b/scripts/verify-published-release.mjs index 703c3f3..90e11c6 100644 --- a/scripts/verify-published-release.mjs +++ b/scripts/verify-published-release.mjs @@ -18,6 +18,7 @@ import { const execFileAsync = promisify(execFile) const repositoryRoot = resolve(import.meta.dirname, '..') const registry = 'https://registry.npmjs.org' +const requestTimeout = 30_000 const { artifacts, manifest, version } = await validateReleaseArtifacts(repositoryRoot) @@ -198,6 +199,7 @@ async function waitForAttestations(url) { const response = await fetch(url, { headers: { accept: 'application/json' }, redirect: 'error', + signal: AbortSignal.timeout(requestTimeout), }) lastStatus = response.status if (response.ok) return response.json() From f573aa5d81587aa621f9106a36385ec699eaf51e Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 31 Jul 2026 00:12:43 -0600 Subject: [PATCH 7/7] Update artifact actions to Node 24 --- .github/workflows/chart-library-benchmarks.yml | 12 ++++++------ .github/workflows/release.yml | 6 +++--- API-FRICTION.md | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.github/workflows/chart-library-benchmarks.yml b/.github/workflows/chart-library-benchmarks.yml index 6a1cc58..eb4d04d 100644 --- a/.github/workflows/chart-library-benchmarks.yml +++ b/.github/workflows/chart-library-benchmarks.yml @@ -48,7 +48,7 @@ jobs: - name: Upload production catalog if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: charts-catalog-${{ github.sha }} path: .catalog-artifact @@ -76,7 +76,7 @@ jobs: - name: Upload comparison bundle baseline candidate if: ${{ always() && steps.benchmark-check.outcome != 'skipped' && (steps.benchmark-check.outcome == 'failure' || inputs.upload_bundle_baseline_candidate) }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: comparison-bundle-baseline-${{ github.sha }} path: .benchmark-output/results/bundle-baseline.candidate.json @@ -118,7 +118,7 @@ jobs: - name: Upload comparison if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: chart-library-comparison-${{ matrix.chart }}-${{ github.run_id }} path: .benchmark-output/results @@ -169,7 +169,7 @@ jobs: - name: Upload conformance if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: chart-library-conformance-${{ matrix.shard }}-${{ github.run_id }} path: | @@ -225,7 +225,7 @@ jobs: - name: Upload stress results if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: chart-library-stress-${{ matrix.name }}-${{ github.run_id }} path: .benchmark-output/stress/results @@ -282,7 +282,7 @@ jobs: fetch-depth: 0 - name: Download validated catalog - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: charts-catalog-${{ github.sha }} path: .catalog-artifact diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 55b90bf..1a94a14 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -156,7 +156,7 @@ jobs: run: node scripts/publish-release.mjs --check - name: Upload checked package artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: charts-release-${{ github.ref_name }} path: .release-artifacts @@ -189,7 +189,7 @@ jobs: package-manager-cache: false - name: Download checked package artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: charts-release-${{ github.ref_name }} path: .release-artifacts @@ -222,7 +222,7 @@ jobs: package-manager-cache: false - name: Download checked package artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: charts-release-${{ github.ref_name }} path: .release-artifacts diff --git a/API-FRICTION.md b/API-FRICTION.md index 0a95275..95675ec 100644 --- a/API-FRICTION.md +++ b/API-FRICTION.md @@ -186,6 +186,7 @@ Each entry records: | F-148 | Publisher failure returned before its workers settled | Tooling/Release | resolved | | F-149 | Release checks could stall or accept an unbound tag | Tooling/Release | resolved | | F-150 | Nx worktree caches followed the common Git directory | Tooling | monitoring | +| F-151 | Artifact actions targeted deprecated Node 20 | Tooling | resolved | ## Findings @@ -3602,3 +3603,16 @@ Each entry records: - Verification: the full 17-target validation graph passes with both directories scoped to the active worktree. Ordinary clones and GitHub Actions retain `.nx/cache` and `.nx/workspace-data`. + +### F-151 — Artifact actions targeted deprecated Node 20 + +- Status: resolved +- Severity: low +- Owner: Tooling +- Observed in: the final split-CI pull-request matrix +- Friction: GitHub forced the pinned v4 artifact actions onto Node 24 and + emitted a deprecation annotation in every artifact-producing shard. +- Decision: pin the official Node 24 releases of `upload-artifact` v6 and + `download-artifact` v7 by exact commit. +- Verification: workflow contracts require immutable action revisions; the + final GitHub matrix exercises every upload path before merge.