Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions API-FRICTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ Each entry records:
| 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 |
| F-152 | Version bumps invalidated workspace bundle evidence | Tooling/Release | resolved |
| F-153 | Changesets left release-facing version claims behind | Tooling/Release | resolved |

## Findings

Expand Down Expand Up @@ -3616,3 +3618,39 @@ Each entry records:
`download-artifact` v7 by exact commit.
- Verification: workflow contracts require immutable action revisions; the
final GitHub matrix exercises every upload path before merge.

### F-152 — Version-only releases invalidated workspace bundle evidence

- Status: resolved
- Severity: high
- Owner: Tooling/Release
- Observed in: the first automated Changesets version pull request
- Friction: the comparison baseline required the TanStack workspace package
version to match its last measured release. Changesets advanced the package
to 0.0.2 without changing measured source, so both the bundle gate and docs
contract rejected an otherwise unchanged artifact.
- Decision: keep exact installed-version checks for external registry
packages. For the TanStack workspace, use the existing exact Git revision of
every measured input as the authoritative provenance boundary; a version-only
release no longer requires rewriting bundle measurements.
- Verification: focused contracts accept a workspace release changing from
0.0.1 to 0.0.2, still reject external package-version drift, and retain the
exact workspace source-revision check.

### F-153 — Changesets left release-facing version claims behind

- Status: resolved
- Severity: high
- Owner: Tooling/Release
- Observed in: auditing the generated 0.0.2 version pull request
- Friction: Changesets advanced manifests, package changelogs, and the lockfile
but left the root README, canonical docs, comparison evidence links, and
marketing material on 0.0.1. Merging the generated pull request would have
published a newer package line beside stale install and status claims.
- Decision: derive the previous release from the generated root changelog,
advance an explicit allowlist of release-facing sources, then run the
repository's canonical docs sync before formatting the version pull request.
Historical changelog and friction evidence remain immutable.
- 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.
7 changes: 4 additions & 3 deletions benchmarks/comparison/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ pnpm benchmark:update-baseline

Update the baseline only after reviewing the built files and confirming that a
size change is intentional. The check permits 3% or 512 bytes, whichever is
larger. It rejects package-version or chart/tier matrix drift before comparing
bytes. The TanStack source revision must exactly match the last commit that
changed core source or a transitive TanStack comparison input;
larger. It rejects external package-version or chart/tier matrix drift before
comparing bytes. TanStack workspace releases may advance without rewriting
unchanged measurements; its source revision must exactly match the last commit
that changed core source or a transitive TanStack comparison input;
documentation-only commits do not stale the evidence. It also requires normal
comparison artifacts to contain zero bytes from the stress-probe modules;
optional measurement machinery must disappear through direct build-time
Expand Down
6 changes: 3 additions & 3 deletions docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ 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 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.
the exact workspace source included in this release and pinned competitor
packages. This comparison records architectural differences and reproducible
evidence without turning untested behavior into a checkmark.

## Tested versions

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"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"
"changeset:version": "changeset version && node scripts/sync-release-changelog.mjs && node scripts/sync-release-version.mjs && pnpm docs:sync && pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile && pnpm format"
},
"devDependencies": {
"@changesets/changelog-github": "0.7.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/charts-core/docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ 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 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.
the exact workspace source included in this release and pinned competitor
packages. This comparison records architectural differences and reproducible
evidence without turning untested behavior into a checkmark.

## Tested versions

Expand Down
12 changes: 7 additions & 5 deletions scripts/compare-chart-libraries.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
formatComparisonImplementationDetail,
} from './benchmark/comparison-capabilities.mjs'
import {
comparisonInstalledVersionFailure,
tanstackComparisonRevision,
tanstackComparisonSourceFailure,
} from './comparison-source-revision.mjs'
Expand Down Expand Up @@ -1164,12 +1165,13 @@ async function checkBundleBaseline(bundles, actualVersions) {
continue
}
const actualVersion = actualVersions[library.id]
if (actualVersion && actualVersion !== expectedVersion) {
failures.push(
`${library.label}: installed version ${actualVersion} does not match baseline ${expectedVersion}`,
)
}
const source = baseline.sources?.[library.id]
const versionFailure = comparisonInstalledVersionFailure(
source,
actualVersion,
expectedVersion,
)
if (versionFailure) failures.push(`${library.label}: ${versionFailure}`)
if (library.id === 'tanstack') {
const sourceFailure = tanstackComparisonSourceFailure(
source,
Expand Down
16 changes: 16 additions & 0 deletions scripts/comparison-source-revision.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,19 @@ export function tanstackComparisonSourceFailure(source, expectedRevision) {
return `bundle baseline workspace revision ${source.revision} does not match measured inputs ${expectedRevision}`
}
}

export function comparisonInstalledVersionFailure(
source,
actualVersion,
baselineVersion,
) {
if (
source?.kind === 'workspace' ||
!actualVersion ||
actualVersion === baselineVersion
) {
return
}

return `installed version ${actualVersion} does not match baseline ${baselineVersion}`
}
18 changes: 18 additions & 0 deletions scripts/comparison-source-revision.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { tmpdir } from 'node:os'
import { dirname, resolve } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import {
comparisonInstalledVersionFailure,
tanstackComparisonInputPaths,
tanstackComparisonRevision,
tanstackComparisonSourceFailure,
Expand Down Expand Up @@ -56,6 +57,23 @@ describe('TanStack comparison source provenance', () => {
`bundle baseline workspace revision ${recordedRevision} does not match measured inputs ${expectedRevision}`,
)
})

it('uses source revisions for workspaces and versions for installed packages', () => {
expect(
comparisonInstalledVersionFailure(
{ kind: 'workspace', revision: 'a'.repeat(40) },
'0.0.2',
'0.0.1',
),
).toBeUndefined()
expect(
comparisonInstalledVersionFailure(
{ kind: 'package', packageName: 'chart.js', version: '4.5.1' },
'4.5.2',
'4.5.1',
),
).toBe('installed version 4.5.2 does not match baseline 4.5.1')
})
})

