Skip to content
Draft
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
109 changes: 62 additions & 47 deletions packages/cli/src/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
PackageReference,
preparePublishPackage,
} from '@usecannon/builder';
import { blueBright, bold, gray } from 'chalk';
import { bold, cyanBright, gray, green, yellow } from 'chalk';
import prompts from 'prompts';
import * as viem from 'viem';
import { log } from '../util/console';
Expand Down Expand Up @@ -37,22 +37,17 @@ interface DeployList {
export async function publish({
fullPackageRef,
cliSettings,
onChainRegistry,
tags = ['latest'],
onChainRegistry,
chainId,
quiet = false,
includeProvisioned = true,
skipConfirm = false,
}: Params) {
if (onChainRegistry instanceof OnChainRegistry) {
if (!onChainRegistry.signer) {
throw new Error('signer not provided in registry');
}
if (!quiet) {
log(blueBright(`Publishing with ${onChainRegistry.signer!.address}`));
log();
}
if (onChainRegistry instanceof OnChainRegistry && !onChainRegistry.signer) {
throw new Error('signer not provided in registry');
}

// Generate CannonStorage to publish ipfs remotely and write to the registry
const toStorage = new CannonStorage(onChainRegistry, {
ipfs: new IPFSLoader(cliSettings.publishIpfsUrl || getCannonRepoRegistryUrl()),
Expand All @@ -62,7 +57,6 @@ export async function publish({
const localRegistry = new LocalRegistry(cliSettings.cannonDirectory);
const fromStorage = new CannonStorage(localRegistry, getMainLoader(cliSettings));

// if the package reference is an ipfs reference (url or hash) we pass it the full package ref since its referencing a specific deploy
let deploys = await localRegistry.scanDeploys(fullPackageRef, chainId);

if (!deploys || deploys.length === 0) {
Expand Down Expand Up @@ -127,7 +121,7 @@ export async function publish({
chainId: deploys[0].chainId,
fromStorage,
toStorage,
tags: publishTags!,
tags: publishTags,
includeProvisioned,
});

Expand All @@ -138,54 +132,75 @@ export async function publish({
throw new Error("There isn't anything new to publish.");
}

for (const publishCall of publishCalls) {
const packageName = new PackageReference(publishCall.packagesNames[0]).name;
log(blueBright(`\nThis will publish ${bold(packageName)} to the registry:`));

for (const fullPackageRef of publishCall.packagesNames) {
const { version, preset } = new PackageReference(fullPackageRef);
log(` - ${version} (preset: ${preset})`);
if (!quiet) {
if (onChainRegistry instanceof OnChainRegistry) {
log('\n' + bold('Publishing Package'));
log(gray('================================================================================'));
log(`Package: ${cyanBright(fullPackageRef)}`);
log(`Signer: ${cyanBright(onChainRegistry.signer!.address)}`);
log(gray('================================================================================\n'));
}
}

log();

if (onChainRegistry instanceof OnChainRegistry) {
const totalFees = await onChainRegistry.calculatePublishingFee(publishCalls.length);

log(`Total publishing fees: ${viem.formatEther(totalFees)} ETH`);
log();
log(bold('Package Details:'));
for (const publishCall of publishCalls) {
const packageName = new PackageReference(publishCall.packagesNames[0]).name;
log(gray('--------------------------------------------------------------------------------'));
log(`Package: ${cyanBright(packageName)}`);

for (const fullPackageRef of publishCall.packagesNames) {
const { version, preset } = new PackageReference(fullPackageRef);
log(` • ${cyanBright(version)}${preset !== 'main' ? ` (preset: ${preset})` : gray(' (default)')}`);
}

if (publishCall.url) {
log(gray('\nIPFS Data:'));
log(gray(` URL: ${publishCall.url}`));
}
}

if (
totalFees > 0n &&
totalFees >= (await onChainRegistry.provider!.getBalance({ address: onChainRegistry.signer!.address }))
) {
throw new Error('You do not appear to have enough ETH in your wallet to publish');
if (onChainRegistry instanceof OnChainRegistry) {
const totalFees = await onChainRegistry.calculatePublishingFee(publishCalls.length);

log(bold('\nTransaction Details:'));
log(gray(` Publishing Fee: ${viem.formatEther(totalFees)} ETH`));

if (totalFees > 0n) {
const balance = await onChainRegistry.provider!.getBalance({
address: onChainRegistry.signer!.address
});

if (totalFees >= balance) {
throw new Error('You do not appear to have enough ETH in your wallet to publish');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The balance check is now inside if (!quiet), meaning in quiet mode the insufficient balance check is skipped. The transaction will still fail, but the user gets a less helpful error. Consider moving the balance check outside the !quiet block:

if (onChainRegistry instanceof OnChainRegistry) {
  const totalFees = await onChainRegistry.calculatePublishingFee(publishCalls.length);
  
  if (totalFees > 0n) {
    const balance = await onChainRegistry.provider!.getBalance({ 
      address: onChainRegistry.signer!.address 
    });
    
    if (totalFees >= balance) {
      throw new Error('You do not appear to have enough ETH in your wallet to publish');
    }
  }
  
  if (!quiet) {
    log(bold('\nTransaction Details:'));
    log(gray(`  Publishing Fee: ${viem.formatEther(totalFees)} ETH`));
  }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The balance check is now inside if (!quiet), meaning in quiet mode the insufficient balance check is skipped. The transaction will still fail, but the user gets a less helpful error. Consider moving the balance check outside the !quiet block:

if (onChainRegistry instanceof OnChainRegistry) {
  const totalFees = await onChainRegistry.calculatePublishingFee(publishCalls.length);
  
  if (totalFees > 0n) {
    const balance = await onChainRegistry.provider!.getBalance({ 
      address: onChainRegistry.signer!.address 
    });
    
    if (totalFees >= balance) {
      throw new Error('You do not appear to have enough ETH in your wallet to publish');
    }
  }
  
  if (!quiet) {
    log(bold('\nTransaction Details:'));
    log(gray(`  Publishing Fee: ${viem.formatEther(totalFees)} ETH`));
  }
}

}
}
}

if (!skipConfirm) {
const verification = await prompts({
const { proceed } = await prompts({
type: 'confirm',
name: 'confirmation',
message: 'Proceed?',
initial: true,
name: 'proceed',
message: 'Proceed with publishing?',
});

if (!verification.confirmation) {
log('Cancelled');
process.exit(1);
if (!proceed) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good change — throw new Error('User aborted') is much better than process.exit(1) for testability and proper error handling upstream.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good change — throw new Error('User aborted') is much better than process.exit(1) for testability and proper error handling upstream.

throw new Error('User aborted');
}
}

log(bold('Publishing package...'));
log(gray('This may take a few minutes.'));
log();

const registrationReceipts = await toStorage.registry.publishMany(publishCalls);

if (!quiet) {
log(blueBright('Transactions:'));
for (const tx of registrationReceipts) log(` - ${tx}`);
log(gray('\nPublishing packages...'));
}

const txHashes = await onChainRegistry.publishMany(publishCalls);

if (!quiet) {
log(`\n${green('✓')} Successfully published packages`);
for (const txHash of txHashes) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good change — returning the txHashes makes this function more useful for programmatic consumers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good change — returning the txHashes makes this function more useful for programmatic consumers.

log(gray(`Transaction Hash: ${txHash}`));
}
log('\n');
}

return txHashes;
}