diff --git a/package.json b/package.json index 47c984aa7d9..4025c078336 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 changeset:version && pnpm exec tsx scripts/sync-community-qr-core-lock.ts && pnpm changelog:compact", + "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 62d4ce68bbc..9e82068e9c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5668,7 +5668,7 @@ importers: dependencies: '@agent-native/core': specifier: ^0.219.0 - version: link:../../../packages/core + version: 0.219.0(10122618e0728099358b990c00e85614) postgres: specifier: ^3.4.9 version: 3.4.9 @@ -8500,23 +8500,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/agentkit@0.219.0': + resolution: {integrity: sha512-aMCbnImC617tDfQrEyUuSy5gbdaBILacVgnHBIVYF5jikIWqCIUyYHvQMvhNCsMYlrcjkWBL9Y7ETs3ub5Cm5w==} - '@agent-native/core-corpus@0.212.0': - resolution: {integrity: sha512-mqN7+pMgGmMgfhhnkOzLA7cdRUoV33crvVukxrrsPIP4wLZxxhcuoWOf+0+IDaYbxaZSkOQ4Z0xlDxxwfSgJOw==} + '@agent-native/core-corpus@0.219.0': + resolution: {integrity: sha512-eK6t3twgWaCpNPnHZvNn7pGfOL+LZ1vd9H6MbApAJEgwnfbMWOGxxPKqvhQaANDgNPz97vm/Ko6F1piYN37CPA==} - '@agent-native/core-corpus@0.212.1': - resolution: {integrity: sha512-hdgtwyt8/07l2QxeF5PmTTcnfjzdhkOj1Y323BPWdzf36lxIn4LABrSUFkJbPshDGfc1RRN2kN1nS0z2Ku+3HA==} - - '@agent-native/core@0.210.2': - resolution: {integrity: sha512-bMhyOLLzT8NFcHN79zAYCkjRBHW2S/Zh+/l9dFCmbmE/UqLSXnCmDCsLa0L+7eMxbVAT+/HeTYCIVlv7yrfQlw==} + '@agent-native/core@0.219.0': + resolution: {integrity: sha512-twQ73+RmBujEvC4O1ezowkADUK83jfMO71E7KMqBbNENWbItZfBmS1czOEtT1MVEgyZOFRtrQ0lyCQBgpSFWTA==} engines: {node: '>=22.22.0'} hasBin: true peerDependencies: @@ -8647,150 +8638,8 @@ packages: xlsx: optional: true - '@agent-native/core@0.212.0': - resolution: {integrity: sha512-hQoZPCdoRehI84ZUxyNX8TVDaqtiIooQhLw2bPBHd1SN1OPl+hPjDnTDYHrBRZpv9kFs47tfMPpGnMAJZgsmeg==} - 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/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.88': + resolution: {integrity: sha512-ANLNXQdz8rECuoVrLnXIhaFNM48WnQMUdH1uZAUj+S6/8drcAvSepHje+HKDOgt2ffzPleQ6CfwD+a2eowyxVg==} engines: {node: '>=22.22.0'} hasBin: true peerDependencies: @@ -25250,29 +25099,18 @@ snapshots: dependencies: zod: 3.25.76 - '@agent-native/agentkit@0.210.2': + '@agent-native/agentkit@0.219.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': + '@agent-native/core-corpus@0.219.0': optional: true - '@agent-native/core-corpus@0.212.0': - optional: true - - '@agent-native/core-corpus@0.212.1': - optional: true - - '@agent-native/core@0.210.2(795ac86893f01d29064052ac20cbfb5b)': + '@agent-native/core@0.219.0(10122618e0728099358b990c00e85614)': dependencies: - '@agent-native/agentkit': 0.210.2 - '@agent-native/recap-cli': 0.5.77(playwright@1.63.0) + '@agent-native/agentkit': 0.219.0 + '@agent-native/recap-cli': 0.5.88(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) @@ -25296,7 +25134,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) @@ -25324,7 +25162,7 @@ snapshots: yjs: 13.6.33 zod: 4.6.7 optionalDependencies: - '@agent-native/core-corpus': 0.212.1 + '@agent-native/core-corpus': 0.219.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) @@ -25417,159 +25255,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.88(playwright@1.63.0)': optionalDependencies: playwright: 1.63.0 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 2fb8b06aacb..d39babc484f 100644 --- a/scripts/package-release-workflow.test.ts +++ b/scripts/package-release-workflow.test.ts @@ -22,6 +22,10 @@ import { isAlreadyStaged, preparePackagesForTagging, } from "./changeset-publish-sequential.ts"; +import { + stableChangesetConfig, + withStableChangesetConfig, +} from "./changeset-version-stable.ts"; import { packagesCoveredBy } from "./check-changeset.mjs"; import { parseReleasePullRequestPackageFiles } from "./list-release-pr-package-files.ts"; import { @@ -42,15 +46,11 @@ import { requiredContextsFromRules, waitForRequiredChecks, } from "./release-required-checks.ts"; -import { - gitBlobSha, - syncCommunityQrBlobPins, - syncCommunityQrCoreLock, -} from "./sync-community-qr-core-lock.ts"; import { assertCompletePullRequestFiles, assertCommunityQrFilePinsSynced, decodeGitHubContentResponse, + gitBlobSha, parseCompletePullRequestFiles, } from "./verify-community-qr-release-pin.ts"; import { @@ -1233,7 +1233,11 @@ esac .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 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( @@ -1276,102 +1280,85 @@ esac 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"); + 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; - 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/, + 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("accepts all synchronized community QR template pins and rejects other catalog changes", () => { 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; - }); -} diff --git a/scripts/verify-community-qr-release-pin.ts b/scripts/verify-community-qr-release-pin.ts index 47e7b802ada..8ad2f661737 100644 --- a/scripts/verify-community-qr-release-pin.ts +++ b/scripts/verify-community-qr-release-pin.ts @@ -1,6 +1,5 @@ import { execFileSync } from "node:child_process"; - -import { gitBlobSha } from "./sync-community-qr-core-lock.ts"; +import { createHash } from "node:crypto"; const repo = process.env.REPO; const baseSha = process.env.BASE_SHA; @@ -11,6 +10,13 @@ const communityQrPackagePath = "templates/community/qr/package.json"; const communityQrTemplatePath = "templates/community/qr"; const pullRequestFilesLimit = 3_000; +export function gitBlobSha(content: Buffer): string { + return createHash("sha1") + .update(`blob ${content.length}\0`) + .update(content) + .digest("hex"); +} + export function assertCompletePullRequestFiles( files: string[], changedFilesCount: unknown,