diff --git a/.changeset/size-history-ci-policy.md b/.changeset/size-history-ci-policy.md
new file mode 100644
index 00000000..a845151c
--- /dev/null
+++ b/.changeset/size-history-ci-policy.md
@@ -0,0 +1,2 @@
+---
+---
diff --git a/docs/src/content/docs/guides/performance-resources.md b/docs/src/content/docs/guides/performance-resources.md
index 7fca3164..5760d983 100644
--- a/docs/src/content/docs/guides/performance-resources.md
+++ b/docs/src/content/docs/guides/performance-resources.md
@@ -961,6 +961,50 @@ bun run --filter @sheetwrite/bench bench:formula
Registry measurements for every published release. Each delta is measured against the release immediately before it.
+v0.3.0v0.3.1
+
+
+
+
+
+| Package | Tarball | Installed |
+
+
+@sheetwrite/wasm |
+287.6 KiB0 B+0.0% |
+795.6 KiB0 B+0.0% |
+
+
+@sheetwrite/core |
+517.4 KiB+11 B+0.0% |
+2.71 MiB+24 B+0.0% |
+
+
+@sheetwrite/xlsx |
+85.5 KiB0 B+0.0% |
+429.6 KiB0 B+0.0% |
+
+
+@sheetwrite/react |
+9.2 KiB0 B+0.0% |
+31.9 KiB0 B+0.0% |
+
+
+@sheetwrite/vue |
+10.1 KiB0 B+0.0% |
+35.7 KiB0 B+0.0% |
+
+
+@sheetwrite/svelte |
+6.2 KiB0 B+0.0% |
+18.5 KiB0 B+0.0% |
+
+
+
+
+
+
+
v0.2.0v0.3.0
diff --git a/scripts/changeset-ci.ts b/scripts/changeset-ci.ts
index c07f3be2..886f16a5 100644
--- a/scripts/changeset-ci.ts
+++ b/scripts/changeset-ci.ts
@@ -4,10 +4,52 @@ import { PUBLISHABLE_PACKAGE_ORDER } from "./workspace-tooling.js";
const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
const REPOSITORY_ROOT = resolve(import.meta.dir, "..");
+const PACKAGE_SIZE_HISTORY_PREFIX = "automation/package-size-history-";
+const REQUIRED_PACKAGE_SIZE_HISTORY_FILES = new Set([
+ "docs/src/content/docs/guides/performance-resources.md",
+ "scripts/size-history.json",
+]);
+const ALLOWED_PACKAGE_SIZE_HISTORY_FILES = new Set([
+ ...REQUIRED_PACKAGE_SIZE_HISTORY_FILES,
+ "docs/src/generated/docs-contract.json",
+]);
export function releaseVersionFromHeadRef(headRef: string | undefined): string | undefined {
return headRef !== undefined && STABLE_VERSION.test(headRef) ? headRef : undefined;
}
+export function packageSizeHistoryVersionFromHeadRef(
+ headRef: string | undefined,
+): string | undefined {
+ if (headRef === undefined || !headRef.startsWith(PACKAGE_SIZE_HISTORY_PREFIX)) return undefined;
+ const version = headRef.slice(PACKAGE_SIZE_HISTORY_PREFIX.length);
+ return STABLE_VERSION.test(version) ? version : undefined;
+}
+export function isGeneratedPackageSizeHistoryChange(paths: readonly string[]): boolean {
+ const uniquePaths = new Set(paths);
+ return (
+ uniquePaths.size === paths.length &&
+ [...REQUIRED_PACKAGE_SIZE_HISTORY_FILES].every((path) => uniquePaths.has(path)) &&
+ paths.every((path) => ALLOWED_PACKAGE_SIZE_HISTORY_FILES.has(path))
+ );
+}
+
+async function changedFilesSinceDevelop(): Promise {
+ const child = Bun.spawn(["git", "diff", "--name-only", "origin/develop...HEAD"], {
+ cwd: REPOSITORY_ROOT,
+ stdout: "pipe",
+ stderr: "pipe",
+ });
+ const [stdout, stderr, exitCode] = await Promise.all([
+ new Response(child.stdout).text(),
+ new Response(child.stderr).text(),
+ child.exited,
+ ]);
+ if (exitCode !== 0) throw new Error(`Unable to inspect package size history diff: ${stderr}`);
+ return stdout
+ .split("\n")
+ .map((path) => path.trim())
+ .filter((path) => path.length > 0);
+}
export function validateReleasePackageVersions(version: string, root = REPOSITORY_ROOT): void {
for (const name of PUBLISHABLE_PACKAGE_ORDER) {
@@ -30,6 +72,19 @@ async function main(): Promise {
console.log(`Release package versions match branch ${releaseVersion}`);
return;
}
+ const packageSizeHistoryVersion = packageSizeHistoryVersionFromHeadRef(
+ process.env.GITHUB_HEAD_REF,
+ );
+ if (packageSizeHistoryVersion !== undefined) {
+ validateReleasePackageVersions(packageSizeHistoryVersion);
+ if (isGeneratedPackageSizeHistoryChange(await changedFilesSinceDevelop())) {
+ console.log(`Package size history versions match branch ${packageSizeHistoryVersion}`);
+ return;
+ }
+ console.log(
+ "Package size history branch contains non-generated changes; requiring a changeset",
+ );
+ }
const child = Bun.spawn(["bun", "run", "changeset:status", "--", "--since=origin/develop"], {
cwd: REPOSITORY_ROOT,
stdin: "inherit",
diff --git a/scripts/size-history.json b/scripts/size-history.json
index d125620e..cc17e1a0 100644
--- a/scripts/size-history.json
+++ b/scripts/size-history.json
@@ -237,6 +237,85 @@
"unit": "count"
}
}
+ },
+ {
+ "version": "0.3.1",
+ "capturedAt": "2026-07-23T17:11:35.485Z",
+ "source": "npm registry published artifacts",
+ "metrics": {
+ "package.@sheetwrite/wasm.tarballBytes": {
+ "actual": 294455,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/wasm.unpackedBytes": {
+ "actual": 814645,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/wasm.fileCount": {
+ "actual": 12,
+ "unit": "count"
+ },
+ "package.@sheetwrite/core.tarballBytes": {
+ "actual": 529843,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/core.unpackedBytes": {
+ "actual": 2846784,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/core.fileCount": {
+ "actual": 335,
+ "unit": "count"
+ },
+ "package.@sheetwrite/xlsx.tarballBytes": {
+ "actual": 87523,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/xlsx.unpackedBytes": {
+ "actual": 439871,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/xlsx.fileCount": {
+ "actual": 59,
+ "unit": "count"
+ },
+ "package.@sheetwrite/react.tarballBytes": {
+ "actual": 9398,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/react.unpackedBytes": {
+ "actual": 32679,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/react.fileCount": {
+ "actual": 8,
+ "unit": "count"
+ },
+ "package.@sheetwrite/vue.tarballBytes": {
+ "actual": 10354,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/vue.unpackedBytes": {
+ "actual": 36578,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/vue.fileCount": {
+ "actual": 8,
+ "unit": "count"
+ },
+ "package.@sheetwrite/svelte.tarballBytes": {
+ "actual": 6354,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/svelte.unpackedBytes": {
+ "actual": 18919,
+ "unit": "bytes"
+ },
+ "package.@sheetwrite/svelte.fileCount": {
+ "actual": 10,
+ "unit": "count"
+ }
+ }
}
]
}
diff --git a/scripts/workspace-tooling.test.ts b/scripts/workspace-tooling.test.ts
index 612001e3..7a18baa2 100644
--- a/scripts/workspace-tooling.test.ts
+++ b/scripts/workspace-tooling.test.ts
@@ -2,7 +2,12 @@ import { afterEach, describe, expect, it } from "bun:test";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
-import { releaseVersionFromHeadRef, validateReleasePackageVersions } from "./changeset-ci.js";
+import {
+ isGeneratedPackageSizeHistoryChange,
+ packageSizeHistoryVersionFromHeadRef,
+ releaseVersionFromHeadRef,
+ validateReleasePackageVersions,
+} from "./changeset-ci.js";
import {
assertUniqueOrderedNodes,
PACKAGE_BUILD_NODES,
@@ -246,6 +251,40 @@ describe("changeset workspace contract", () => {
it("runs the release-aware Changesets status command", () => {
expect(releaseVersionFromHeadRef("0.3.1")).toBe("0.3.1");
expect(releaseVersionFromHeadRef("feature/docs")).toBeUndefined();
+ expect(packageSizeHistoryVersionFromHeadRef("automation/package-size-history-0.3.1")).toBe(
+ "0.3.1",
+ );
+ expect(packageSizeHistoryVersionFromHeadRef("automation/package-size-history-next")).toBe(
+ undefined,
+ );
+ expect(
+ isGeneratedPackageSizeHistoryChange([
+ "docs/src/content/docs/guides/performance-resources.md",
+ "scripts/size-history.json",
+ ]),
+ ).toBeTrue();
+ expect(
+ isGeneratedPackageSizeHistoryChange([
+ "docs/src/content/docs/guides/performance-resources.md",
+ "docs/src/generated/docs-contract.json",
+ "scripts/size-history.json",
+ ]),
+ ).toBeTrue();
+ expect(
+ isGeneratedPackageSizeHistoryChange([
+ "docs/src/content/docs/guides/performance-resources.md",
+ "scripts/changeset-ci.ts",
+ "scripts/size-history.json",
+ ]),
+ ).toBeFalse();
+ expect(isGeneratedPackageSizeHistoryChange(["scripts/size-history.json"])).toBeFalse();
+ expect(
+ isGeneratedPackageSizeHistoryChange([
+ "docs/src/content/docs/guides/performance-resources.md",
+ "scripts/size-history.json",
+ "scripts/size-history.json",
+ ]),
+ ).toBeFalse();
expect(() => validateReleasePackageVersions("0.3.1")).not.toThrow();
expect(() => validateReleasePackageVersions("0.3.2")).toThrow(
"Release branch 0.3.2 requires @sheetwrite/wasm@0.3.2",