function commitAll(repository, message) {
Expand Down
6 changes: 5 additions & 1 deletion scripts/docs-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,11 @@ export function comparisonBaselineContractFailures(baseline, expectedVersions) {
}
for (const library of chartLibraries) {
const manifestVersion = expectedVersions[library.id]
if (baseline.packageVersions?.[library.id] !== manifestVersion) {
const baselineVersion = baseline.packageVersions?.[library.id]
if (
!baselineVersion ||
(library.id !== 'tanstack' && baselineVersion !== manifestVersion)
) {
failures.push(
`comparison bundle baseline version is stale for ${library.label}: expected ${manifestVersion}`,
)
Expand Down
6 changes: 6 additions & 0 deletions scripts/docs-contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ Body
}

expect(comparisonBaselineContractFailures(baseline, versions)).toEqual([])
expect(
comparisonBaselineContractFailures(baseline, {
...versions,
tanstack: '0.0.2',
}),
).toEqual([])

const stale = structuredClone(baseline)
stale.packageVersions.chartjs = '4.5.0'
Expand Down
10 changes: 10 additions & 0 deletions scripts/release-workflow.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const changesetConfig = JSON.parse(
'utf8',
),
)
const packageManifest = JSON.parse(
await readFile(resolve(import.meta.dirname, '../package.json'), 'utf8'),
)

describe('release workflow contract', () => {
test('versions only the fixed public package set', () => {
Expand All @@ -48,6 +51,13 @@ describe('release workflow contract', () => {
])
})

test('synchronizes changelogs and release-facing docs in version pull requests', () => {
assert.equal(
packageManifest.scripts['changeset:version'],
'changeset version && node scripts/sync-release-changelog.mjs && node scripts/sync-release-version.mjs && pnpm docs:sync && pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile && pnpm format',
)
})

test('orchestrates versions and tags only from successful main CI', () => {
assert.match(
workflow,
Expand Down
94 changes: 94 additions & 0 deletions scripts/sync-release-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
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 const releaseVersionSources = [
{ path: 'README.md', references: 1 },
{ path: 'MARKETING.md', references: 6 },
{ path: 'docs/overview.md', references: 1 },
{ path: 'docs/installation.md', references: 1 },
{ path: 'docs/comparison.md', references: 5 },
]

export function changelogVersions(source) {
return [
...source.matchAll(/^## (\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\s|$)/gm),
]
.map((match) => match[1])
.filter((version, index, versions) => versions.indexOf(version) === index)
}

export function syncReleaseVersionReference(
source,
previousVersion,
version,
path,
expectedReferences,
) {
const previousReferences = source.split(previousVersion).length - 1
const currentReferences = source.split(version).length - 1
const expectedCount = `${expectedReferences} ${
expectedReferences === 1 ? 'reference' : 'references'
}`
if (previousReferences === 0) {
assert.equal(
currentReferences,
expectedReferences,
`${path} must contain ${expectedCount} to release ${version}`,
)
return source
}

assert.equal(
previousReferences,
expectedReferences,
`${path} must contain ${expectedCount} to release ${previousVersion}`,
)
assert.equal(
currentReferences,
0,
`${path} mixes release versions ${previousVersion} and ${version}`,
)
return source.replaceAll(previousVersion, version)
}

export async function syncReleaseVersion({
repositoryRoot = resolve(import.meta.dirname, '..'),
} = {}) {
const packages = await readReleasePackages(repositoryRoot)
const version = packages[0].manifest.version
const rootChangelog = await readFile(
resolve(repositoryRoot, 'CHANGELOG.md'),
'utf8',
)
const versions = changelogVersions(rootChangelog)
assert.equal(
versions[0],
version,
`Root changelog must begin with release ${version}`,
)
const previousVersion = versions.find((entry) => entry !== version)
assert.ok(previousVersion, `Root changelog has no release before ${version}`)

await Promise.all(
releaseVersionSources.map(async ({ path, references }) => {
const target = resolve(repositoryRoot, path)
const source = await readFile(target, 'utf8')
const next = syncReleaseVersionReference(
source,
previousVersion,
version,
path,
references,
)
if (next !== source) await writeFile(target, next)
}),
)
}

const entrypoint = process.argv[1]
if (entrypoint && import.meta.url === pathToFileURL(resolve(entrypoint)).href) {
await syncReleaseVersion()
}
Loading