From 4375aa221caca8ea5c351d8fcb685d271793ec60 Mon Sep 17 00:00:00 2001 From: 3mdistal <86723305+3mdistal@users.noreply.github.com> Date: Wed, 21 Oct 2026 20:52:06 -0400 Subject: [PATCH 1/3] Keep stable releases from rewriting the community QR template Stable release prep now runs Changesets with bumpVersionsWithWorkspaceProtocolOnly, so a release no longer edits QR's plain npm range on @agent-native/core. That rewrite broke the lockfile and Dispatch's QR blob pins after every release, and the release-time pin rewrite would make the auto-merge guard refuse the next release PR. Snapshot releases keep the shared config, so their peer ranges still move to each snapshot. Remove the QR lock and pin sync script, resolve QR's Core from npm, and stop release prep with a frozen-lockfile check when a manifest and the lockfile disagree. Co-Authored-By: Claude Opus 5.5 --- package.json | 2 +- pnpm-lock.yaml | 350 ++--------------------- scripts/changeset-version-stable.ts | 74 +++++ scripts/package-release-workflow.test.ts | 174 +++++------ scripts/sync-community-qr-core-lock.ts | 112 -------- 5 files changed, 171 insertions(+), 541 deletions(-) create mode 100644 scripts/changeset-version-stable.ts delete mode 100644 scripts/sync-community-qr-core-lock.ts diff --git a/package.json b/package.json index 97ef9dcd861..6c6ab8918de 100644 --- a/package.json +++ b/package.json @@ -203,7 +203,7 @@ "changeset:add": "changeset add", "changeset:status": "changeset status --since=origin/main", "changeset:version": "changeset version", - "changeset:release-version": "pnpm changeset:version && pnpm exec tsx scripts/sync-community-qr-core-lock.ts && pnpm changelog:compact", + "changeset:release-version": "pnpm exec tsx scripts/changeset-version-stable.ts && pnpm changelog:compact && pnpm install --frozen-lockfile --lockfile-only --ignore-scripts", "changelog:compact": "tsx scripts/compact-changelogs.ts --write", "guard:changelog": "tsx scripts/compact-changelogs.ts --check", "changeset:publish": "pnpm -r build && changeset publish", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c0727ef539..1e68627e038 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5668,7 +5668,7 @@ importers: dependencies: '@agent-native/core': specifier: ^0.214.0 - version: link:../../../packages/core + version: 0.214.0(de0204abe5993dfa30bb15f10a035ff4) postgres: specifier: ^3.4.9 version: 3.4.9 @@ -8503,155 +8503,14 @@ packages: '@ag-ui/core@0.0.59': resolution: {integrity: sha512-hDgy4ipTqXieT8YG8Mr917Y+FD/f11VK1GefZ5CwTDCuNqS/oTwjJ5l/DZkicThgS8hQW/Y7wPylPBMBJ8BkUg==} - '@agent-native/agentkit@0.210.2': - resolution: {integrity: sha512-OP2+TWO5NWJBbSZebEQMT2DxDpdHdtm8NBhPU0r5L8h3u4b68ULm/LJV9Xpshp3jVdRy5Jp2+CqbFSoezT/Rdg==} - - '@agent-native/agentkit@0.212.0': - resolution: {integrity: sha512-20tRARnohmr/Tl2RSU2TQNICREzqVVvj/m2O3yUFBklWAnEEqFIz0yOC3wvDLbgjRow2iHKJU5y2kxR4v00NDQ==} - - '@agent-native/core-corpus@0.210.2': - resolution: {integrity: sha512-XafLUGe0yOrBEIGa8l0gaKsNOYIjxHQ96ZvzBJ2gUi8sQHaqCCfbIFFGKifHatN/B80i7bQCkWOOSMjrXUwnVQ==} - - '@agent-native/core-corpus@0.212.0': - resolution: {integrity: sha512-mqN7+pMgGmMgfhhnkOzLA7cdRUoV33crvVukxrrsPIP4wLZxxhcuoWOf+0+IDaYbxaZSkOQ4Z0xlDxxwfSgJOw==} - - '@agent-native/core-corpus@0.212.1': - resolution: {integrity: sha512-hdgtwyt8/07l2QxeF5PmTTcnfjzdhkOj1Y323BPWdzf36lxIn4LABrSUFkJbPshDGfc1RRN2kN1nS0z2Ku+3HA==} + '@agent-native/agentkit@0.214.0': + resolution: {integrity: sha512-mYYYtTVv0BVCwKcEeFRlpxZCYuor0bbvDN373VH2sLpD4iWBLIXDGfgo4JBn9zjMn1o0JP6AEF7JZ+xjWvmNZg==} - '@agent-native/core@0.210.2': - resolution: {integrity: sha512-bMhyOLLzT8NFcHN79zAYCkjRBHW2S/Zh+/l9dFCmbmE/UqLSXnCmDCsLa0L+7eMxbVAT+/HeTYCIVlv7yrfQlw==} - engines: {node: '>=22.22.0'} - hasBin: true - peerDependencies: - '@agent-native/core-corpus': '>=0.196.0' - '@ai-sdk/anthropic': '>=3' - '@ai-sdk/cohere': '>=3' - '@ai-sdk/google': '>=3' - '@ai-sdk/groq': '>=3' - '@ai-sdk/mistral': '>=3' - '@ai-sdk/openai': '>=3' - '@amplitude/analytics-browser': ^2.45.8 - '@anthropic-ai/tokenizer': ^0.0.4 - '@better-auth/scim': 1.7.6 - '@better-auth/sso': 1.7.6 - '@builder.io/ai-utils': ^0.84.0 - '@electric-sql/pglite': 0.5.9 - '@launchdarkly/node-server-sdk': ^9.13.6 - '@openrouter/ai-sdk-provider': '>=2' - '@opentelemetry/api': ^1.9.1 - '@resvg/resvg-js': ^2.6.2 - '@rrweb/record': ^2.1.0 - '@sentry/browser': ^10.60.0 || ^11.0.0 - '@sentry/node': ^10.60.0 || ^11.0.0 - '@sentry/vite-plugin': ^5.4.0 - '@tailwindcss/typography': '>=0.5' - '@tailwindcss/vite': '>=4' - '@tanstack/react-query': 5.101.2 - '@typesafe-ai/sdk': ^0.6.0 - '@vitejs/plugin-react-swc': '>=4' - ai: '>=6' - ai-sdk-ollama: '>=3' - botframework-connector: ^4.23.3 - drizzle-kit: '>=0.31' - jszip: ^3.10.1 - mammoth: ^1.12.0 - node-pty: '>=1' - officeparser: ^7.3.0 - prettier: ^3.8.3 - react: 19.2.7 - react-dom: 19.2.7 - react-router: '>=8' - sharp: ^0.35.3 - tailwindcss: '>=4' - ts-morph: ^28.0.0 - vite: '>=7' - xlsx: ^0.18.5 - peerDependenciesMeta: - '@agent-native/core-corpus': - optional: true - '@ai-sdk/anthropic': - optional: true - '@ai-sdk/cohere': - optional: true - '@ai-sdk/google': - optional: true - '@ai-sdk/groq': - optional: true - '@ai-sdk/mistral': - optional: true - '@ai-sdk/openai': - optional: true - '@amplitude/analytics-browser': - optional: true - '@anthropic-ai/tokenizer': - optional: true - '@better-auth/scim': - optional: true - '@better-auth/sso': - optional: true - '@builder.io/ai-utils': - optional: true - '@electric-sql/pglite': - optional: true - '@launchdarkly/node-server-sdk': - optional: true - '@openrouter/ai-sdk-provider': - optional: true - '@opentelemetry/api': - optional: true - '@resvg/resvg-js': - optional: true - '@rrweb/record': - optional: true - '@sentry/browser': - optional: true - '@sentry/node': - optional: true - '@sentry/vite-plugin': - optional: true - '@tailwindcss/typography': - optional: true - '@tailwindcss/vite': - optional: true - '@tanstack/react-query': - optional: true - '@typesafe-ai/sdk': - optional: true - '@vitejs/plugin-react-swc': - optional: true - ai: - optional: true - ai-sdk-ollama: - optional: true - botframework-connector: - optional: true - drizzle-kit: - optional: true - jszip: - optional: true - mammoth: - optional: true - node-pty: - optional: true - officeparser: - optional: true - prettier: - optional: true - react-router: - optional: true - sharp: - optional: true - tailwindcss: - optional: true - ts-morph: - optional: true - vite: - optional: true - xlsx: - optional: true + '@agent-native/core-corpus@0.214.0': + resolution: {integrity: sha512-8pnVBO2tuxxm7wCAOG//f7hym9to9EUQcL10PnEg5RaRHSq8lsvCdSJX11xNfgugEt6N2FTFB260aKd/I1+ZIA==} - '@agent-native/core@0.212.0': - resolution: {integrity: sha512-hQoZPCdoRehI84ZUxyNX8TVDaqtiIooQhLw2bPBHd1SN1OPl+hPjDnTDYHrBRZpv9kFs47tfMPpGnMAJZgsmeg==} + '@agent-native/core@0.214.0': + resolution: {integrity: sha512-wNUGwiwU+o+bfky9wxqzAh/DRsl7jng3TQiXpyiC9i9yorkg8h+bbfnmbuhKo1loKPndt4jd7Iiel4tC3g1NnQ==} engines: {node: '>=22.22.0'} hasBin: true peerDependencies: @@ -8782,18 +8641,8 @@ packages: xlsx: optional: true - '@agent-native/recap-cli@0.5.77': - resolution: {integrity: sha512-z/+p4ER7gin0mHoBSRcwwVtilEMQGpHkL2PZsiokvyTkKJRySRHqh9oo49vNVAzqq9aMOpmX+zsfIfAMY0JGFA==} - engines: {node: '>=22.22.0'} - hasBin: true - peerDependencies: - playwright: ^1.63.0 - peerDependenciesMeta: - playwright: - optional: true - - '@agent-native/recap-cli@0.5.79': - resolution: {integrity: sha512-ha2uD5NJhMOMIkAmKE2+k4BV7nGZdHzaKpEjb/yvNCqlqMhPnIq5drsSbE7b7FZFocXAEZkXLT5SV5YRSFBptA==} + '@agent-native/recap-cli@0.5.83': + resolution: {integrity: sha512-DA38Bqu7gmJZNcfe17po0s+zd3xG09lJnsaFZ+9m7W0Uyzjbp17VjBTvY00riau0B6MswAbXDXNfMrgH5k/nVw==} engines: {node: '>=22.22.0'} hasBin: true peerDependencies: @@ -25265,29 +25114,18 @@ snapshots: dependencies: zod: 3.25.76 - '@agent-native/agentkit@0.210.2': + '@agent-native/agentkit@0.214.0': dependencies: '@ag-ui/core': 0.0.59 '@noble/hashes': 2.4.0 - '@agent-native/agentkit@0.212.0': - dependencies: - '@ag-ui/core': 0.0.59 - '@noble/hashes': 2.4.0 - - '@agent-native/core-corpus@0.210.2': - optional: true - - '@agent-native/core-corpus@0.212.0': + '@agent-native/core-corpus@0.214.0': optional: true - '@agent-native/core-corpus@0.212.1': - optional: true - - '@agent-native/core@0.210.2(795ac86893f01d29064052ac20cbfb5b)': + '@agent-native/core@0.214.0(de0204abe5993dfa30bb15f10a035ff4)': dependencies: - '@agent-native/agentkit': 0.210.2 - '@agent-native/recap-cli': 0.5.77(playwright@1.63.0) + '@agent-native/agentkit': 0.214.0 + '@agent-native/recap-cli': 0.5.83(playwright@1.63.0) '@anthropic-ai/sdk': 0.90.0(zod@4.6.7) '@babel/parser': 8.0.7 '@better-auth/core': 1.7.6(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.6.7))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) @@ -25311,7 +25149,7 @@ snapshots: dotenv: 17.4.2 drizzle-orm: 0.45.3(@electric-sql/pglite@0.5.9)(@libsql/client@0.15.15)(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9) entities: 8.1.0 - esbuild: 0.28.1 + esbuild: 0.28.2 fast-xml-parser: 5.11.1 h3: 2.0.1(crossws@0.4.15(srvx@1.0.5))(ocache@0.3.0) i18next: 26.3.1(typescript@7.0.2) @@ -25339,7 +25177,7 @@ snapshots: yjs: 13.6.33 zod: 4.6.7 optionalDependencies: - '@agent-native/core-corpus': 0.212.1 + '@agent-native/core-corpus': 0.214.0 '@ai-sdk/anthropic': 3.0.86(zod@4.6.7) '@ai-sdk/cohere': 3.0.39(zod@4.6.7) '@ai-sdk/google': 3.0.83(zod@4.6.7) @@ -25432,159 +25270,7 @@ snapshots: - vitest - vue - '@agent-native/core@0.212.0(425ad63e441766c7cc702a9e78bce500)': - dependencies: - '@agent-native/agentkit': 0.212.0 - '@agent-native/recap-cli': 0.5.79(playwright@1.63.0) - '@anthropic-ai/sdk': 0.90.0(zod@4.6.7) - '@babel/parser': 8.0.7 - '@better-auth/core': 1.7.6(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.6.7))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) - '@clack/prompts': 1.7.0 - '@mcp-b/webmcp-polyfill': 5.1.0 - '@modelcontextprotocol/client': 2.0.0 - '@modelcontextprotocol/core': 2.2.0 - '@modelcontextprotocol/ext-apps': 2.0.3(@modelcontextprotocol/client@2.0.0)(@modelcontextprotocol/core@2.2.0)(@modelcontextprotocol/server@2.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.6.7) - '@modelcontextprotocol/server': 2.0.0 - '@mozilla/readability': 0.6.0 - '@neondatabase/serverless': 1.1.0 - '@noble/hashes': 2.4.0 - '@standard-schema/spec': 1.1.0 - '@tanstack/query-persist-client-core': 5.101.2 - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - better-auth: 1.7.6(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.3(@electric-sql/pglite@0.5.9)(@libsql/client@0.15.15)(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(solid-js@1.9.13)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(happy-dom@20.12.0)(jsdom@30.1.2(@noble/hashes@2.4.0)(canvas@3.2.3))(vite@8.1.5(@types/node@26.6.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.46.1)(tsx@4.23.15)(yaml@2.9.0))) - cron-parser: 5.6.0 - diff: 9.0.0 - diff-match-patch: 1.0.5 - dotenv: 17.4.2 - drizzle-orm: 0.45.3(@electric-sql/pglite@0.5.9)(@libsql/client@0.15.15)(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9) - entities: 8.1.0 - esbuild: 0.28.1 - fast-xml-parser: 5.11.1 - h3: 2.0.1(crossws@0.4.15(srvx@1.0.5))(ocache@0.3.0) - i18next: 26.3.1(typescript@7.0.2) - idb-keyval: 6.3.0 - ink: 6.8.0(@types/react@19.2.17)(react-devtools-core@6.1.5)(react@19.2.7) - isbot: 5.1.44 - jiti: 2.7.0 - jose: 6.2.3 - linkedom: 0.18.13(canvas@3.2.3) - magic-string: 1.4.3 - minimatch: 10.2.5 - nitro: 3.0.260903-beta - parse5: 8.0.1 - postgres: 3.4.9 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-i18next: 17.0.8(i18next@26.3.1(typescript@7.0.2))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2) - run: 2.0.1 - safe-regex2: 5.1.1 - turndown: 7.2.4 - undici: 7.28.0 - ws: 8.22.0 - y-protocols: 1.0.7(yjs@13.6.33) - yaml: 2.9.0 - yjs: 13.6.33 - zod: 4.6.7 - optionalDependencies: - '@agent-native/core-corpus': 0.212.0 - '@ai-sdk/anthropic': 3.0.86(zod@4.6.7) - '@ai-sdk/cohere': 3.0.39(zod@4.6.7) - '@ai-sdk/google': 3.0.83(zod@4.6.7) - '@ai-sdk/groq': 3.0.42(zod@4.6.7) - '@ai-sdk/mistral': 3.0.40(zod@4.6.7) - '@ai-sdk/openai': 4.0.70(zod@4.6.7) - '@amplitude/analytics-browser': 2.45.8 - '@anthropic-ai/tokenizer': 0.0.4 - '@better-auth/scim': 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.6.7))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(better-auth@1.7.6(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.3(@electric-sql/pglite@0.5.9)(@libsql/client@0.15.15)(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(solid-js@1.9.13)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(happy-dom@20.12.0)(jsdom@30.1.2(@noble/hashes@2.4.0)(canvas@3.2.3))(vite@8.1.5(@types/node@26.6.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.46.1)(tsx@4.23.15)(yaml@2.9.0))))(better-call@1.4.0(zod@4.6.7)) - '@better-auth/sso': 1.7.6(@better-auth/core@1.7.6(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.6.7))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.2)(better-auth@1.7.6(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.3(@electric-sql/pglite@0.5.9)(@libsql/client@0.15.15)(@neondatabase/serverless@1.1.0)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9))(pg@8.22.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(solid-js@1.9.13)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(happy-dom@20.12.0)(jsdom@30.1.2(@noble/hashes@2.4.0)(canvas@3.2.3))(vite@8.1.5(@types/node@26.6.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.46.1)(tsx@4.23.15)(yaml@2.9.0))))(better-call@1.4.0(zod@4.6.7)) - '@builder.io/ai-utils': 0.84.0 - '@electric-sql/pglite': 0.5.9 - '@launchdarkly/node-server-sdk': 9.13.6 - '@openrouter/ai-sdk-provider': 3.1.0(ai@7.0.106(zod@4.6.7))(zod@4.6.7) - '@opentelemetry/api': 1.9.1 - '@resvg/resvg-js': 2.6.2 - '@rrweb/record': 2.1.0 - '@sentry/browser': 11.1.0 - '@sentry/node': 11.1.0(@babel/core@7.29.7)(rollup@4.62.2) - '@sentry/vite-plugin': 5.4.0(rollup@4.62.2) - '@tailwindcss/typography': 0.5.20(tailwindcss@4.3.1) - '@tailwindcss/vite': 4.3.1(vite@8.1.5(@types/node@26.6.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.46.1)(tsx@4.23.15)(yaml@2.9.0)) - '@tanstack/react-query': 5.101.2(react@19.2.7) - '@typesafe-ai/sdk': 0.6.0 - '@vitejs/plugin-react-swc': 4.3.1(vite@8.1.5(@types/node@26.6.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.46.1)(tsx@4.23.15)(yaml@2.9.0)) - ai: 7.0.106(zod@4.6.7) - ai-sdk-ollama: 3.8.8(ai@7.0.106(zod@4.6.7))(zod@4.6.7) - botframework-connector: 4.23.3 - drizzle-kit: 0.31.10 - jszip: 3.10.1 - mammoth: 1.12.0 - node-pty: 1.1.0 - officeparser: 7.3.0 - prettier: 3.9.6 - react-router: 8.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - sharp: 0.35.3(@types/node@26.6.5) - tailwindcss: 4.3.1 - ts-morph: 28.0.0 - vite: 8.1.5(@types/node@26.6.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.46.1)(tsx@4.23.15)(yaml@2.9.0) - xlsx: 0.18.5 - transitivePeerDependencies: - - '@aws-sdk/client-rds-data' - - '@better-auth/utils' - - '@better-fetch/fetch' - - '@cloudflare/workers-types' - - '@libsql/client' - - '@libsql/client-wasm' - - '@lynx-js/react' - - '@netlify/db' - - '@op-engineering/op-sqlite' - - '@planetscale/database' - - '@prisma/client' - - '@sveltejs/kit' - - '@tanstack/react-start' - - '@tanstack/solid-start' - - '@tidbcloud/serverless' - - '@types/better-sqlite3' - - '@types/pg' - - '@types/react' - - '@types/sql.js' - - '@upstash/redis' - - '@vercel/postgres' - - '@xata.io/client' - - better-call - - better-sqlite3 - - bufferutil - - bun-types - - canvas - - crossws - - expo-sqlite - - gel - - knex - - kysely - - mongodb - - mysql2 - - nanostores - - next - - ocache - - pg - - playwright - - prisma - - react-devtools-core - - react-native - - solid-js - - sql.js - - sqlite3 - - svelte - - typescript - - utf-8-validate - - vitest - - vue - - '@agent-native/recap-cli@0.5.77(playwright@1.63.0)': - optionalDependencies: - playwright: 1.63.0 - - '@agent-native/recap-cli@0.5.79(playwright@1.63.0)': + '@agent-native/recap-cli@0.5.83(playwright@1.63.0)': optionalDependencies: playwright: 1.63.0 @@ -43524,7 +43210,7 @@ snapshots: tsx@4.23.15: dependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 diff --git a/scripts/changeset-version-stable.ts b/scripts/changeset-version-stable.ts new file mode 100644 index 00000000000..3667928bd11 --- /dev/null +++ b/scripts/changeset-version-stable.ts @@ -0,0 +1,74 @@ +import { spawnSync } from "node:child_process"; +import { readFile, writeFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const rootDir = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "..", +); + +export const CHANGESET_CONFIG_PATH = path.join( + rootDir, + ".changeset", + "config.json", +); + +/** + * Stable releases only rewrite `workspace:` ranges. A plain npm range on an + * internal package is deliberate: the community QR template declares + * `@agent-native/core` that way because its files are copied into other + * workspaces, and Dispatch pins those files by Git blob id. Snapshot releases + * keep the shared config, so their peer ranges still move to each snapshot. + */ +export function stableChangesetConfig(source: string): string { + const config = JSON.parse(source) as Record; + return `${JSON.stringify( + { ...config, bumpVersionsWithWorkspaceProtocolOnly: true }, + null, + 2, + )}\n`; +} + +export async function withStableChangesetConfig( + run: () => T | Promise, + configPath = CHANGESET_CONFIG_PATH, +): Promise { + const original = await readFile(configPath, "utf8"); + await writeFile(configPath, stableChangesetConfig(original)); + try { + return await run(); + } finally { + await writeFile(configPath, original); + } +} + +function runChangesetVersion(): void { + const changesetsBin = createRequire(import.meta.url).resolve( + "@changesets/cli/bin.js", + ); + const result = spawnSync(process.execPath, [changesetsBin, "version"], { + cwd: rootDir, + stdio: "inherit", + }); + if (result.error) throw result.error; + if (result.status !== 0) { + throw new Error(`changeset version exited with ${result.status}.`); + } +} + +function isDirectExecution(): boolean { + const entrypoint = process.argv[1]; + return Boolean( + entrypoint && + import.meta.url === pathToFileURL(path.resolve(entrypoint)).href, + ); +} + +if (isDirectExecution()) { + withStableChangesetConfig(runChangesetVersion).catch((error) => { + console.error(error); + process.exit(1); + }); +} diff --git a/scripts/package-release-workflow.test.ts b/scripts/package-release-workflow.test.ts index 15cb71c8d51..d5674e4dd24 100644 --- a/scripts/package-release-workflow.test.ts +++ b/scripts/package-release-workflow.test.ts @@ -14,12 +14,11 @@ import { isAlreadyStaged, preparePackagesForTagging, } from "./changeset-publish-sequential.ts"; -import { packagesCoveredBy } from "./check-changeset.mjs"; import { - gitBlobSha, - syncCommunityQrBlobPins, - syncCommunityQrCoreLock, -} from "./sync-community-qr-core-lock.ts"; + stableChangesetConfig, + withStableChangesetConfig, +} from "./changeset-version-stable.ts"; +import { packagesCoveredBy } from "./check-changeset.mjs"; type Workflow = Record; @@ -393,7 +392,7 @@ describe("npm package release workflow", () => { .scripts as Record; assert.equal( packageScripts["changeset:release-version"], - "pnpm changeset:version && pnpm exec tsx scripts/sync-community-qr-core-lock.ts && pnpm changelog:compact", + "pnpm exec tsx scripts/changeset-version-stable.ts && pnpm changelog:compact && pnpm install --frozen-lockfile --lockfile-only --ignore-scripts", ); const consume = releaseSteps.find( @@ -436,102 +435,85 @@ describe("npm package release workflow", () => { assert(changesetsIndex < restoreIndex); }); - it("syncs the community QR release importer to the in-repo Core package", () => { - const lockfile = `lockfileVersion: '9.0' -importers: - templates/community/qr: - dependencies: - '@agent-native/core': - specifier: ^0.212.0 - version: 0.212.0(425ad63e441766c7cc702a9e78bce500) - postgres: - specifier: ^3.4.9 - version: 3.4.9 - - templates/analytics: - dependencies: {} -`; - - const updated = syncCommunityQrCoreLock(lockfile, "^0.213.0"); - - assert.match( - updated, - /specifier: \^0\.213\.0\n version: link:\.\.\/\.\.\/\.\.\/packages\/core/, - ); - assert.match(updated, /specifier: \^3\.4\.9\n version: 3\.4\.9/); - assert.match(updated, / templates\/analytics:\n dependencies: \{\}/); - assert.equal(syncCommunityQrCoreLock(updated, "^0.213.0"), updated); - assert.throws( - () => syncCommunityQrCoreLock("importers: {}\n", "^0.213.0"), - /Missing templates\/community\/qr importer/, + it("keeps plain internal ranges like the QR template's Core out of stable releases", async () => { + const { getPackages } = changesetsRequire("@manypkg/get-packages") as { + getPackages: (cwd: string) => Promise; + }; + const { parse: parseChangesetConfig } = changesetsRequire( + "@changesets/config", + ) as { parse: (json: unknown, packages: unknown) => unknown }; + const assembleReleasePlan = ( + changesetsRequire("@changesets/assemble-release-plan") as { + default: ( + changesets: unknown[], + packages: unknown, + config: unknown, + preState: undefined, + ) => { releases: Array<{ name: string; type: string }> }; + } + ).default; + + const packages = await getPackages(process.cwd()); + const sharedConfig = readFileSync(".changeset/config.json", "utf8"); + const corePatch = [ + { + id: "core-patch", + summary: "Core patch", + releases: [{ name: "@agent-native/core", type: "patch" }], + }, + ]; + const releasesWith = (configSource: string) => + assembleReleasePlan( + corePatch, + packages, + parseChangesetConfig(JSON.parse(configSource), packages), + undefined, + ).releases; + + const stable = releasesWith(stableChangesetConfig(sharedConfig)).map( + (release) => release.name, + ); + const fixedGroup = (JSON.parse(sharedConfig) as { fixed: string[][] }) + .fixed[0]; + for (const name of fixedGroup) assert(stable.includes(name), name); + assert(!stable.includes("qr")); + + // Without the stable override, Changesets rewrites QR's Core range in a + // `none` release, which breaks the lockfile and Dispatch's QR blob pins. + assert.equal( + releasesWith(sharedConfig).find((release) => release.name === "qr")?.type, + "none", ); }); - it("keeps the committed community QR importer synced with its manifest", () => { - const templatePackage = JSON.parse( - readFileSync("templates/community/qr/package.json", "utf8"), - ) as { dependencies?: Record }; - const coreSpecifier = templatePackage.dependencies?.["@agent-native/core"]; - assert(typeof coreSpecifier === "string"); - - const lockfile = readFileSync("pnpm-lock.yaml", "utf8"); - assert.equal(syncCommunityQrCoreLock(lockfile, coreSpecifier), lockfile); + it("leaves the shared Changesets config moving snapshot peer ranges", () => { + // Nightly and dev snapshots rewrite peers such as Dispatch's + // `>=0.209.1` Core range to the exact snapshot, which the range rejects. + const config = JSON.parse( + readFileSync(".changeset/config.json", "utf8"), + ) as Record; + assert.notEqual(config.bumpVersionsWithWorkspaceProtocolOnly, true); }); - it("rewrites the QR template blob pins from the files on disk", () => { - const source = `templatePath: "templates/other-first", - files: [ - { - path: "package.json", - blobSha: "${"d".repeat(40)}", - }, - ], - }, - { - templatePath: "templates/community/qr", - files: [ - { - path: "package.json", - blobSha: "${"a".repeat(40)}", - }, - { - path: "README.md", - blobSha: "${"b".repeat(40)}", - }, - ], - }, - { - templatePath: "templates/other", - files: [ - { - path: "package.json", - blobSha: "${"c".repeat(40)}", - }, - ], - },`; - - const updated = syncCommunityQrBlobPins(source, (file) => - file === "package.json" ? "1".repeat(40) : "2".repeat(40), - ); + it("restores the shared Changesets config when a stable version run fails", async () => { + const dir = mkdtempSync(path.join(tmpdir(), "changeset-config-")); + const configPath = path.join(dir, "config.json"); + const original = '{\n "baseBranch": "main"\n}\n'; + writeFileSync(configPath, original); - assert.match(updated, /package\.json",\n\s+blobSha: "1{40}"/); - assert.match(updated, /README\.md",\n\s+blobSha: "2{40}"/); - assert.match(updated, /blobSha: "c{40}"/); - assert.match(updated, /blobSha: "d{40}"/); - assert.equal( - syncCommunityQrBlobPins(updated, (file) => - file === "package.json" ? "1".repeat(40) : "2".repeat(40), - ), - updated, - ); - assert.throws( - () => syncCommunityQrBlobPins("export {};\n", () => "x"), - /Missing templates\/community\/qr source/, - ); - assert.equal( - gitBlobSha(Buffer.from("hello\n")), - "ce013625030ba8dba906f756967f9e9ca394464a", - ); + try { + await assert.rejects( + withStableChangesetConfig(() => { + const during = JSON.parse(readFileSync(configPath, "utf8")); + assert.equal(during.bumpVersionsWithWorkspaceProtocolOnly, true); + throw new Error("version failed"); + }, configPath), + /version failed/, + ); + assert.equal(readFileSync(configPath, "utf8"), original); + } finally { + rmSync(dir, { recursive: true, force: true }); + } }); it("waits until local runtime packages are fetchable before publishing dependents", async () => { diff --git a/scripts/sync-community-qr-core-lock.ts b/scripts/sync-community-qr-core-lock.ts deleted file mode 100644 index 0ff60288a3e..00000000000 --- a/scripts/sync-community-qr-core-lock.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { createHash } from "node:crypto"; -import { readFileSync } from "node:fs"; -import { readFile, writeFile } from "node:fs/promises"; -import path from "node:path"; -import { pathToFileURL } from "node:url"; - -const templatePackagePath = "templates/community/qr/package.json"; -const lockfilePath = "pnpm-lock.yaml"; -const importerPath = "templates/community/qr"; -const workspaceCorePath = "../../../packages/core"; -const communityAppsPath = "packages/dispatch/src/server/lib/community-apps.ts"; - -export function syncCommunityQrCoreLock( - lockfile: string, - coreSpecifier: string, -): string { - const importerStart = lockfile.indexOf(` ${importerPath}:\n`); - if (importerStart < 0) { - throw new Error(`Missing ${importerPath} importer in pnpm-lock.yaml.`); - } - - const importerBodyStart = importerStart + importerPath.length + 4; - const nextImporter = /\n \S/m.exec(lockfile.slice(importerBodyStart)); - const importerEnd = nextImporter - ? importerBodyStart + nextImporter.index + 1 - : lockfile.length; - const importer = lockfile.slice(importerStart, importerEnd); - const coreDependency = - /^ '@agent-native\/core':\n specifier: [^\n]+\n version: [^\n]+/m; - - if (!coreDependency.test(importer)) { - throw new Error( - `Missing @agent-native/core dependency in ${importerPath} importer.`, - ); - } - - const updatedImporter = importer.replace( - coreDependency, - ` '@agent-native/core':\n specifier: ${coreSpecifier}\n version: link:${workspaceCorePath}`, - ); - - return `${lockfile.slice(0, importerStart)}${updatedImporter}${lockfile.slice(importerEnd)}`; -} - -export function gitBlobSha(content: Buffer): string { - return createHash("sha1") - .update(`blob ${content.length}\0`) - .update(content) - .digest("hex"); -} - -// Dispatch pins each QR template file by Git blob id, so a manifest change -// invalidates the pin until it is rewritten here. -export function syncCommunityQrBlobPins( - source: string, - blobShaFor: (templateFile: string) => string, -): string { - const sourceStart = source.indexOf(`templatePath: "${importerPath}"`); - if (sourceStart < 0) { - throw new Error(`Missing ${importerPath} source in ${communityAppsPath}.`); - } - const closing = source.indexOf("\n },", sourceStart); - const sourceEnd = closing < 0 ? source.length : closing; - const pin = /(path: "([^"]+)",\n\s+blobSha: ")[a-f0-9]{40}(")/g; - let pins = 0; - const updatedBlock = source - .slice(sourceStart, sourceEnd) - .replace(pin, (_match, before: string, file: string, after: string) => { - pins += 1; - return `${before}${blobShaFor(file)}${after}`; - }); - if (pins === 0) { - throw new Error(`No blob pins found for ${importerPath}.`); - } - const updated = `${source.slice(0, sourceStart)}${updatedBlock}${source.slice(sourceEnd)}`; - return updated; -} - -async function main(): Promise { - const [packageSource, lockfile] = await Promise.all([ - readFile(templatePackagePath, "utf8"), - readFile(lockfilePath, "utf8"), - ]); - const templatePackage = JSON.parse(packageSource) as { - dependencies?: Record; - }; - const coreSpecifier = templatePackage.dependencies?.["@agent-native/core"]; - if (typeof coreSpecifier !== "string") { - throw new Error( - `Missing @agent-native/core dependency in ${templatePackagePath}.`, - ); - } - - const updated = syncCommunityQrCoreLock(lockfile, coreSpecifier); - if (updated !== lockfile) await writeFile(lockfilePath, updated); - - const communityApps = await readFile(communityAppsPath, "utf8"); - const repinned = syncCommunityQrBlobPins(communityApps, (file) => - gitBlobSha(readFileSync(path.join(importerPath, file))), - ); - if (repinned !== communityApps) await writeFile(communityAppsPath, repinned); -} - -if ( - process.argv[1] && - import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href -) { - main().catch((error: unknown) => { - console.error(error); - process.exitCode = 1; - }); -} From a4084145ea1e1a27ea79bcf558869883e438d801 Mon Sep 17 00:00:00 2001 From: 3mdistal <86723305+3mdistal@users.noreply.github.com> Date: Wed, 21 Oct 2026 20:53:12 -0400 Subject: [PATCH 2/3] Describe the QR pin changeset as the repin it was Co-Authored-By: Claude Opus 5.5 --- .changeset/qr-pin-release-sync.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/qr-pin-release-sync.md b/.changeset/qr-pin-release-sync.md index 554c8de2bdf..a033141f6f3 100644 --- a/.changeset/qr-pin-release-sync.md +++ b/.changeset/qr-pin-release-sync.md @@ -2,4 +2,4 @@ "@agent-native/dispatch": patch --- -Refresh the QR community app file pins during release versioning so they match the current template. +Repin the QR community app's package.json so its file pins match the current template. From 2549b658df907da6bb455206f28f8bc126df683e Mon Sep 17 00:00:00 2001 From: 3mdistal <86723305+3mdistal@users.noreply.github.com> Date: Wed, 21 Oct 2026 21:22:51 -0400 Subject: [PATCH 3/3] Route changeset:version through the stable wrapper Running the alias by hand still rewrote the QR template's Core range, so it now runs the stable wrapper and release prep reuses it. Snapshot jobs call `pnpm changeset version --snapshot` directly and keep the shared config. Regenerate the lockfile by re-resolving only QR's Core entry, so tsx keeps its existing esbuild resolution. Co-Authored-By: Claude Opus 5.5 --- package.json | 4 ++-- pnpm-lock.yaml | 2 +- scripts/package-release-workflow.test.ts | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6c6ab8918de..a559e6467e1 100644 --- a/package.json +++ b/package.json @@ -202,8 +202,8 @@ "changeset": "changeset", "changeset:add": "changeset add", "changeset:status": "changeset status --since=origin/main", - "changeset:version": "changeset version", - "changeset:release-version": "pnpm exec tsx scripts/changeset-version-stable.ts && pnpm changelog:compact && pnpm install --frozen-lockfile --lockfile-only --ignore-scripts", + "changeset:version": "pnpm exec tsx scripts/changeset-version-stable.ts", + "changeset:release-version": "pnpm changeset:version && pnpm changelog:compact && pnpm install --frozen-lockfile --lockfile-only --ignore-scripts", "changelog:compact": "tsx scripts/compact-changelogs.ts --write", "guard:changelog": "tsx scripts/compact-changelogs.ts --check", "changeset:publish": "pnpm -r build && changeset publish", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e68627e038..308d4126007 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43210,7 +43210,7 @@ snapshots: tsx@4.23.15: dependencies: - esbuild: 0.28.2 + esbuild: 0.28.1 optionalDependencies: fsevents: 2.3.3 diff --git a/scripts/package-release-workflow.test.ts b/scripts/package-release-workflow.test.ts index d5674e4dd24..19368596af5 100644 --- a/scripts/package-release-workflow.test.ts +++ b/scripts/package-release-workflow.test.ts @@ -392,7 +392,11 @@ describe("npm package release workflow", () => { .scripts as Record; assert.equal( packageScripts["changeset:release-version"], - "pnpm exec tsx scripts/changeset-version-stable.ts && pnpm changelog:compact && pnpm install --frozen-lockfile --lockfile-only --ignore-scripts", + "pnpm changeset:version && pnpm changelog:compact && pnpm install --frozen-lockfile --lockfile-only --ignore-scripts", + ); + assert.equal( + packageScripts["changeset:version"], + "pnpm exec tsx scripts/changeset-version-stable.ts", ); const consume = releaseSteps.find(