diff --git a/.changeset/catalog-case-119.md b/.changeset/catalog-case-119.md new file mode 100644 index 00000000..004a0923 --- /dev/null +++ b/.changeset/catalog-case-119.md @@ -0,0 +1,8 @@ +--- +'@tanstack/react-charts-catalog': patch +--- + +Publish the stacked-bar band-and-rule cursor as catalog case 119 at +`@tanstack/react-charts-catalog/cases/119-stacked-bar-band-cursor`, bringing the +published catalog to 110 cases. The case demonstrates mixed cursor +presentation, labels, motion, and keyed updates. diff --git a/.changeset/native-host-parity.md b/.changeset/native-host-parity.md new file mode 100644 index 00000000..5b32aeee --- /dev/null +++ b/.changeset/native-host-parity.md @@ -0,0 +1,7 @@ +--- +'@tanstack/react-native-charts': patch +--- + +Document React Native parity for shared focus and free-cursor state, +renderer-native focus and state layers, selection activation, pinned tooltip +context, viewport-filtered focus, polygon areas, and `NativePaintContext.canvas`. diff --git a/.changeset/steady-release-links.md b/.changeset/steady-release-links.md new file mode 100644 index 00000000..4275028b --- /dev/null +++ b/.changeset/steady-release-links.md @@ -0,0 +1,7 @@ +--- +'@tanstack/charts': patch +--- + +Pin packaged documentation and comparison protocol links to the immutable tag +for the same package version. Release automation now advances those tag links +together with every visible release-version reference. diff --git a/.github/workflows/chart-library-benchmarks.yml b/.github/workflows/chart-library-benchmarks.yml index 83de18af..a926882d 100644 --- a/.github/workflows/chart-library-benchmarks.yml +++ b/.github/workflows/chart-library-benchmarks.yml @@ -98,6 +98,18 @@ jobs: - name: Set Nx SHAs uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1 + - name: Verify repository-derived comparison provenance + if: github.event_name != 'pull_request' || steps.static-changes.outputs.static == 'full' + env: + NX_CLOUD_CONTINUOUS_ASSIGNMENT: 'false' + run: pnpm exec nx run charts-workspace:benchmark-check + + - name: Verify packed package consumers + if: github.event_name != 'pull_request' || steps.static-changes.outputs.static == 'full' + env: + NX_CLOUD_CONTINUOUS_ASSIGNMENT: 'false' + run: pnpm exec nx run charts-workspace:package-check + - name: Start Nx Agents id: nx-agents if: github.event_name != 'pull_request' || steps.static-changes.outputs.static == 'full' @@ -107,7 +119,7 @@ jobs: if: github.event_name != 'pull_request' || steps.static-changes.outputs.static == 'full' env: NX_PARALLEL: 4 - run: pnpm exec nx run charts-workspace:ci + run: pnpm exec nx run charts-workspace:ci-distributed - name: Stop Nx Agents if: always() && steps.nx-agents.outcome == 'success' @@ -342,7 +354,7 @@ jobs: require_partition "$CANARY_REQUIRED" "$CANARY_RESULT" publish-catalog: - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + if: always() && !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.static.result == 'success' && needs.ci.result == 'success' needs: - static - ci diff --git a/.nx/workflows/distribution.yaml b/.nx/workflows/distribution.yaml index 2883e98a..a209a985 100644 --- a/.nx/workflows/distribution.yaml +++ b/.nx/workflows/distribution.yaml @@ -1,13 +1,7 @@ distribute-on: - default: 2 linux-medium-js, 1 linux-large-js + default: 3 linux-medium-js assignment-rules: - - targets: - - package-check - run-on: - - agent: linux-large-js - parallelism: 1 - - projects: - '*' run-on: diff --git a/API-FRICTION.md b/API-FRICTION.md index 09ec2ca4..50cd4913 100644 --- a/API-FRICTION.md +++ b/API-FRICTION.md @@ -210,7 +210,7 @@ Each entry records: | F-171 | Packed declarations assume one platform global set | Tooling | resolved | | F-172 | Metro skipped the fixture-owned Babel runtime | Tooling | resolved | | F-173 | Metro retained the complete universal barrel | API/Tooling | monitoring | -| F-174 | OIDC release cannot claim a new npm package name | Tooling | resolved | +| F-174 | OIDC release cannot claim a new npm package name | Tooling | monitoring | | F-175 | Native SVG resource normalization collapsed authored IDs | Application | resolved | | F-176 | Large marks were focused by distant anchor points | API | monitoring | | F-177 | Bubble overlap inherited incidental source order | Application | resolved | @@ -3502,6 +3502,13 @@ Each entry records: manifest, and embed routes passed live HTTP and browser checks. Later catalog publications intentionally identify the current `main` commit rather than remaining pinned to an npm release tag. +- `0.7.0` follow-up: successful `static` and aggregate `ci` jobs still left + `publish-catalog` skipped because its job-level condition inherited GitHub's + implicit `success()` after optional upstream partitions skipped. The job now + uses `always()`, rejects cancelled workflows, and explicitly requires both + direct prerequisites to have succeeded on a push to `main`. The workflow + contract locks that condition so a validated main artifact cannot silently + remain unpublished or publish after cancellation. ### F-120 — Key-only focus collapsed duplicate observations @@ -4335,6 +4342,12 @@ Each entry records: - Verification: focused tests cover version-heading discovery, complete replacement, idempotency, and missing-version rejection. Generated package docs remain outputs of `pnpm docs:sync`, never hand-edited sources. +- `0.7.0` follow-up: visible version strings advanced, but the README, + installation guide, and four comparison-protocol links still targeted the + `0.6.5` source commit. The synchronizer now counts immutable + `tree/v` and `blob/v` links as release references, validates + their exact count, and advances them with the package version. The focused + regression counts only the matching version tag. ### F-154 — Root barrels crossed the browser host boundary @@ -4986,7 +4999,7 @@ Each entry records: ### F-174 — OIDC release cannot claim a new npm package name -- Status: resolved +- Status: monitoring - Severity: high - Owner: Tooling - Observed in: adding `@tanstack/react-native-charts` to the fixed release set @@ -5010,6 +5023,14 @@ Each entry records: reads confirmed `latest=0.5.1`, integrity, and attestations for every package. The temporary npm login was revoked and no bootstrap environment secret remains. +- `0.7.0` follow-up: `@tanstack/react-charts-catalog` was added to the fixed + set without its own npm trusted-publisher mapping. Release run `31217442235` + published the other 12 packages with provenance, then failed with + `ENEEDAUTH` on the catalog. The guarded bootstrap workflow proved it was the + sole missing fixed-set artifact before using the protected recovery token. + Keep this finding open until npm names `TanStack/charts` and `release.yml` as + the catalog package's trusted publisher and a tokenless coordinated release + verifies it. ### F-175 — Native SVG resource normalization collapsed authored IDs @@ -5747,6 +5768,18 @@ Each entry records: the same validation graph as type, package, bundle, adapter, and catalog gates. Baseline changes remain explicit and require attribution before the tracked file or canonical comparison page is refreshed. +- Release follow-up evidence: the correction pull request's Nx agent received + a synthetic merge checkout and resolved that checkout as the latest + comparison input revision, even though none of the tracked inputs had + changed since `cd7768378f40de237607dc1fc6640e1dc8490571`. The same command + resolved the recorded revision correctly in the coordinator's full clone. +- Release follow-up decision: run the cacheable comparison provenance target + on the full-history coordinator before starting continuous Nx assignment. + The distributed aggregate then replays that exact result instead of deriving + repository provenance inside an agent workspace. +- Release follow-up verification: the workflow contract fixes the coordinator + ordering and disables continuous assignment for that step. The coordinator + `benchmark-check` passes against the unchanged `cd7768378` evidence. ### F-198 — Union-valued axes rejected configured D3 scales @@ -6703,6 +6736,31 @@ Each entry records: packages and server-renders the grouped-bar catalog component through the `@tanstack/charts/bar` path. The complete packed web, declaration, bare React Native, and Expo consumer gate passes with that fixture enabled. +- Release follow-up evidence: disabling automatic peer installation did not + make the correction pull request hermetic on a distributed Nx agent. + `@types/d3-force` is a regular `@tanstack/charts` dependency, and pnpm's + offline resolver still needed registry metadata to match its declared + `^3.0.10` range even though the agent had the exact linked package and an + override. The coordinator's frozen workspace install had populated that + metadata; the agent's package mirror had not. +- Release follow-up decision: preserve the package's semver range and the + offline consumer fixtures. Run `package-check` on the setup-populated + coordinator before starting Nx agents. A dedicated `ci-distributed` target + has exactly the normal `ci` dependencies except `package-check`, making the + execution boundary explicit instead of depending on a cache handoff. Local + `ci` and `validate` retain the complete gate. Distributed checks no longer + reserve a large agent for this coordinator-only target. +- Release follow-up cache evidence: the coordinator package gate passed in + correction run `31221086294`, but the complete distributed graph still + scheduled `package-check` as a cache miss and reproduced the missing-metadata + failure. A successful cache write is therefore not a sufficient ownership + boundary for this environment-sensitive gate. +- Release follow-up verification: the CI workflow contract requires + `package-check` to run with continuous assignment disabled before Nx agents + start, requires `ci-distributed` to contain the exact `ci` dependency list + minus `package-check`, and keeps the distribution contract on medium agents. + The packed web, declarations, runtime, standalone catalog, bare React Native, + Expo, and seven-adapter gates pass locally with both fixture installs offline. ### F-225 — Noninteractive SSR emitted hidden focus geometry @@ -7349,6 +7407,17 @@ Each entry records: release-source evidence stays pinned to its measured commit. The remote tag and GitHub release remain absent; publishing them triggers external release automation and requires a separate explicit release action. +- Release follow-up evidence: the historical-revision unit test read this + repository's real `0.6.5` merge. It passed in a full clone but failed inside + the correction pull request's Nx agent workspace, which did not retain that + historical commit. +- Release follow-up decision: make the unit test construct its own temporary + Git repository with a `0.6.4` base, a `changeset-release/main` version commit, + and a no-fast-forward `0.6.5` merge. Production release status still reads + the full checkout supplied by the release workflow. +- Release follow-up verification: the hermetic release-status suite passes all + ten cases and asserts the exact synthetic merge revision without depending + on repository history. ### F-249 — Interrupted motion retained stale presentation state diff --git a/CHANGELOG.md b/CHANGELOG.md index 00334eb6..4bb5e028 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -246,6 +246,11 @@ Keep React Native chart-host focusability aligned with the shared definition contract and toggle sticky activation exactly once per accessibility action. +- Publish the stacked-bar band-and-rule cursor as catalog case 119 at + `@tanstack/react-charts-catalog/cases/119-stacked-bar-band-cursor`, bringing the + published catalog to 110 cases. The case demonstrates mixed cursor + presentation, labels, motion, and keyed updates. + - Updated dependencies [[`38cddc8`](https://github.com/TanStack/charts/commit/38cddc846c8f342aedcd237956a0057155022ae9), [`a5f9702`](https://github.com/TanStack/charts/commit/a5f97022f90043254e0e0dde174cdf2a63b6a198), [`4134429`](https://github.com/TanStack/charts/commit/4134429b49973cf64df1f36123ba8392571562eb), [`6fd52a8`](https://github.com/TanStack/charts/commit/6fd52a8910c9f933609dce14bffab5277f6325b2)]: - @tanstack/charts@0.7.0 - @tanstack/react-charts@0.7.0 @@ -281,6 +286,11 @@ Keep React Native chart-host focusability aligned with the shared definition contract and toggle sticky activation exactly once per accessibility action. +- Complete React Native host parity for shared focus and free-cursor state, + renderer-native focus and state layers, selection activation, pinned tooltip + context, viewport-filtered focus, polygon areas, and the public + `NativePaintContext.canvas` surface. + - Updated dependencies [[`38cddc8`](https://github.com/TanStack/charts/commit/38cddc846c8f342aedcd237956a0057155022ae9), [`a5f9702`](https://github.com/TanStack/charts/commit/a5f97022f90043254e0e0dde174cdf2a63b6a198), [`4134429`](https://github.com/TanStack/charts/commit/4134429b49973cf64df1f36123ba8392571562eb), [`6fd52a8`](https://github.com/TanStack/charts/commit/6fd52a8910c9f933609dce14bffab5277f6325b2)]: - @tanstack/charts@0.7.0 diff --git a/README.md b/README.md index a73478e4..f07336c0 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ server-rendered application charts. > [!IMPORTANT] > This README follows unreleased `main`. The latest published release is > TanStack Charts `0.7.0`; use its -> [release-source documentation](https://github.com/TanStack/charts/tree/4f5653e552ddf1d268b49da7046199f11b2be44c/docs). +> [release-source documentation](https://github.com/TanStack/charts/tree/v0.7.0/docs). > It is pre-alpha and not ready for production use. Most chart libraries are easy until the chart stops being standard. TanStack diff --git a/docs/comparison.md b/docs/comparison.md index cf1b8844..9bacd07f 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -170,11 +170,11 @@ Canvas composition while keeping D3 and state ownership explicit. ## Evidence and reproduction -- [Standard comparison protocol](https://github.com/TanStack/charts/blob/4f5653e552ddf1d268b49da7046199f11b2be44c/benchmarks/comparison/README.md) +- [Standard comparison protocol](https://github.com/TanStack/charts/blob/v0.7.0/benchmarks/comparison/README.md) - [Current tracked bundle baseline](https://github.com/TanStack/charts/blob/main/benchmarks/comparison/bundle-baseline.json) -- [Pinned release-source bundle baseline](https://github.com/TanStack/charts/blob/4f5653e552ddf1d268b49da7046199f11b2be44c/benchmarks/comparison/bundle-baseline.json) -- [Stress protocol](https://github.com/TanStack/charts/blob/4f5653e552ddf1d268b49da7046199f11b2be44c/benchmarks/comparison/stress/README.md) -- [Catalog conformance protocol](https://github.com/TanStack/charts/blob/4f5653e552ddf1d268b49da7046199f11b2be44c/benchmarks/conformance/README.md) +- [Pinned release-source bundle baseline](https://github.com/TanStack/charts/blob/v0.7.0/benchmarks/comparison/bundle-baseline.json) +- [Stress protocol](https://github.com/TanStack/charts/blob/v0.7.0/benchmarks/comparison/stress/README.md) +- [Catalog conformance protocol](https://github.com/TanStack/charts/blob/v0.7.0/benchmarks/conformance/README.md) ```sh pnpm benchmark:size diff --git a/docs/installation.md b/docs/installation.md index 14c7e079..b8aef086 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -5,7 +5,7 @@ description: Install TanStack Charts, compact scales, a framework adapter, and o These docs follow unreleased `main`. The latest published pre-alpha is TanStack Charts `0.7.0`; use its -[release-source docs](https://github.com/TanStack/charts/tree/4f5653e552ddf1d268b49da7046199f11b2be44c/docs) +[release-source docs](https://github.com/TanStack/charts/tree/v0.7.0/docs) for the exact surface. Install the core and compact scales in each application that authors chart definitions: diff --git a/packages/charts-core/docs/comparison.md b/packages/charts-core/docs/comparison.md index cf1b8844..9bacd07f 100644 --- a/packages/charts-core/docs/comparison.md +++ b/packages/charts-core/docs/comparison.md @@ -170,11 +170,11 @@ Canvas composition while keeping D3 and state ownership explicit. ## Evidence and reproduction -- [Standard comparison protocol](https://github.com/TanStack/charts/blob/4f5653e552ddf1d268b49da7046199f11b2be44c/benchmarks/comparison/README.md) +- [Standard comparison protocol](https://github.com/TanStack/charts/blob/v0.7.0/benchmarks/comparison/README.md) - [Current tracked bundle baseline](https://github.com/TanStack/charts/blob/main/benchmarks/comparison/bundle-baseline.json) -- [Pinned release-source bundle baseline](https://github.com/TanStack/charts/blob/4f5653e552ddf1d268b49da7046199f11b2be44c/benchmarks/comparison/bundle-baseline.json) -- [Stress protocol](https://github.com/TanStack/charts/blob/4f5653e552ddf1d268b49da7046199f11b2be44c/benchmarks/comparison/stress/README.md) -- [Catalog conformance protocol](https://github.com/TanStack/charts/blob/4f5653e552ddf1d268b49da7046199f11b2be44c/benchmarks/conformance/README.md) +- [Pinned release-source bundle baseline](https://github.com/TanStack/charts/blob/v0.7.0/benchmarks/comparison/bundle-baseline.json) +- [Stress protocol](https://github.com/TanStack/charts/blob/v0.7.0/benchmarks/comparison/stress/README.md) +- [Catalog conformance protocol](https://github.com/TanStack/charts/blob/v0.7.0/benchmarks/conformance/README.md) ```sh pnpm benchmark:size diff --git a/packages/charts-core/docs/installation.md b/packages/charts-core/docs/installation.md index 14c7e079..b8aef086 100644 --- a/packages/charts-core/docs/installation.md +++ b/packages/charts-core/docs/installation.md @@ -5,7 +5,7 @@ description: Install TanStack Charts, compact scales, a framework adapter, and o These docs follow unreleased `main`. The latest published pre-alpha is TanStack Charts `0.7.0`; use its -[release-source docs](https://github.com/TanStack/charts/tree/4f5653e552ddf1d268b49da7046199f11b2be44c/docs) +[release-source docs](https://github.com/TanStack/charts/tree/v0.7.0/docs) for the exact surface. Install the core and compact scales in each application that authors chart definitions: diff --git a/packages/react-charts-catalog/CHANGELOG.md b/packages/react-charts-catalog/CHANGELOG.md index fa4a1898..71f27be4 100644 --- a/packages/react-charts-catalog/CHANGELOG.md +++ b/packages/react-charts-catalog/CHANGELOG.md @@ -31,6 +31,11 @@ Keep React Native chart-host focusability aligned with the shared definition contract and toggle sticky activation exactly once per accessibility action. +- Publish the stacked-bar band-and-rule cursor as catalog case 119 at + `@tanstack/react-charts-catalog/cases/119-stacked-bar-band-cursor`, bringing the + published catalog to 110 cases. The case demonstrates mixed cursor + presentation, labels, motion, and keyed updates. + - Updated dependencies [[`38cddc8`](https://github.com/TanStack/charts/commit/38cddc846c8f342aedcd237956a0057155022ae9), [`a5f9702`](https://github.com/TanStack/charts/commit/a5f97022f90043254e0e0dde174cdf2a63b6a198), [`4134429`](https://github.com/TanStack/charts/commit/4134429b49973cf64df1f36123ba8392571562eb), [`6fd52a8`](https://github.com/TanStack/charts/commit/6fd52a8910c9f933609dce14bffab5277f6325b2)]: - @tanstack/charts@0.7.0 - @tanstack/react-charts@0.7.0 diff --git a/packages/react-native-charts/CHANGELOG.md b/packages/react-native-charts/CHANGELOG.md index faac9e67..cda1036e 100644 --- a/packages/react-native-charts/CHANGELOG.md +++ b/packages/react-native-charts/CHANGELOG.md @@ -31,6 +31,11 @@ Keep React Native chart-host focusability aligned with the shared definition contract and toggle sticky activation exactly once per accessibility action. +- Complete React Native host parity for shared focus and free-cursor state, + renderer-native focus and state layers, selection activation, pinned tooltip + context, viewport-filtered focus, polygon areas, and the public + `NativePaintContext.canvas` surface. + - Updated dependencies [[`38cddc8`](https://github.com/TanStack/charts/commit/38cddc846c8f342aedcd237956a0057155022ae9), [`a5f9702`](https://github.com/TanStack/charts/commit/a5f97022f90043254e0e0dde174cdf2a63b6a198), [`4134429`](https://github.com/TanStack/charts/commit/4134429b49973cf64df1f36123ba8392571562eb), [`6fd52a8`](https://github.com/TanStack/charts/commit/6fd52a8910c9f933609dce14bffab5277f6325b2)]: - @tanstack/charts@0.7.0 diff --git a/project.json b/project.json index 2624b115..e68a62ec 100644 --- a/project.json +++ b/project.json @@ -202,6 +202,31 @@ "command": "node -e \"console.log('CI targets passed.')\"" }, "cache": true + }, + "ci-distributed": { + "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", + "react-native-types", + "bundle-check", + "benchmark-check", + "catalog-check", + "react-catalog-check", + "catalog-loading-check" + ], + "options": { + "command": "node -e \"console.log('Distributed CI targets passed.')\"" + }, + "cache": true } } } diff --git a/scripts/check-packed-consumers.mjs b/scripts/check-packed-consumers.mjs index 063037b2..981f0862 100644 --- a/scripts/check-packed-consumers.mjs +++ b/scripts/check-packed-consumers.mjs @@ -612,7 +612,7 @@ async function installFixture(tarballs) { ) await writeFile( resolve(fixtureDirectory, 'pnpm-workspace.yaml'), - `packages:\n - '.'\noverrides:\n 'd3-sankey': ${JSON.stringify( + `packages:\n - '.'\nautoInstallPeers: false\noverrides:\n 'd3-sankey': ${JSON.stringify( installedDependency('d3-sankey'), )}\n '@tanstack/charts': ${JSON.stringify( coreTarball, @@ -699,7 +699,7 @@ async function verifyStandaloneCatalogConsumer(tarballs) { ) await writeFile( resolve(standaloneCatalogDirectory, 'pnpm-workspace.yaml'), - `packages:\n - '.'\noverrides:\n '@tanstack/charts': ${JSON.stringify( + `packages:\n - '.'\nautoInstallPeers: false\noverrides:\n '@tanstack/charts': ${JSON.stringify( coreTarball, )}\n '@tanstack/react-charts': ${JSON.stringify( reactTarball, diff --git a/scripts/ci-workflow.test.mjs b/scripts/ci-workflow.test.mjs index 2d4d02d9..9a3afd19 100644 --- a/scripts/ci-workflow.test.mjs +++ b/scripts/ci-workflow.test.mjs @@ -22,6 +22,10 @@ const comparisonRunner = await readFile( resolve(import.meta.dirname, './compare-chart-libraries.mjs'), 'utf8', ) +const packedConsumer = await readFile( + resolve(import.meta.dirname, './check-packed-consumers.mjs'), + 'utf8', +) const nxDistribution = await readFile( resolve(import.meta.dirname, '../.nx/workflows/distribution.yaml'), 'utf8', @@ -69,6 +73,21 @@ describe('CI workflow contract', () => { assert.doesNotMatch(setupAction, /playwright-\d+\.\d+\.\d+/) }) + test('keeps packed-consumer installs offline without resolving optional peers', () => { + assert.equal( + (packedConsumer.match(/autoInstallPeers: false/g) ?? []).length, + 2, + ) + assert.equal( + ( + packedConsumer.match( + /\['install', '--offline', '--ignore-scripts', '--frozen-lockfile=false'\]/g, + ) ?? [] + ).length, + 2, + ) + }) + test('starts static checks immediately and gates expensive pull request partitions', () => { assert.doesNotMatch(job('changes'), /^\s*needs:/m) assert.doesNotMatch(job('static'), /^\s*needs:/m) @@ -228,6 +247,23 @@ describe('CI workflow contract', () => { staticChecks.indexOf('name: Start Nx Agents') > staticChecks.indexOf('name: Setup'), ) + assert.match( + staticChecks, + /name:\s*Verify repository-derived comparison provenance[\s\S]*?NX_CLOUD_CONTINUOUS_ASSIGNMENT:\s*['"]false['"][\s\S]*?pnpm exec nx run charts-workspace:benchmark-check/, + ) + assert.ok( + staticChecks.indexOf( + 'name: Verify repository-derived comparison provenance', + ) < staticChecks.indexOf('name: Start Nx Agents'), + ) + assert.match( + staticChecks, + /name:\s*Verify packed package consumers[\s\S]*?NX_CLOUD_CONTINUOUS_ASSIGNMENT:\s*['"]false['"][\s\S]*?pnpm exec nx run charts-workspace:package-check/, + ) + assert.ok( + staticChecks.indexOf('name: Verify packed package consumers') < + staticChecks.indexOf('name: Start Nx Agents'), + ) assert.ok( staticChecks.indexOf('name: Stop Nx Agents') > staticChecks.indexOf('name: Run full cached validation graph'), @@ -238,7 +274,10 @@ describe('CI workflow contract', () => { ) assert.match(staticChecks, /git cat-file -e "\${BASE_SHA}\^\{commit\}"/) assert.match(staticChecks, /git diff --no-renames --name-only -z/) - assert.match(staticChecks, /run:\s*pnpm exec nx run charts-workspace:ci/) + assert.match( + staticChecks, + /run:\s*pnpm exec nx run charts-workspace:ci-distributed/, + ) assert.match(staticChecks, /NX_PARALLEL:\s*4/) assert.match( staticChecks, @@ -270,19 +309,13 @@ describe('CI workflow contract', () => { assert.match(staticChecks, /path:\s*\.catalog-artifact/) }) - test('assigns the package critical path to a large Nx agent', () => { + test('runs distributed checks on medium Nx agents', () => { assert.equal( nxDistribution, `distribute-on: - default: 2 linux-medium-js, 1 linux-large-js + default: 3 linux-medium-js assignment-rules: - - targets: - - package-check - run-on: - - agent: linux-large-js - parallelism: 1 - - projects: - '*' run-on: @@ -292,17 +325,32 @@ assignment-rules: ) }) - test('keeps every distributed Nx task cacheable', () => { + test('keeps the coordinator-only package gate out of distribution', () => { const ci = projectConfig.targets.ci + const distributedCi = projectConfig.targets['ci-distributed'] assert.match(nxConfig.nxCloudId, /^[0-9a-f]{24}$/) assert.equal(ci.cache, true) + assert.equal(distributedCi.cache, true) + assert.equal(distributedCi.executor, ci.executor) + assert.ok(ci.dependsOn.includes('package-check')) + assert.deepEqual( + distributedCi.dependsOn, + ci.dependsOn.filter((target) => target !== 'package-check'), + ) assert.ok(ci.dependsOn.includes('react-native-types')) + assert.ok(distributedCi.dependsOn.includes('react-native-types')) + assert.ok(!distributedCi.dependsOn.includes('package-check')) assert.ok(!ci.dependsOn.includes('workspace-diff-check')) + assert.ok(!distributedCi.dependsOn.includes('workspace-diff-check')) assert.equal(projectConfig.targets['workspace-diff-check'].cache, false) assert.deepEqual(nxConfig.targetDefaults['react-native-types'], { cache: true, inputs: ['reactNativeTypes'], }) + assert.deepEqual(nxConfig.targetDefaults['package-check'], { + cache: true, + inputs: ['release'], + }) assert.ok( nxConfig.namedInputs.format.includes( '!{workspaceRoot}/.nx/workspace-data/**/*', @@ -514,7 +562,7 @@ assignment-rules: const publish = job('publish-catalog') assert.match( publish, - /if:\s*github\.event_name == 'push' && github\.ref == 'refs\/heads\/main'/, + /if:\s*always\(\) && !cancelled\(\) && github\.event_name == 'push' && github\.ref == 'refs\/heads\/main' && needs\.static\.result == 'success' && needs\.ci\.result == 'success'/, ) assert.deepEqual(needs(publish), ['static', 'ci']) assert.match(publish, /contents:\s*write/) diff --git a/scripts/release-status.test.mjs b/scripts/release-status.test.mjs index 959193d7..72e7bd88 100644 --- a/scripts/release-status.test.mjs +++ b/scripts/release-status.test.mjs @@ -1,10 +1,16 @@ import { describe, expect, it } from 'vitest' -import { resolve } from 'node:path' +import { execFile } from 'node:child_process' +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { promisify } from 'node:util' import { classifyReleaseStatus, readReleaseRevision, } from './release-status.mjs' +const execFileAsync = promisify(execFile) + describe('automated release status', () => { it('waits while changesets still need a version pull request', () => { expect( @@ -153,10 +159,63 @@ describe('automated release status', () => { }) it('finds the changesets merge that introduced the current version', async () => { - const repositoryRoot = resolve(import.meta.dirname, '..') - const revision = await readReleaseRevision(repositoryRoot, '0.6.5') + const repositoryRoot = await mkdtemp( + join(tmpdir(), 'charts-release-status-'), + ) + + try { + await git(repositoryRoot, 'init', '--initial-branch=main') + await git(repositoryRoot, 'config', 'user.name', 'Release Status Test') + await git( + repositoryRoot, + 'config', + 'user.email', + 'release-status@example.com', + ) + await git(repositoryRoot, 'config', 'commit.gpgsign', 'false') + await git(repositoryRoot, 'config', 'merge.gpgsign', 'false') + await git(repositoryRoot, 'config', 'core.hooksPath', '.git/no-hooks') + + const manifestDirectory = join(repositoryRoot, 'packages', 'charts-core') + const manifestPath = join(manifestDirectory, 'package.json') + await mkdir(manifestDirectory, { recursive: true }) + await writeManifest(manifestPath, '0.6.4') + await git(repositoryRoot, 'add', 'packages/charts-core/package.json') + await git(repositoryRoot, 'commit', '-m', 'chore: initial version') + + await git(repositoryRoot, 'switch', '-c', 'changeset-release/main') + await writeManifest(manifestPath, '0.6.5') + await git(repositoryRoot, 'add', 'packages/charts-core/package.json') + await git(repositoryRoot, 'commit', '-m', 'chore: version packages') + + await git(repositoryRoot, 'switch', 'main') + await git( + repositoryRoot, + 'merge', + '--no-ff', + 'changeset-release/main', + '-m', + 'Merge pull request #1 from TanStack/changeset-release/main', + ) - expect(revision).toMatch(/^[0-9a-f]{40}$/) - expect(revision).toBe('4f5653e552ddf1d268b49da7046199f11b2be44c') + const { stdout } = await git(repositoryRoot, 'rev-parse', 'HEAD') + const expectedRevision = stdout.trim() + const revision = await readReleaseRevision(repositoryRoot, '0.6.5') + + expect(revision).toBe(expectedRevision) + } finally { + await rm(repositoryRoot, { recursive: true, force: true }) + } }) }) + +function git(repositoryRoot, ...args) { + return execFileAsync('git', args, { cwd: repositoryRoot }) +} + +async function writeManifest(manifestPath, version) { + await writeFile( + manifestPath, + `${JSON.stringify({ name: '@tanstack/charts', version }, null, 2)}\n`, + ) +} diff --git a/scripts/sync-release-version.mjs b/scripts/sync-release-version.mjs index 3bae5001..5d5656f8 100644 --- a/scripts/sync-release-version.mjs +++ b/scripts/sync-release-version.mjs @@ -5,11 +5,11 @@ import { pathToFileURL } from 'node:url' import { readReleasePackages } from './release-package-config.mjs' export const releaseVersionSources = [ - { path: 'README.md', references: 1 }, + { path: 'README.md', references: 2, tagReferences: 1 }, { path: 'MARKETING.md', references: 6 }, { path: 'docs/overview.md', references: 1 }, - { path: 'docs/installation.md', references: 1 }, - { path: 'docs/comparison.md', references: 1 }, + { path: 'docs/installation.md', references: 2, tagReferences: 1 }, + { path: 'docs/comparison.md', references: 5, tagReferences: 4 }, ] export function changelogVersions(source) { @@ -80,7 +80,7 @@ export async function syncReleaseVersion({ assert.ok(previousVersion, `Root changelog has no release before ${version}`) await Promise.all( - releaseVersionSources.map(async ({ path, references }) => { + releaseVersionSources.map(async ({ path, references, tagReferences }) => { const target = resolve(repositoryRoot, path) const source = await readFile(target, 'utf8') const next = syncReleaseVersionReference( @@ -90,11 +90,30 @@ export async function syncReleaseVersion({ path, references, ) + if (tagReferences !== undefined) { + assert.equal( + releaseTagReferenceCount(next, version), + tagReferences, + `${path} must contain ${tagReferences} immutable v${version} release ${tagReferences === 1 ? 'link' : 'links'}`, + ) + } if (next !== source) await writeFile(target, next) }), ) } +export function releaseTagReferenceCount(source, version) { + const escaped = version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + return [ + ...source.matchAll( + new RegExp( + `https://github\\.com/TanStack/charts/(?:tree|blob)/v${escaped}/`, + 'g', + ), + ), + ].length +} + const entrypoint = process.argv[1] if (entrypoint && import.meta.url === pathToFileURL(resolve(entrypoint)).href) { await syncReleaseVersion() diff --git a/scripts/sync-release-version.test.mjs b/scripts/sync-release-version.test.mjs index 751289e7..3fc06597 100644 --- a/scripts/sync-release-version.test.mjs +++ b/scripts/sync-release-version.test.mjs @@ -3,6 +3,7 @@ import { resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { changelogVersions, + releaseTagReferenceCount, releaseVersionSources, syncReleaseVersionReference, } from './sync-release-version.mjs' @@ -18,7 +19,7 @@ describe('release version synchronization', () => { ) expect(releaseManifest.version).toMatch(/^\d+\.\d+\.\d+/) - for (const { path, references } of releaseVersionSources) { + for (const { path, references, tagReferences } of releaseVersionSources) { const source = await readFile(resolve(repositoryRoot, path), 'utf8') expect( syncReleaseVersionReference( @@ -29,6 +30,11 @@ describe('release version synchronization', () => { references, ), ).toBe(source) + if (tagReferences !== undefined) { + expect(releaseTagReferenceCount(source, releaseManifest.version)).toBe( + tagReferences, + ) + } } }) @@ -85,6 +91,20 @@ describe('release version synchronization', () => { ).toBe('TanStack Charts `0.6.1`; tag /v0.6.1/; Observable Plot `0.6.17`.') }) + it('counts only immutable links for the matching release tag', () => { + expect( + releaseTagReferenceCount( + [ + 'https://github.com/TanStack/charts/tree/v0.7.0/docs', + 'https://github.com/TanStack/charts/blob/v0.7.0/README.md', + 'https://github.com/TanStack/charts/blob/main/README.md', + 'https://github.com/TanStack/charts/blob/4f5653e552ddf1d268b49da7046199f11b2be44c/README.md', + ].join('\n'), + '0.7.0', + ), + ).toBe(2) + }) + it('rejects a release-facing file with no recognized version', () => { expect(() => syncReleaseVersionReference(