Skip to content
Merged

Dev #75

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
6 changes: 2 additions & 4 deletions contracts/standards/behavior/ICopyable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ pragma solidity 0.8.33;
*
* Bloxes implementing this interface can be cloned by factory patterns (e.g. CopyBlox,
* FactoryBlox) and initialized in one call with owner/broadcaster/recovery/timelock/
* eventForwarder plus arbitrary init data, or have clone-specific data set via
* setCloneData.
* eventForwarder plus arbitrary init data, or have clone-specific data set
*
* Use cases:
* - Clone and init in one step: factory calls initializeWithData(..., initData).
* - Clone with standard init then set clone data: factory calls initialize(...)
* then the deployer or factory calls setCloneData(initData).
* - Clone with standard init then set clone data: factory calls initializeWithData(...)
*/
interface ICopyable {
/**
Expand Down
11 changes: 4 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Bloxchain",
"version": "1.0.0-alpha.8",
"version": "1.0.0-alpha.11",
"description": "Library engine for building enterprise grade decentralized permissioned applications",
"type": "module",
"main": "truffle-config.cjs",
Expand Down Expand Up @@ -38,17 +38,14 @@
"test:sanity-sdk:examples": "npx tsx --tsconfig scripts/sanity-sdk/tsconfig.json scripts/sanity-sdk/run-all-tests.ts --examples",
"test:sanity-sdk:all": "npx tsx --tsconfig scripts/sanity-sdk/tsconfig.json scripts/sanity-sdk/run-all-tests.ts --all",
"test:e2e": "npm run deploy:truffle && npm run test:sanity:core && npm run test:sanity-sdk:core",
"test:packages": "node scripts/test-packages.cjs",
"extract-abi": "node scripts/extract-abi.cjs",
"release:sync-versions": "node scripts/sync-versions.cjs",
"release:sync-versions:tag:alpha": "node scripts/sync-versions.cjs --tag alpha.8",
"build:sdk": "cd sdk/typescript && npm run build",
"build:sdk:clean": "cd sdk/typescript && npm run clean && npm run build",
"prepublish:sdk": "npm run release:sync-versions && npm run extract-abi && npm run build:sdk",
"prepublish:contracts": "npm run release:sync-versions",
"generate:contracts-lock": "cd package && npm install --package-lock-only",
"publish:contracts": "npm run prepublish:contracts && cd package && npm publish --tag alpha.8",
"publish:sdk": "npm run prepublish:sdk && cd sdk/typescript && npm publish --tag alpha.8",
"release:prepare": "node scripts/release-prepare.cjs",
"publish:contracts": "npm run release:prepare && cd package && npm publish --tag alpha.11",
"publish:sdk": "npm run release:sync-versions && npm run extract-abi && npm run build:sdk && cd sdk/typescript && npm publish --tag alpha.11",
"docgen": "npm run compile:hardhat && cd docgen && npm run docgen",
"docgen:install": "cd docgen && npm install",
"format": "prettier --config .prettierrc --write \"contracts/**/*.sol\"",
Expand Down
2 changes: 1 addition & 1 deletion package/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bloxchain/contracts",
"version": "1.0.0-alpha.8",
"version": "1.0.0-alpha.11",
"description": "Library engine for building enterprise grade decentralized permissioned applications",
"files": [
"core",
Expand Down
6 changes: 6 additions & 0 deletions package/scripts/postpublish-contracts.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const copiedContractsDir = path.join(contractsDir, 'contracts');
const copiedAbiDir = path.join(contractsDir, 'abi');
const copiedStandardsDir = path.join(contractsDir, 'standards');
const copiedComponentsDir = path.join(contractsDir, 'components');
const copiedCoreDir = path.join(contractsDir, 'core');

console.log('🧹 Cleaning up after publish...\n');

Expand All @@ -32,4 +33,9 @@ if (fs.existsSync(copiedComponentsDir)) {
console.log('✅ Removed copied components directory');
}

if (fs.existsSync(copiedCoreDir)) {
fs.rmSync(copiedCoreDir, { recursive: true, force: true });
console.log('✅ Removed copied core directory');
}

console.log('\n✅ Cleanup complete!');
307 changes: 307 additions & 0 deletions scripts/release-prepare.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
#!/usr/bin/env node
// release-prepare.cjs
// Single-command pre-publication: sync versions, extract ABIs, prepare package, test, verify.
// Usage: npm run release:prepare (from repo root)
// Env: SKIP_TESTS=1 | PREPARE_CONTRACTS_ONLY=1 | DEBUG=1

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const rootDir = path.resolve(__dirname, '..');
const contractsPackageDir = path.join(rootDir, 'package');
const sdkPackageDir = path.join(rootDir, 'sdk', 'typescript');

const SKIP_TESTS = process.env.SKIP_TESTS === '1';
const PREPARE_CONTRACTS_ONLY = process.env.PREPARE_CONTRACTS_ONLY === '1';
const DEBUG = process.env.DEBUG === '1';

const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
};

function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}

