Skip to content
Open
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
12 changes: 7 additions & 5 deletions apps/builder/src/export/PackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Comment on lines 533 to +537
: `^${managedVersion}`;
}
} else {
// Production: Use stable published versions
Expand Down
28 changes: 11 additions & 17 deletions apps/builder/src/export/__tests__/PackageManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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$/);
Expand Down Expand Up @@ -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:*');
});

Expand All @@ -487,7 +485,7 @@ describe('PackageManager', () => {
const updated = await packageManager.updatePackageJson(
basePackageJson,
formConfig,
'evm',
'stellar',
'testFunction',
{ env: 'staging' }
);
Expand All @@ -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 () => {
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);

Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
24 changes: 12 additions & 12 deletions apps/builder/src/export/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};