From 5c4d55372ea9680bf66a271e200a37f49f493ba0 Mon Sep 17 00:00:00 2001 From: yonkoo11 Date: Sun, 26 Jul 2026 08:30:06 +0100 Subject: [PATCH 1/3] fix(export): use managed stable version when rc dist-tag is stale (#407) --- apps/builder/src/export/PackageManager.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/builder/src/export/PackageManager.ts b/apps/builder/src/export/PackageManager.ts index 4fb65513..6d310dc0 100644 --- a/apps/builder/src/export/PackageManager.ts +++ b/apps/builder/src/export/PackageManager.ts @@ -527,13 +527,15 @@ export class PackageManager { updatedDependencies[pkgName] = 'workspace:*'; } else if (env === 'staging') { // Staging: Use RC versions for testing latest features - // If managedVersion is already an RC version (e.g., 0.0.0-rc-20250915124300), use it - // Otherwise, fall back to using the 'rc' dist-tag so consumers pull the latest RC snapshot + // If managedVersion is an RC version (e.g., 0.0.0-rc-20250915124300), use it directly. + // Otherwise, use the managed stable version — the 'rc' dist-tag can be stale and + // resolve to a build that predates features shipped in stable releases. if (managedVersion.includes('-rc')) { updatedDependencies[pkgName] = managedVersion; } else { - // Using the dist-tag ensures alignment with snapshot format 0.0.0-rc-YYYYMMDDHHMMSS - updatedDependencies[pkgName] = 'rc'; + updatedDependencies[pkgName] = managedVersion.startsWith('^') + ? managedVersion + : `^${managedVersion}`; } } else { // Production: Use stable published versions From d79978c868947691b8019805df0af0f9c1ab8f84 Mon Sep 17 00:00:00 2001 From: yonkoo11 Date: Mon, 17 Aug 2026 19:45:20 +0100 Subject: [PATCH 2/3] test(export): align staging version expectations --- apps/builder/src/export/PackageManager.ts | 2 +- .../EnsExportPins.verification.test.ts | 6 ++-- .../export/__tests__/PackageManager.test.ts | 28 ++++++++----------- .../PackageManagerConfigLoading.test.ts | 11 ++++---- .../__tests__/VersioningSafetyGuard.test.ts | 12 ++++---- .../ExportSnapshotTests.test.ts.snap | 6 ++-- apps/builder/src/export/versions.ts | 4 +-- 7 files changed, 31 insertions(+), 38 deletions(-) diff --git a/apps/builder/src/export/PackageManager.ts b/apps/builder/src/export/PackageManager.ts index 6d310dc0..5868d069 100644 --- a/apps/builder/src/export/PackageManager.ts +++ b/apps/builder/src/export/PackageManager.ts @@ -458,7 +458,7 @@ export class PackageManager { * * For @openzeppelin/adapter-* packages (published from openzeppelin-adapters): * - 'local': Uses workspace:* for monorepo development - * - 'staging': Uses RC versions or the npm `rc` dist-tag when versions.ts has stable semver + * - 'staging': Preserves managed RC versions; otherwise uses the managed stable caret range * - 'production': Uses stable published versions (^x.y.z) from versions.ts * * @param dependencies Original dependencies object diff --git a/apps/builder/src/export/__tests__/EnsExportPins.verification.test.ts b/apps/builder/src/export/__tests__/EnsExportPins.verification.test.ts index 38400d31..07a1caed 100644 --- a/apps/builder/src/export/__tests__/EnsExportPins.verification.test.ts +++ b/apps/builder/src/export/__tests__/EnsExportPins.verification.test.ts @@ -9,7 +9,7 @@ import { createMinimalContractSchema, createMinimalFormConfig } from '../utils/t import { extractFilesFromZip } from '../utils/zipInspector'; describe('ENS export dependency pins (production)', () => { - it('pins ui-types ^3.3.0, viem ^2.35.0, adapter-evm ^2.3.0 and ships .npmrc + vite interop', async () => { + it('pins ui-types ^3.5.0, viem ^2.35.0, adapter-evm ^2.7.0 and ships .npmrc + vite interop', async () => { const networks = await getNetworksByEcosystem('evm'); const networkConfig = networks.find((n) => n.id === 'ethereum-mainnet') ?? networks[0]; expect(networkConfig).toBeDefined(); @@ -30,9 +30,9 @@ describe('ENS export dependency pins (production)', () => { const packageJson = JSON.parse(files['package.json']); const deps = packageJson.dependencies as Record; - expect(deps['@openzeppelin/ui-types']).toBe('^3.3.0'); + expect(deps['@openzeppelin/ui-types']).toBe('^3.5.0'); expect(deps['viem']).toBe('^2.35.0'); - expect(deps['@openzeppelin/adapter-evm']).toBe('^2.3.0'); + expect(deps['@openzeppelin/adapter-evm']).toBe('^2.7.0'); expect(deps['@openzeppelin/ui-components']).toBe('^3.8.1'); expect(deps['@openzeppelin/ui-renderer']).toBe('^3.4.0'); expect(deps['@openzeppelin/ui-react']).toBe('^3.3.0'); diff --git a/apps/builder/src/export/__tests__/PackageManager.test.ts b/apps/builder/src/export/__tests__/PackageManager.test.ts index 6fed5b93..3876890e 100644 --- a/apps/builder/src/export/__tests__/PackageManager.test.ts +++ b/apps/builder/src/export/__tests__/PackageManager.test.ts @@ -16,7 +16,7 @@ vi.mock('../versions', async () => { '@openzeppelin/adapter-midnight': '0.0.4', '@openzeppelin/adapter-polkadot': '0.2.0', '@openzeppelin/adapter-solana': '0.0.3', - '@openzeppelin/adapter-stellar': '0.0.3', + '@openzeppelin/adapter-stellar': '0.0.3-rc.123', '@openzeppelin/ui-react': '0.1.3', '@openzeppelin/ui-renderer': '0.1.4', '@openzeppelin/ui-types': '0.2.0', @@ -440,9 +440,8 @@ describe('PackageManager', () => { expect(result.dependencies['@openzeppelin/ui-renderer']).toMatch(/^\^/); expect(result.dependencies['@openzeppelin/ui-types']).toMatch(/^\^/); - // Adapter packages use RC versions for staging: accept 'rc' tag or timestamped RC - const rcVersionOrTag = /^(rc|\d+\.\d+\.\d+-rc(?:[-.]\d+)?)$/; - expect(result.dependencies['@openzeppelin/adapter-evm']).toMatch(rcVersionOrTag); + // Stable managed adapter versions use a caret range instead of the potentially stale rc tag. + expect(result.dependencies['@openzeppelin/adapter-evm']).toMatch(/^\^\d+\.\d+\.\d+$/); // Verify external deps don't get -rc treatment expect(result.dependencies['react']).not.toMatch(/-rc$/); @@ -473,8 +472,7 @@ describe('PackageManager', () => { { env: 'staging' } ); const result = JSON.parse(updated); - const rcVersionOrTag = /^(rc|\d+\.\d+\.\d+-rc(?:[-.]\d+)?)$/; - expect(result.dependencies['@openzeppelin/adapter-polkadot']).toMatch(rcVersionOrTag); + expect(result.dependencies['@openzeppelin/adapter-polkadot']).toMatch(/^\^\d+\.\d+\.\d+$/); expect(result.dependencies['@openzeppelin/adapter-polkadot']).not.toBe('workspace:*'); }); @@ -487,7 +485,7 @@ describe('PackageManager', () => { const updated = await packageManager.updatePackageJson( basePackageJson, formConfig, - 'evm', + 'stellar', 'testFunction', { env: 'staging' } ); @@ -497,9 +495,8 @@ describe('PackageManager', () => { expect(result.dependencies['@openzeppelin/ui-renderer']).toMatch(/^\^/); expect(result.dependencies['@openzeppelin/ui-types']).toMatch(/^\^/); - // Adapter packages resolve to RC format (either dist-tag 'rc' or timestamped RC) - const rcVersionOrTag = /^(rc|\d+\.\d+\.\d+-rc(?:[-.]\d+)?)$/; - expect(result.dependencies['@openzeppelin/adapter-evm']).toMatch(rcVersionOrTag); + // An actual managed RC snapshot is preserved exactly. + expect(result.dependencies['@openzeppelin/adapter-stellar']).toBe('0.0.3-rc.123'); }); it('should verify RC detection logic works correctly', async () => { @@ -520,9 +517,8 @@ describe('PackageManager', () => { // UI packages use stable versions in staging (no RC pipeline for openzeppelin-ui) expect(stagingResult.dependencies['@openzeppelin/ui-renderer']).toMatch(/^\^/); - // Adapter packages use RC versions in staging - const rcVersionOrTag = /^(rc|\d+\.\d+\.\d+-rc(?:[-.]\d+)?)$/; - expect(stagingResult.dependencies['@openzeppelin/adapter-evm']).toMatch(rcVersionOrTag); + // Stable managed adapter versions use a caret range in staging. + expect(stagingResult.dependencies['@openzeppelin/adapter-evm']).toMatch(/^\^\d+\.\d+\.\d+$/); // Test that production doesn't get -rc const prodUpdated = await packageManager.updatePackageJson( @@ -586,10 +582,8 @@ describe('PackageManager', () => { const stagingResult = JSON.parse(stagingUpdated); // UI packages use stable versions (no RC pipeline) expect(stagingResult.dependencies['@openzeppelin/ui-renderer']).toMatch(/^\^/); - // Adapter packages use RC versions - expect(stagingResult.dependencies['@openzeppelin/adapter-evm']).toMatch( - /^(rc|\d+\.\d+\.\d+-rc(?:[-.]\d+)?)$/ - ); + // Stable managed adapter versions use a caret range. + expect(stagingResult.dependencies['@openzeppelin/adapter-evm']).toMatch(/^\^\d+\.\d+\.\d+$/); // Test production environment const prodUpdated = await packageManager.updatePackageJson( diff --git a/apps/builder/src/export/__tests__/PackageManagerConfigLoading.test.ts b/apps/builder/src/export/__tests__/PackageManagerConfigLoading.test.ts index 393a64bc..e4d98b4e 100644 --- a/apps/builder/src/export/__tests__/PackageManagerConfigLoading.test.ts +++ b/apps/builder/src/export/__tests__/PackageManagerConfigLoading.test.ts @@ -107,8 +107,7 @@ const createMinimalFormConfig = (fieldTypes: string[] = ['text']): BuilderFormCo }); describe('PackageManager configuration loading', () => { - // Accept either the 'rc' dist-tag or timestamped RC variants like "0.0.0-rc-20250813180014" - const rcVersionOrTag = /^(rc|\d+\.\d+\.\d+-rc(?:[-.]\d+)?)$/; + const stableRangeOrRc = /^(\^\d+\.\d+\.\d+|\d+\.\d+\.\d+-rc(?:[-.]\d+)?)$/; /** * This test suite focuses on testing the PackageManager with configurations * passed directly to the constructor, bypassing the dynamic loading mechanism. @@ -184,8 +183,8 @@ describe('PackageManager configuration loading', () => { const result = JSON.parse(updated); // UI packages from openzeppelin-ui use stable versions (no RC pipeline) expect(result.dependencies['@openzeppelin/ui-types']).toMatch(/^\^/); - // Adapter packages use RC versions for staging - expect(result.dependencies['@openzeppelin/adapter-evm']).toMatch(rcVersionOrTag); + // Stable adapter metadata becomes a caret range; managed RC snapshots remain exact. + expect(result.dependencies['@openzeppelin/adapter-evm']).toMatch(stableRangeOrRc); // External dependencies should remain unchanged expect(result.dependencies['react']).toBe('^19.0.0'); @@ -238,9 +237,9 @@ describe('PackageManager configuration loading', () => { expect(stagingResult.dependencies['@openzeppelin/ui-types']).toMatch(/^\^/); expect(prodResult.dependencies['@openzeppelin/ui-types']).toMatch(/^\^/); - // Adapters (published from openzeppelin-adapters): workspace:* local, rc staging, ^stable prod + // Adapters: workspace:* local, stable range or managed RC staging, ^stable prod. expect(localResult.dependencies['@openzeppelin/adapter-evm']).toBe('workspace:*'); - expect(stagingResult.dependencies['@openzeppelin/adapter-evm']).toMatch(rcVersionOrTag); + expect(stagingResult.dependencies['@openzeppelin/adapter-evm']).toMatch(stableRangeOrRc); expect(prodResult.dependencies['@openzeppelin/adapter-evm']).toMatch(/^\^/); // All should have same external dependencies diff --git a/apps/builder/src/export/__tests__/VersioningSafetyGuard.test.ts b/apps/builder/src/export/__tests__/VersioningSafetyGuard.test.ts index 9d1215ce..7f3fb862 100644 --- a/apps/builder/src/export/__tests__/VersioningSafetyGuard.test.ts +++ b/apps/builder/src/export/__tests__/VersioningSafetyGuard.test.ts @@ -205,7 +205,7 @@ describe('Versioning Safety Guard', () => { }); describe('Builder release-channel resolution (published adapters)', () => { - it('uses npm rc dist-tag for staging when versions.ts holds a stable semver', async () => { + it('uses a caret range for staging when versions.ts holds a stable semver', async () => { const versionsModule = await import('../versions'); const original = { ...versionsModule.packageVersions }; const testAdapter = adapterPackageMap['evm']; @@ -221,7 +221,7 @@ describe('Versioning Safety Guard', () => { { env: 'staging' } ); const deps = JSON.parse(updated).dependencies || {}; - expect(deps[testAdapter]).toBe('rc'); + expect(deps[testAdapter]).toBe('^1.2.3'); } finally { Object.assign(versionsModule.packageVersions, original); } @@ -252,7 +252,7 @@ describe('Versioning Safety Guard', () => { describe('no workspace:* in staging exports', () => { it.each(DEVELOPED_ECOSYSTEMS)( - 'should resolve %s adapter dependency to RC version in staging', + 'should resolve %s adapter dependency to a publishable version in staging', async (ecosystem) => { const packageManager = new PackageManager(mockRendererConfig); @@ -274,11 +274,11 @@ describe('Versioning Safety Guard', () => { `Must not be "workspace:*" — this will break end users.` ).not.toBe('workspace:*'); - const rcVersionOrTag = /^(rc|\d+\.\d+\.\d+-rc(?:[-.]\d+)?)$/; + const stableRangeOrRc = /^(\^\d+\.\d+\.\d+|\d+\.\d+\.\d+-rc(?:[-.]\d+)?)$/; expect( deps[expectedAdapter], - `Staging: ${ecosystem} adapter "${expectedAdapter}" should be RC version or dist-tag` - ).toMatch(rcVersionOrTag); + `Staging: ${ecosystem} adapter "${expectedAdapter}" should be a stable range or managed RC` + ).toMatch(stableRangeOrRc); for (const [pkg, version] of Object.entries(deps)) { expect( diff --git a/apps/builder/src/export/__tests__/__snapshots__/ExportSnapshotTests.test.ts.snap b/apps/builder/src/export/__tests__/__snapshots__/ExportSnapshotTests.test.ts.snap index 8b098997..c5f22cc1 100644 --- a/apps/builder/src/export/__tests__/__snapshots__/ExportSnapshotTests.test.ts.snap +++ b/apps/builder/src/export/__tests__/__snapshots__/ExportSnapshotTests.test.ts.snap @@ -341,11 +341,11 @@ export default function GeneratedForm({ adapter, isWalletConnected }: GeneratedF exports[`Export Snapshot Tests > evm Export Snapshots > should match snapshot for package.json structure > package-json-evm 1`] = ` { "dependencies": { - "@openzeppelin/adapter-evm": "^2.3.0", + "@openzeppelin/adapter-evm": "^2.7.0", "@openzeppelin/ui-components": "^3.8.1", "@openzeppelin/ui-react": "^3.3.0", "@openzeppelin/ui-renderer": "^3.4.0", - "@openzeppelin/ui-types": "^3.3.0", + "@openzeppelin/ui-types": "^3.5.0", "@openzeppelin/ui-utils": "^3.3.0", "@tanstack/react-query": "^5.0.0", "@wagmi/core": "^2.20.3", @@ -712,7 +712,7 @@ exports[`Export Snapshot Tests > polkadot Export Snapshots > should match snapsh "@openzeppelin/ui-components": "^3.8.1", "@openzeppelin/ui-react": "^3.3.0", "@openzeppelin/ui-renderer": "^3.4.0", - "@openzeppelin/ui-types": "^3.3.0", + "@openzeppelin/ui-types": "^3.5.0", "@openzeppelin/ui-utils": "^3.3.0", "@tanstack/react-query": "^5.0.0", "@wagmi/core": "^2.20.3", diff --git a/apps/builder/src/export/versions.ts b/apps/builder/src/export/versions.ts index c919ef3e..f143149b 100644 --- a/apps/builder/src/export/versions.ts +++ b/apps/builder/src/export/versions.ts @@ -6,7 +6,7 @@ */ export const packageVersions = { - '@openzeppelin/adapter-evm': '2.3.0', + '@openzeppelin/adapter-evm': '2.7.0', '@openzeppelin/adapter-midnight': '2.2.0', '@openzeppelin/adapter-polkadot': '2.2.0', '@openzeppelin/adapter-solana': '2.2.0', @@ -14,7 +14,7 @@ export const packageVersions = { '@openzeppelin/ui-react': '3.3.0', '@openzeppelin/ui-renderer': '3.4.0', '@openzeppelin/ui-storage': '1.2.3', - '@openzeppelin/ui-types': '3.3.0', + '@openzeppelin/ui-types': '3.5.0', '@openzeppelin/ui-components': '3.8.1', '@openzeppelin/ui-utils': '3.3.0', '@openzeppelin/ui-styles': '1.1.0', From ccd38dc553ebae7a564aa94711e4cfcc810c9e5b Mon Sep 17 00:00:00 2001 From: yonkoo11 Date: Sun, 23 Aug 2026 11:34:01 +0100 Subject: [PATCH 3/3] chore(export): sync published package versions --- .../ExportSnapshotTests.test.ts.snap | 24 +++++++++---------- apps/builder/src/export/versions.ts | 24 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/builder/src/export/__tests__/__snapshots__/ExportSnapshotTests.test.ts.snap b/apps/builder/src/export/__tests__/__snapshots__/ExportSnapshotTests.test.ts.snap index 905219ca..899a4944 100644 --- a/apps/builder/src/export/__tests__/__snapshots__/ExportSnapshotTests.test.ts.snap +++ b/apps/builder/src/export/__tests__/__snapshots__/ExportSnapshotTests.test.ts.snap @@ -341,12 +341,12 @@ export default function GeneratedForm({ adapter, isWalletConnected }: GeneratedF exports[`Export Snapshot Tests > evm Export Snapshots > should match snapshot for package.json structure > package-json-evm 1`] = ` { "dependencies": { - "@openzeppelin/adapter-evm": "^4.0.0", - "@openzeppelin/ui-components": "^3.8.2", - "@openzeppelin/ui-react": "^3.3.1", - "@openzeppelin/ui-renderer": "^3.4.1", - "@openzeppelin/ui-types": "^3.5.1", - "@openzeppelin/ui-utils": "^4.0.0", + "@openzeppelin/adapter-evm": "^4.0.2", + "@openzeppelin/ui-components": "^3.8.3", + "@openzeppelin/ui-react": "^3.3.2", + "@openzeppelin/ui-renderer": "^3.4.2", + "@openzeppelin/ui-types": "^3.5.2", + "@openzeppelin/ui-utils": "^4.0.1", "@tanstack/react-query": "^5.0.0", "@wagmi/core": "^2.20.3", "react": "^19.2.1", @@ -708,12 +708,12 @@ export default function GeneratedForm({ adapter, isWalletConnected }: GeneratedF exports[`Export Snapshot Tests > polkadot Export Snapshots > should match snapshot for package.json structure > package-json-polkadot 1`] = ` { "dependencies": { - "@openzeppelin/adapter-polkadot": "^3.0.0", - "@openzeppelin/ui-components": "^3.8.2", - "@openzeppelin/ui-react": "^3.3.1", - "@openzeppelin/ui-renderer": "^3.4.1", - "@openzeppelin/ui-types": "^3.5.1", - "@openzeppelin/ui-utils": "^4.0.0", + "@openzeppelin/adapter-polkadot": "^4.0.2", + "@openzeppelin/ui-components": "^3.8.3", + "@openzeppelin/ui-react": "^3.3.2", + "@openzeppelin/ui-renderer": "^3.4.2", + "@openzeppelin/ui-types": "^3.5.2", + "@openzeppelin/ui-utils": "^4.0.1", "@tanstack/react-query": "^5.0.0", "@wagmi/core": "^2.20.3", "react": "^19.2.1", diff --git a/apps/builder/src/export/versions.ts b/apps/builder/src/export/versions.ts index 10719c36..bb6394f4 100644 --- a/apps/builder/src/export/versions.ts +++ b/apps/builder/src/export/versions.ts @@ -6,16 +6,16 @@ */ export const packageVersions = { - '@openzeppelin/adapter-evm': '4.0.0', - '@openzeppelin/adapter-midnight': '4.0.0', - '@openzeppelin/adapter-polkadot': '3.0.0', - '@openzeppelin/adapter-solana': '4.0.0', - '@openzeppelin/adapter-stellar': '4.0.0', - '@openzeppelin/ui-react': '3.3.1', - '@openzeppelin/ui-renderer': '3.4.1', - '@openzeppelin/ui-storage': '1.2.4', - '@openzeppelin/ui-types': '3.5.1', - '@openzeppelin/ui-components': '3.8.2', - '@openzeppelin/ui-utils': '4.0.0', - '@openzeppelin/ui-styles': '1.1.0', + '@openzeppelin/adapter-evm': '4.0.2', + '@openzeppelin/adapter-midnight': '4.0.1', + '@openzeppelin/adapter-polkadot': '4.0.2', + '@openzeppelin/adapter-solana': '4.0.1', + '@openzeppelin/adapter-stellar': '4.0.1', + '@openzeppelin/ui-react': '3.3.2', + '@openzeppelin/ui-renderer': '3.4.2', + '@openzeppelin/ui-storage': '1.2.5', + '@openzeppelin/ui-types': '3.5.2', + '@openzeppelin/ui-components': '3.8.3', + '@openzeppelin/ui-utils': '4.0.1', + '@openzeppelin/ui-styles': '1.1.1', };