function logStep(step, message) {
log(`\n${step} ${message}`, 'cyan');
}

function logSuccess(message) {
log(`✅ ${message}`, 'green');
}

function logError(message) {
log(`❌ ${message}`, 'red');
}

function logWarning(message) {
log(`⚠️ ${message}`, 'yellow');
}

function fail(message) {
logError(message);
throw new Error(message);
}

const REQUIRED_PACKAGE_JSON_FIELDS = ['name', 'version', 'description', 'license'];

function validatePackageJson(packagePath, packageName) {
const packageJsonPath = path.join(packagePath, 'package.json');
if (!fs.existsSync(packageJsonPath)) {
fail(`${packageName}: package.json not found`);
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
for (const field of REQUIRED_PACKAGE_JSON_FIELDS) {
if (!packageJson[field]) {
fail(`${packageName}: missing required field '${field}' in package.json`);
}
}
if (!/^\d+\.\d+\.\d+/.test(packageJson.version)) {
fail(`${packageName}: invalid version format '${packageJson.version}' (expected semver)`);
}
return packageJson;
}

function exec(command, options = {}) {
const defaultOptions = {
cwd: rootDir,
stdio: 'inherit',
encoding: 'utf8',
shell: true,
};
try {
execSync(command, { ...defaultOptions, ...options });
return true;
} catch (error) {
if (DEBUG && error.stack) {
log('\n' + error.stack, 'yellow');
}
throw error;
}
}

function execInPackage(dir, command, options = {}) {
try {
execSync(command, {
cwd: dir,
stdio: 'inherit',
encoding: 'utf8',
shell: true,
...options,
});
return true;
} catch (error) {
if (DEBUG && error.stack) {
log('\n' + error.stack, 'yellow');
}
throw error;
}
}

function syncVersions() {
logStep('📋', 'Step 1: Syncing versions...');
exec('npm run release:sync-versions');
const rootPkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
const contractsPkg = JSON.parse(fs.readFileSync(path.join(contractsPackageDir, 'package.json'), 'utf8'));
if (rootPkg.version !== contractsPkg.version) {
fail(`Version mismatch: root ${rootPkg.version} vs package ${contractsPkg.version}`);
}
logSuccess('Versions synced and verified');
}

function extractAbi() {
logStep('📋', 'Step 2: Extracting ABIs...');
exec('npm run extract-abi');
const rootAbiDir = path.join(rootDir, 'abi');
if (!fs.existsSync(rootAbiDir)) {
fail('abi/ directory not found after extract-abi');
}
const abiFiles = fs.readdirSync(rootAbiDir).filter((f) => f.endsWith('.abi.json'));
if (abiFiles.length === 0) {
fail('No .abi.json files in abi/ after extract-abi');
}
logSuccess('ABIs extracted and verified');
}

function getSolPathsRecursive(dir, baseDir, excludedDirs) {
const results = [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const relPath = path.relative(baseDir, fullPath);
if (entry.isDirectory()) {
if (excludedDirs.includes(entry.name)) continue;
results.push(...getSolPathsRecursive(fullPath, baseDir, excludedDirs));
} else if (entry.name.endsWith('.sol')) {
results.push(relPath);
}
}
return results;
}

function prepareContractsPackage() {
logStep('📋', 'Step 3: Preparing @bloxchain/contracts package...');
execInPackage(contractsPackageDir, 'node scripts/prepublish-contracts.cjs');
const coreDir = path.join(contractsPackageDir, 'core');
const abiDir = path.join(contractsPackageDir, 'abi');
if (!fs.existsSync(coreDir)) {
fail('package/core/ not found after prepare');
}
if (!fs.existsSync(abiDir)) {
fail('package/abi/ not found after prepare');
}
if (fs.readdirSync(coreDir).length === 0) {
fail('package/core/ is empty after prepare');
}
if (fs.readdirSync(abiDir).length === 0) {
fail('package/abi/ is empty after prepare');
}
const sourceContractsDir = path.join(rootDir, 'contracts');
const excludedDirs = ['examples', 'experimental'];
const expectedSolPaths = getSolPathsRecursive(sourceContractsDir, sourceContractsDir, excludedDirs);
const missing = expectedSolPaths.filter(
(rel) => !fs.existsSync(path.join(contractsPackageDir, rel))
);
if (missing.length > 0) {
fail(
'Package layout mismatch: missing .sol files (expected from contracts/, excluding examples & experimental):\n ' +
missing.join('\n ')
);
}
logSuccess('Contracts package prepared and verified (layout matches contracts/)');
}

function runTests() {
if (SKIP_TESTS) {
logWarning('Skipping tests (SKIP_TESTS=1)');
return;
}
logStep('📋', 'Step 4: Running tests...');
exec('npm run test:foundry');
logSuccess('Foundry tests passed');
// Sanity tests require deploy/chain; optional for prepare
log('Running sanity:core (may require deployed contracts)...', 'yellow');
const sanityOk = exec('npm run test:sanity:core', { throwOnError: false });
if (!sanityOk) {
logWarning('test:sanity:core failed or skipped; continuing. Run manually if needed.');
} else {
logSuccess('Sanity core tests passed');
}
log('Running sanity-sdk:core (may require deployed contracts)...', 'yellow');
const sanitySdkOk = exec('npm run test:sanity-sdk:core', { throwOnError: false });
if (!sanitySdkOk) {
logWarning('test:sanity-sdk:core failed or skipped; continuing. Run manually if needed.');
} else {
logSuccess('Sanity SDK tests passed');
}
}

function verifyContractsPackage() {
logStep('📋', 'Step 5: Verifying @bloxchain/contracts package...');
const packageJson = validatePackageJson(contractsPackageDir, '@bloxchain/contracts');
const files = packageJson.files || [];
for (const name of files) {
if (name === 'README.md') continue;
const fullPath = path.join(contractsPackageDir, name);
if (!fs.existsSync(fullPath)) {
fail(`Required package file missing: ${name}`);
}
}
logSuccess('Required files present');
let packOutput;
try {
packOutput = execSync('npm pack --dry-run 2>&1', {
cwd: contractsPackageDir,
encoding: 'utf8',
shell: true,
});
} catch (error) {
fail('npm pack --dry-run failed: ' + error.message);
}
const requiredInPack = ['core', 'abi', 'standards'];
for (const dir of requiredInPack) {
if (!new RegExp(dir + '[/\\\\]').test(packOutput)) {
fail(`npm pack output missing ${dir}/`);
}
}
logSuccess('npm pack --dry-run OK');
}

function prepareSdk() {
if (PREPARE_CONTRACTS_ONLY) {
logWarning('Skipping SDK prepare (PREPARE_CONTRACTS_ONLY=1)');
return;
}
logStep('📋', 'Step 6: Preparing @bloxchain/sdk...');
exec('npm run build:sdk');
validatePackageJson(sdkPackageDir, '@bloxchain/sdk');
const distPath = path.join(sdkPackageDir, 'dist');
if (!fs.existsSync(path.join(distPath, 'index.js'))) {
fail('SDK dist/index.js not found after build');
}
if (!fs.existsSync(path.join(distPath, 'index.d.ts'))) {
fail('SDK dist/index.d.ts not found after build');
}
let packOutput;
try {
packOutput = execSync('npm pack --dry-run 2>&1', {
cwd: sdkPackageDir,
encoding: 'utf8',
shell: true,
});
} catch (error) {
fail('SDK npm pack --dry-run failed: ' + error.message);
}
const hasDist = /dist[/\\]/.test(packOutput);
if (!hasDist) fail('SDK npm pack output missing dist/');
logSuccess('SDK prepared and verified');
}

function printSummary() {
log('\n' + '='.repeat(60), 'bright');
log('✅ Release prepare complete', 'green');
log('='.repeat(60) + '\n', 'bright');
log('Ready to publish. Run:', 'cyan');
log(' npm login', 'yellow');
log(' npm run publish:contracts', 'yellow');
log(' npm run publish:sdk', 'yellow');
log('\nOr manually:', 'cyan');
log(' cd package && npm publish --tag alpha.11', 'yellow');
log(' cd sdk/typescript && npm publish --tag alpha.11', 'yellow');
log('');
}

function main() {
log('\n' + '='.repeat(60), 'bright');
log('📦 Release Prepare', 'bright');
log('='.repeat(60), 'bright');
try {
syncVersions();
extractAbi();
prepareContractsPackage();
runTests();
verifyContractsPackage();
prepareSdk();
printSummary();
process.exit(0);
} catch (error) {
log('\n' + '='.repeat(60), 'bright');
logError('Release prepare failed');
log('='.repeat(60) + '\n', 'bright');
logError(error.message);
process.exit(1);
}
}

if (require.main === module) {
main();
}

module.exports = { main };
Loading