diff --git a/package.json b/package.json index 0533145..4f00315 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,29 @@ { "name": "@shadow-drive/cli", - "version": "0.5.3", + "version": "0.6.0", "main": "shdw-drive.js", "bin": { - "shdw-drive": "./dist/shdw-drive.js" + "shdw-drive": "./dist/src/shdw-drive.js" }, "license": "MIT", "publishConfig": { "access": "public" }, "scripts": { - "build": "tsc" + "clean": "rimraf dist", + "build": "npm run clean && tsc", + "prepublishOnly": "npm run build" }, "dependencies": { "@coral-xyz/anchor": "^0.27.0", - "@shadow-drive/sdk": "^4.0.2", + "@shadow-drive/sdk": "^5.0.0", "@solana/spl-token": "^0.2.0", "@solana/web3.js": "^1.41.0", "bigint-conversion": "^2.2.1", "bottleneck": "^2.19.5", "bs58": "^5.0.0", "cli-progress": "^3.11.2", - "commander": "^9.2.0", + "commander": "^11.0.0", "form-data": "^4.0.0", "loglevel": "^1.8.0", "mime-types": "^2.1.35", @@ -37,6 +39,7 @@ "@types/node-fetch": "^2.6.1", "@types/prompts": "^2.0.14", "@types/typescript": "^2.0.0", - "pkg": "^5.6.0" + "pkg": "^5.6.0", + "rimraf": "^5.0.1" } } diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 30faca1..c494f8a 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -17,6 +17,7 @@ import { Program } from "@coral-xyz/anchor"; import { SHDW_DRIVE_ENDPOINT, programId } from "../constants"; import fetch from "node-fetch"; import Bottleneck from "bottleneck"; +import { StorageAccount, StorageAccountV2 } from "@shadow-drive/sdk"; export function loadWalletKey(keypair: string): Keypair { if (!keypair || keypair == "") { @@ -115,32 +116,21 @@ export function bytesToHuman(bytes: any, si = false, dp = 1) { * @returns */ export async function getFormattedStorageAccounts( - key: anchor.web3.PublicKey, - totalAccounts: number + rawAccounts: Array<{ + publicKey: anchor.web3.PublicKey; + account: StorageAccountV2; + }> ): Promise<[Array, Array]> { const limiter = new Bottleneck({ minTime: 50, maxConcurrent: 10, }); - let accountsToFetch: anchor.web3.PublicKey[] = []; - - for (let i = 0; i <= totalAccounts; i++) { - let [acc] = await anchor.web3.PublicKey.findProgramAddress( - [ - Buffer.from("storage-account"), - key.toBytes(), - new anchor.BN(i).toTwos(0).toArrayLike(Buffer, "le", 4), - ], - programId - ); - accountsToFetch.push(acc); - } - - log.debug(`Accounts to Fetch length: ${accountsToFetch.length}`); + let accountKeys = [...rawAccounts.map((account) => account.publicKey)]; + log.debug(`Accounts to Fetch length: ${rawAccounts.length}`); let accounts: any = []; await Promise.all( - accountsToFetch.map(async (account) => { + accountKeys.map(async (account) => { try { const storageAccountDetails = await limiter.schedule(() => fetch(`${SHDW_DRIVE_ENDPOINT}/storage-account-info`, { @@ -202,7 +192,7 @@ export async function getFormattedStorageAccounts( return acc; } }); - return [formattedAccounts, accountsToFetch]; + return [formattedAccounts, accountKeys]; } export function getAnchorEnvironment( keypair: anchor.web3.Keypair, diff --git a/src/shdw-drive.ts b/src/shdw-drive.ts index d6d404f..cee00f3 100644 --- a/src/shdw-drive.ts +++ b/src/shdw-drive.ts @@ -7,14 +7,12 @@ import * as anchor from "@coral-xyz/anchor"; import { PublicKey } from "@solana/web3.js"; import { program } from "commander"; import log from "loglevel"; -import fetch from "node-fetch"; -import { BYTES_PER_GIB, SHDW_DRIVE_ENDPOINT, tokenMint } from "./constants"; +import { BYTES_PER_GIB, tokenMint } from "./constants"; import { bytesToHuman, findAssociatedTokenAddress, getAnchorEnvironment, getFormattedStorageAccounts, - getStorageConfigPDA, humanSizeToBytes, loadWalletKey, parseScientific, @@ -22,11 +20,12 @@ import { validateStorageAccount, } from "./helpers"; import cliProgress from "cli-progress"; -import { ShadowDriveResponse, ShdwDrive } from "@shadow-drive/sdk"; +import { ShadowDriveResponse, ShdwDrive, UserInfo } from "@shadow-drive/sdk"; import { from, map, mergeMap, tap, toArray } from "rxjs"; import mime from "mime-types"; +import { version } from "../package.json"; -program.version("0.5.3"); +program.version(version); program.description( "CLI for interacting with Shade Drive. This tool uses Solana's Mainnet-Beta network with an internal RPC configuration. It does not use your local Solana configurations." ); @@ -71,11 +70,11 @@ programCommand("create-storage-account") log.error( `Unable to retrieve Shadow Drive storage config account.\n${e}` ); - return; + return process.exit(0); } - if (!storageConfigInfo) return; + if (!storageConfigInfo) return process.exit(0); // If userInfo hasn't been initialized, default to 0 for account seed - let userInfoAccount = await connection.getAccountInfo(userInfo); + let userInfoAccount = await UserInfo.fetch(connection, userInfo); let accountSeed = new anchor.BN(0); if (userInfoAccount !== null) { let userInfoData = await programClient.account.userInfo.fetch( @@ -95,7 +94,7 @@ programCommand("create-storage-account") log.error( "You must agree to the Terms of Service before creating your first storage account on Shadow Drive." ); - return; + return process.exit(0); } } @@ -105,7 +104,7 @@ programCommand("create-storage-account") log.error( `${options.size} is not a valid input for size. Please use a string like '1KB', '1MB', '1GB'.` ); - return; + return process.exit(0); } const shadesPerGib = storageConfigInfo.shadesPerGib; const storageInputBigInt = new anchor.BN(Number(storageInputAsBytes)); @@ -115,7 +114,7 @@ programCommand("create-storage-account") .mul(shadesPerGib) .div(bytesPerGib); const accountCostUiAmount = parseScientific( - (accountCostEstimate / new anchor.BN(10 ** 9)).toString() + accountCostEstimate.div(new anchor.BN(10 ** 9)).toString() ); const confirmStorageCost = await prompts({ @@ -125,9 +124,10 @@ programCommand("create-storage-account") initial: false, }); if (!confirmStorageCost.acceptStorageCost) { - return log.error( + log.error( "You must accept the estimated storage cost to continue." ); + return process.exit(0); } log.debug("storageInputAsBytes", storageInputAsBytes); @@ -140,7 +140,7 @@ programCommand("create-storage-account") ); } catch (e) { log.error(`Unable to retrieve Associated token account.\n${e}`); - return; + return process.exit(0); } log.debug("Associated token account: ", ata.toString()); @@ -163,11 +163,11 @@ programCommand("create-storage-account") programClient.programId ); - log.debug("storageRequested:", storageRequested); + log.debug("storageRequested:", storageRequested.toNumber()); log.debug("identifier:", identifier); - log.debug("storageAccount:", storageAccount); - log.debug("userInfo:", userInfo); - log.debug("stakeAccount:", stakeAccount); + log.debug("storageAccount:", storageAccount.toString()); + log.debug("userInfo:", userInfo.toString()); + log.debug("stakeAccount:", stakeAccount.toString()); log.debug("Sending off initializeAccount tx"); const txnSpinner = ora( @@ -177,21 +177,20 @@ programCommand("create-storage-account") try { storageResponse = await drive.createStorageAccount( options.name, - storageInput, - "v2" + storageInput ); + log.info(storageResponse); } catch (e) { txnSpinner.fail( "Error processing transaction. See below for details:" ); - log.error(`${e}`); - - return; + log.error(`${e.message}`); + return process.exit(0); } txnSpinner.succeed( `Successfully created your new storage account of ${options.size} located at the following address on Solana: ${storageResponse.shdw_bucket}` ); - return; + return process.exit(0); }); programCommand("upload-file") @@ -209,7 +208,6 @@ programCommand("upload-file") ) .action(async (options, cmd) => { await handleUpload(options, cmd, "file"); - return; }); programCommand("edit-file") @@ -240,11 +238,12 @@ programCommand("edit-file") options.file.lastIndexOf("/") + 1 ); const fileData = fs.readFileSync(options.file); - const userInfoAccount = await connection.getAccountInfo(userInfo); + const userInfoAccount = await UserInfo.fetch(connection, userInfo); if (userInfoAccount === null) { - return log.error( + log.error( "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." ); + return process.exit(0); } const splitURL: Array = options.url.split("/"); const storageAccount = new anchor.web3.PublicKey(splitURL[3]); @@ -254,22 +253,14 @@ programCommand("edit-file") connection ); if (!storageAccountType || storageAccountType === null) { - return log.error( + log.error( `Storage account ${storageAccount.toString()} is not a valid Shadow Drive Storage Account.` ); + return process.exit(0); } - let storageAccountOnChain; - if (storageAccountType === "V1") { - storageAccountOnChain = - programClient.account.storageAccount.fetch(storageAccount); - } - if (storageAccountType === "V2") { - storageAccountOnChain = - await programClient.account.storageAccountV2.fetch( - storageAccount - ); - } + let storageAccountOnChain = + await programClient.account.storageAccountV2.fetch(storageAccount); log.debug({ storageAccountOnChain }); @@ -291,8 +282,7 @@ programCommand("edit-file") { name: fileName, file: fileData, - }, - "v2" + } ); txnSpinner.succeed(`File account updated: ${fileName}`); log.info( @@ -300,8 +290,10 @@ programCommand("edit-file") uploadResponse.finalized_location ); log.info("Your updated file is immediately accessible."); + return process.exit(0); } catch (e) { txnSpinner.fail(e.message); + return process.exit(0); } }); @@ -315,7 +307,6 @@ async function handleUpload( const connection = new anchor.web3.Connection(options.rpc, "confirmed"); const drive = await new ShdwDrive(connection, wallet).init(); const userInfo = drive.userInfo; - const [programClient, provider] = getAnchorEnvironment(keypair, connection); const programLogPath = path.join( process.cwd(), `shdw-drive-upload-${Math.round(new Date().getTime() / 100)}.json` @@ -327,10 +318,10 @@ async function handleUpload( : [path.resolve(options.file)]; if (mode === "directory" && !fs.statSync(options.directory).isDirectory()) { - return log.error("Please select a folder of files to upload."); + log.error("Please select a folder of files to upload."); + return process.exit(0); } const fileSpinner = ora("Collecting all files").start(); - let fileData: any = []; let tmpFileData: any = []; filesToRead.forEach((file) => { const fileName = file.substring(file.lastIndexOf("/") + 1); @@ -357,25 +348,18 @@ async function handleUpload( }); fileSpinner.succeed(); - const userInfoAccount = await connection.getAccountInfo(userInfo); + const userInfoAccount = await UserInfo.fetch(connection, userInfo); if (userInfoAccount === null) { - return log.error( + log.error( "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." ); + return process.exit(0); } - let userInfoData = await programClient.account.userInfo.fetch(userInfo); - - log.debug({ userInfoData }); - - let numberOfStorageAccounts = userInfoData.accountCounter - 1; - const accountsSpinner = ora("Fetching all storage accounts").start(); - let [formattedAccounts] = await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts - ); + let rawAccounts = await drive.getStorageAccounts(); + let [formattedAccounts] = await getFormattedStorageAccounts(rawAccounts); formattedAccounts = formattedAccounts.sort( sortByProperty("accountCounterSeed") @@ -404,7 +388,7 @@ async function handleUpload( log.error( "You must pick a storage account to use for your upload." ); - return; + return process.exit(0); } storageAccount = formattedAccounts[pickedAccount.option].pubkey; @@ -428,7 +412,7 @@ async function handleUpload( log.error( `Could not find storage account: ${storageAccount.toString()}` ); - return; + return process.exit(0); } tmpFileData.forEach((file: any) => { @@ -542,6 +526,7 @@ async function handleUpload( progress.stop(); log.debug(results); log.info(`${results.length} files uploaded.`); + return process.exit(0); }); } programCommand("upload-multiple-files") @@ -564,7 +549,6 @@ programCommand("upload-multiple-files") ) .action(async (options, cmd) => { await handleUpload(options, cmd, "directory"); - return; }); programCommand("delete-file") @@ -597,9 +581,10 @@ programCommand("delete-file") connection ); if (!storageAccountType || storageAccountType === null) { - return log.error( + log.error( `Storage account ${storageAccount.toString()} is not a valid Shadow Drive Storage Account.` ); + return process.exit(0); } let storageAccountOnChain: any; if (storageAccountType === "V1") { @@ -622,16 +607,15 @@ programCommand("delete-file") ); deleteResponse = await drive.deleteFile( storageAccount, - options.url, - "v2" + options.url ); - log.info(`File ${options.url} successfully deleted.`); } catch (e) { log.error("Error with request"); log.error(e); + return process.exit(0); } - - return log.info(`File ${options.url} successfully deleted`); + log.info(`File ${options.url} successfully deleted`); + return process.exit(0); }); programCommand("get-storage-account") @@ -645,24 +629,16 @@ programCommand("get-storage-account") const connection = new anchor.web3.Connection(options.rpc, "confirmed"); const drive = await new ShdwDrive(connection, wallet).init(); const userInfo = drive.userInfo; - const [programClient, provider] = getAnchorEnvironment( - keypair, - connection - ); - const userInfoAccount = await connection.getAccountInfo(userInfo); + const userInfoAccount = await UserInfo.fetch(connection, userInfo); if (userInfoAccount === null) { - return log.error( + log.error( "You have not created a storage account yet on Shadow Drive. Please see the 'create-storage-account' command to get started." ); + return process.exit(0); } - // TODO add hanlding for if userInfo is not initialized yet for a keypair - const userInfoData = await programClient.account.userInfo.fetch( - userInfo - ); - const numberOfStorageAccounts = userInfoData.accountCounter - 1; + let rawAccounts = await drive.getStorageAccounts(); let [formattedAccounts] = await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts + rawAccounts ); formattedAccounts = formattedAccounts.sort( @@ -684,7 +660,7 @@ programCommand("get-storage-account") if (typeof pickedAccount.option === "undefined") { log.error("You must pick a storage account to get."); - return; + return process.exit(0); } const storageAccount = formattedAccounts[pickedAccount.option]; @@ -693,7 +669,8 @@ programCommand("get-storage-account") storageAccount.identifier } - ${storageAccount.pubkey?.toString()}:` ); - return log.info(storageAccount); + log.info(storageAccount); + return process.exit(0); }); programCommand("delete-storage-account") @@ -708,23 +685,17 @@ programCommand("delete-storage-account") const drive = await new ShdwDrive(connection, wallet).init(); const userInfo = drive.userInfo; - const [programClient, provider] = getAnchorEnvironment( - keypair, - connection - ); - const userInfoAccount = await connection.getAccountInfo(userInfo); + + const userInfoAccount = await UserInfo.fetch(connection, userInfo); if (userInfoAccount === null) { - return log.error( + log.error( "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." ); + return process.exit(0); } - - let userInfoData = await programClient.account.userInfo.fetch(userInfo); - - let numberOfStorageAccounts = userInfoData.accountCounter - 1; + let rawAccounts = await drive.getStorageAccounts(); let [formattedAccounts] = await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts + rawAccounts ); formattedAccounts = formattedAccounts.sort( sortByProperty("accountCounterSeed") @@ -747,7 +718,7 @@ programCommand("delete-storage-account") if (typeof pickedAccount.option === "undefined") { log.error("You must pick a storage account to add storage to."); - return; + return process.exit(0); } // Get current storage and user funds @@ -757,9 +728,10 @@ programCommand("delete-storage-account") connection ); if (!storageAccountType || storageAccountType === null) { - return log.error( + log.error( `Storage account ${storageAccount.toString()} is not a valid Shadow Drive Storage Account.` ); + return process.exit(0); } log.debug({ storageAccount: storageAccount.toString(), @@ -769,22 +741,21 @@ programCommand("delete-storage-account") "Sending storage account deletion request. Subject to solana traffic conditions (w/ 120s timeout)." ).start(); try { - if (storageAccountType === "V1") { - await drive.deleteStorageAccount(storageAccount, "v1"); - } - - if (storageAccountType === "V2") { - await drive.deleteStorageAccount(storageAccount, "v2"); - } + const deleteStorage = await drive.deleteStorageAccount( + storageAccount + ); + log.info(deleteStorage); } catch (e) { txnSpinner.fail( "Error sending transaction. Please see information below." ); - return log.error(e); + log.error(e.message); + return process.exit(0); } txnSpinner.succeed( `Storage account deletion request successfully submitted for account ${storageAccount.toString()}. You have until the end of the current Solana Epoch to revert this account deletion request. Once the account is fully deleted, you will receive the SOL rent and SHDW staked back in your wallet.` ); + return process.exit(0); }); programCommand("undelete-storage-account") @@ -798,23 +769,17 @@ programCommand("undelete-storage-account") const connection = new anchor.web3.Connection(options.rpc, "confirmed"); const drive = await new ShdwDrive(connection, wallet).init(); const userInfo = drive.userInfo; - const [programClient, provider] = getAnchorEnvironment( - keypair, - connection - ); - const userInfoAccount = await connection.getAccountInfo(userInfo); + const userInfoAccount = await UserInfo.fetch(connection, userInfo); if (userInfoAccount === null) { - return log.error( + log.error( "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." ); + return process.exit(0); } - let userInfoData = await programClient.account.userInfo.fetch(userInfo); - - let numberOfStorageAccounts = userInfoData.accountCounter - 1; + let rawAccounts = await drive.getStorageAccounts(); let [formattedAccounts] = await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts + rawAccounts ); formattedAccounts = formattedAccounts.sort( @@ -841,7 +806,7 @@ programCommand("undelete-storage-account") log.error( "You must pick a storage account to unmark for deletion." ); - return; + return process.exit(0); } // Get current storage and user funds @@ -851,15 +816,11 @@ programCommand("undelete-storage-account") connection ); if (!storageAccountType || storageAccountType === null) { - return log.error( + log.error( `Storage account ${storageAccount.toString()} is not a valid Shadow Drive Storage Account.` ); + return process.exit(0); } - const [stakeAccount] = await anchor.web3.PublicKey.findProgramAddress( - [Buffer.from("stake-account"), storageAccount.toBytes()], - programClient.programId - ); - log.debug({ storageAccount: storageAccount.toString(), }); @@ -868,22 +829,21 @@ programCommand("undelete-storage-account") "Sending storage account undelete request. Subject to solana traffic conditions (w/ 120s timeout)." ).start(); try { - if (storageAccountType === "V1") { - await drive.cancelDeleteStorageAccount(storageAccount, "v1"); - } - - if (storageAccountType === "V2") { - await drive.cancelDeleteStorageAccount(storageAccount, "v2"); - } + const cancelDelete = await drive.cancelDeleteStorageAccount( + storageAccount + ); + log.info(cancelDelete); } catch (e) { txnSpinner.fail( "Error sending transaction. Please see information below." ); - return log.error(e); + log.error(e.message); + return process.exit(0); } txnSpinner.succeed( `Storage account undelete request successfully submitted for account ${storageAccount.toString()}. This account will no longer be deleted.` ); + return process.exit(0); }); programCommand("add-storage") @@ -900,35 +860,25 @@ programCommand("add-storage") const wallet = new anchor.Wallet(keypair); const connection = new anchor.web3.Connection(options.rpc, "confirmed"); const drive = await new ShdwDrive(connection, wallet).init(); - const userInfo = drive.userInfo; - const [programClient, provider] = getAnchorEnvironment( - keypair, - connection - ); let storageInput = options.size; let storageInputAsBytes = humanSizeToBytes(storageInput); if (storageInputAsBytes === false) { log.error( `${options.size} is not a valid input for size. Please use a string like '1KB', '1MB', '1GB'.` ); - return; + return process.exit(0); } log.debug("storageInputAsBytes", storageInputAsBytes); - const userInfoAccount = await connection.getAccountInfo(userInfo); - if (userInfoAccount === null) { - return log.error( - "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." - ); + let rawAccounts; + try { + rawAccounts = await drive.getStorageAccounts(); + } catch (e) { + log.error(e.message); + return process.exit(0); } - - let userInfoData = await programClient.account.userInfo.fetch(userInfo); - - let numberOfStorageAccounts = userInfoData.accountCounter - 1; - let [formattedAccounts] = await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts + rawAccounts ); formattedAccounts = formattedAccounts.sort( @@ -952,7 +902,7 @@ programCommand("add-storage") if (typeof pickedAccount.option === "undefined") { log.error("You must pick a storage account to add storage to."); - return; + return process.exit(0); } // Get current storage and user funds @@ -962,55 +912,29 @@ programCommand("add-storage") connection ); if (!accountType || accountType === null) { - return log.error( + log.error( `Storage account ${storageAccount} is not a valid Shadow Drive Storage Account.` ); + return process.exit(0); } - const [stakeAccount] = await anchor.web3.PublicKey.findProgramAddress( - [Buffer.from("stake-account"), storageAccount.toBytes()], - programClient.programId - ); - const ownerAta = await findAssociatedTokenAddress( - keypair.publicKey, - tokenMint - ); - - log.debug({ - storageAccount: storageAccount.toString(), - stakeAccount: stakeAccount.toString(), - ownerAta: ownerAta.toString(), - }); - const txnSpinner = ora( "Sending add storage request. Subject to solana traffic conditions (w/ 120s timeout)." ).start(); try { - // Mutable V1 add storage - if (accountType === "V1") { - const addStorage = await drive.addStorage( - storageAccount, - storageInput, - "v1" - ); - log.info(addStorage); - } - // Mutable V2 Add Storage - if (accountType === "V2") { - const addStorage = await drive.addStorage( - storageAccount, - storageInput, - "v2" - ); - log.info(addStorage); - } + const addStorage = await drive.addStorage( + storageAccount, + storageInput + ); + log.info(addStorage); } catch (e) { txnSpinner.fail( "Error sending transaction. Please see information below." ); - return log.error(e); + log.error(e.message); + return process.exit(0); } txnSpinner.succeed(`Storage account capacity successfully increased`); - return; + return process.exit(0); }); programCommand("reduce-storage") @@ -1029,7 +953,7 @@ programCommand("reduce-storage") log.error( `${options.size} is not a valid input for size. Please use a string like '1KB', '1MB', '1GB'.` ); - return; + return process.exit(0); } log.debug("storageInputAsBytes", storageInputAsBytes); const keypair = loadWalletKey(options.keypair); @@ -1037,24 +961,18 @@ programCommand("reduce-storage") const connection = new anchor.web3.Connection(options.rpc, "confirmed"); const drive = await new ShdwDrive(connection, wallet).init(); const userInfo = drive.userInfo; - const [programClient, provider] = getAnchorEnvironment( - keypair, - connection - ); - const userInfoAccount = await connection.getAccountInfo(userInfo); + const userInfoAccount = await UserInfo.fetch(connection, userInfo); if (userInfoAccount === null) { - return log.error( + log.error( "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." ); + return process.exit(0); } - let userInfoData = await programClient.account.userInfo.fetch(userInfo); - - let numberOfStorageAccounts = userInfoData.accountCounter - 1; + let rawAccounts = await drive.getStorageAccounts(); let [formattedAccounts] = await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts + rawAccounts ); formattedAccounts = formattedAccounts.sort( @@ -1083,7 +1001,7 @@ programCommand("reduce-storage") log.error( "You must pick a storage account to remove storage from." ); - return; + return process.exit(0); } // Get current storage and user funds @@ -1093,39 +1011,30 @@ programCommand("reduce-storage") connection ); if (!storageAccountType || storageAccountType === null) { - return log.error( + log.error( `Storage account ${storageAccount.toString()} is not a valid Shadow Drive Storage Account.` ); + return process.exit(0); } const txnSpinner = ora( "Sending reduce storage request. Subject to solana traffic conditions (w/ 120s timeout)." ).start(); try { - if (storageAccountType === "V1") { - const reduceStorage = await drive.reduceStorage( - storageAccount, - storageInput, - "v1" - ); - log.info(reduceStorage); - } - if (storageAccountType === "V2") { - const reduceStorage = await drive.reduceStorage( - storageAccount, - storageInput, - "v2" - ); - log.info(reduceStorage); - } + const reduceStorage = await drive.reduceStorage( + storageAccount, + storageInput + ); + log.info(reduceStorage); } catch (e) { txnSpinner.fail( "Error sending transaction. Please see information below." ); - return log.error(e); + log.error(e.message); + return process.exit(0); } txnSpinner.succeed(`Storage account capacity successfully reduced.`); - return; + return process.exit(0); }); programCommand("make-storage-account-immutable") @@ -1141,24 +1050,18 @@ programCommand("make-storage-account-immutable") const drive = await new ShdwDrive(connection, wallet).init(); const userInfo = drive.userInfo; - const storageConfig = drive.storageConfigPDA; - const [programClient, provider] = getAnchorEnvironment( - keypair, - connection - ); - const userInfoAccount = await connection.getAccountInfo(userInfo); + + const userInfoAccount = await UserInfo.fetch(connection, userInfo); if (userInfoAccount === null) { - return log.error( + log.error( "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." ); + return process.exit(0); } - let userInfoData = await programClient.account.userInfo.fetch(userInfo); - - let numberOfStorageAccounts = userInfoData.accountCounter - 1; + let rawAccounts = await drive.getStorageAccounts(); let [formattedAccounts] = await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts + rawAccounts ); formattedAccounts = formattedAccounts.sort( sortByProperty("accountCounterSeed") @@ -1181,7 +1084,7 @@ programCommand("make-storage-account-immutable") if (typeof pickedAccount.option === "undefined") { log.error("You must pick a storage account to make immutable."); - return; + return process.exit(0); } // Get current storage and user funds @@ -1191,40 +1094,30 @@ programCommand("make-storage-account-immutable") connection ); if (!storageAccountType || storageAccountType === null) { - return log.error( + log.error( `Storage account ${storageAccount.toString()} is not a valid Shadow Drive Storage Account.` ); + return process.exit(0); } const txnSpinner = ora( "Sending make account immutable request. Subject to solana traffic conditions (w/ 120s timeout)." ).start(); try { - if (storageAccountType === "V1") { - const makeImmutable = await drive.makeStorageImmutable( - storageAccount, - "v1" - ); - - log.debug(makeImmutable); - } - - if (storageAccountType === "V2") { - const makeImmutable = await drive.makeStorageImmutable( - storageAccount, - "v2" - ); - - log.debug(makeImmutable); - } + const makeImmutable = await drive.makeStorageImmutable( + storageAccount + ); + log.info(makeImmutable); } catch (e) { txnSpinner.fail( "Error sending transaction. Please see information below." ); - return log.error(e); + log.error(e.message); + return process.exit(0); } txnSpinner.succeed( `Storage account ${storageAccount.toString()} has been marked as immutable. Files can no longer be deleted from this storage account.` ); + return process.exit(0); }); programCommand("claim-stake") @@ -1250,26 +1143,20 @@ programCommand("claim-stake") let unstakeEpochperiod = parseInt( programConstants["UNSTAKE_EPOCH_PERIOD"] ); - const userInfoAccount = await connection.getAccountInfo(userInfo); + const userInfoAccount = await UserInfo.fetch(connection, userInfo); if (userInfoAccount === null) { - return log.error( + log.error( "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." ); + return process.exit(0); } - let userInfoData = await programClient.account.userInfo.fetch(userInfo); - - let numberOfStorageAccounts = userInfoData.accountCounter - 1; - const accountFetchSpinner = ora( "Fetching all storage accounts and claimable stake" ).start(); + let rawAccounts = await drive.getStorageAccounts(); let [formattedAccounts, accountsToFetch] = - await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts - ); - + await getFormattedStorageAccounts(rawAccounts); formattedAccounts = await Promise.all( formattedAccounts.map(async (account: any, idx: number) => { const accountKey = new anchor.web3.PublicKey( @@ -1292,7 +1179,7 @@ programCommand("claim-stake") programClient.programId ); } catch (e) { - return; + return process.exit(0); } let unstakeInfoData; let unstakeTokenAccount; @@ -1303,14 +1190,14 @@ programCommand("claim-stake") unstakeInfo ); } catch (e) { - console.log(e); + log.debug(e.message); return null; } try { unstakeTokenAccountBalance = await connection.getTokenAccountBalance(unstakeAccount); } catch (e) { - console.log(e); + log.debug(e.message); return null; } return { @@ -1332,15 +1219,16 @@ programCommand("claim-stake") accountFetchSpinner.succeed(); if (formattedAccounts.length === 0) { - return log.error( + log.error( "You don't have any storage accounts with claimable stake." ); + return process.exit(0); } const pickedAccount = await prompts({ type: "select", name: "option", - message: "Which storage account do you want to reduce storage on?", + message: "Which storage account do you want to claim stake for?", warn: "Account not eligible for stake claim yet. Please wait until the epoch specified.", choices: formattedAccounts.map((acc: any) => { return { @@ -1355,9 +1243,8 @@ programCommand("claim-stake") }); if (typeof pickedAccount.option === "undefined") { - return log.error( - "You must pick a storage account to reduce storage on." - ); + log.error("You must pick a storage account to reduce storage on."); + return process.exit(0); } // Get current storage and user funds const storageAccount = formattedAccounts[pickedAccount.option].pubkey; @@ -1367,149 +1254,28 @@ programCommand("claim-stake") connection ); if (!storageAccountType || storageAccountType === null) { - return log.error( + log.error( `Storage account ${storageAccount.toString()} is not a valid Shadow Drive Storage Account.` ); + return process.exit(0); } const txnSpinner = ora( "Sending claim stake transaction request. Subject to solana traffic conditions (w/ 120s timeout)." ).start(); try { - if (storageAccountType === "V1") { - const claimStake = await drive.claimStake(storageAccount, "v1"); - log.info(claimStake); - } - if (storageAccountType === "V2") { - const claimStake = await drive.claimStake(storageAccount, "v2"); - log.info(claimStake); - } + const claimStake = await drive.claimStake(storageAccount); + log.info(claimStake); txnSpinner.succeed( `You have claimed ${formattedAccount.unstakeTokenAccountBalance.value.uiAmount} $SHDW from your storage account ${storageAccount}.` ); - return; + return process.exit(0); } catch (e) { txnSpinner.fail( "Error sending transaction. See below for details:" ); - console.log(e); - return; - } - return; - }); - -programCommand("redeem-file-account-rent") - .requiredOption( - "-kp, --keypair ", - "Path to wallet that owns the storage account you want to claim available stake from." - ) - .action(async (options, cmd) => { - const keypair = loadWalletKey(options.keypair); - const wallet = new anchor.Wallet(keypair); - const connection = new anchor.web3.Connection(options.rpc, "confirmed"); - - const drive = await new ShdwDrive(connection, wallet).init(); - - const userInfo = drive.userInfo; - const [programClient, provider] = getAnchorEnvironment( - keypair, - connection - ); - const userInfoAccount = await connection.getAccountInfo(userInfo); - if (userInfoAccount === null) { - return log.error( - "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." - ); - } - - let userInfoData = await programClient.account.userInfo.fetch(userInfo); - - let numberOfStorageAccounts = userInfoData.accountCounter - 1; - - let [formattedAccounts] = await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts - ); - - formattedAccounts = formattedAccounts.sort( - sortByProperty("accountCounterSeed") - ); - - const pickedAccount = await prompts({ - type: "select", - name: "option", - message: - "Which storage account do you want to redeem all file rent from?", - choices: formattedAccounts.map((acc: any) => { - return { - title: `${acc.identifier} - ${acc.pubkey.toString()} - ${ - acc.storageAvailable - } remaining`, - }; - }), - }); - - if (typeof pickedAccount.option === "undefined") { - log.error( - "You must pick a storage account to redeem file rent from." - ); - return; - } - - // Get current storage and user funds - const storageAccount = formattedAccounts[pickedAccount.option].pubkey; - const agrees = await prompts({ - type: "confirm", - name: "confirm", - message: `Warning: this will delete all on-chain file accounts associated with the storage account ${storageAccount.toString()} in order to reclaim the SOL rent. Your data/files will not be removed from Shadow Drive.`, - initial: false, - }); - if (!agrees.confirm) { - return log.error("You must confirm before moving forward."); - } - const onchainStorageAccountInfo = - await programClient.account.storageAccount.fetch(storageAccount); - const numberOfFiles = onchainStorageAccountInfo.initCounter; - let filePubkeys: PublicKey[] = []; - for (let i = 0; i < numberOfFiles; i++) { - const fileSeed = new anchor.BN(i); - let [file] = anchor.web3.PublicKey.findProgramAddressSync( - [ - storageAccount.toBytes(), - fileSeed.toTwos(64).toArrayLike(Buffer, "le", 4), - ], - programClient.programId - ); - let fileAccountInfo = await connection.getAccountInfo(file); - if (fileAccountInfo) { - filePubkeys.push(file); - } + log.error(e.message); + return process.exit(0); } - const progress = new cliProgress.SingleBar({ - format: "Progress | {bar} | {percentage}% || {value}/{total} file accounts closed", - barCompleteChar: "\u2588", - barIncompleteChar: "\u2591", - hideCursor: true, - }); - progress.start(filePubkeys.length, 0); - await Promise.all( - filePubkeys.map(async (pubkey) => { - try { - const redeem = await drive.redeemRent( - storageAccount, - pubkey - ); - log.info(redeem); - progress.increment(1); - } catch (e) { - log.error("Error with transaction, see below for details"); - log.error(e); - } - }) - ); - progress.stop(); - log.info( - `Successfully reclaimed rent from all file accounts in storage account ${storageAccount.toString()}` - ); }); programCommand("refresh-stake") .requiredOption( @@ -1524,24 +1290,16 @@ programCommand("refresh-stake") const drive = await new ShdwDrive(connection, wallet).init(); const userInfo = drive.userInfo; - const [programClient, provider] = getAnchorEnvironment( - keypair, - connection - ); - const userInfoAccount = await connection.getAccountInfo(userInfo); + const userInfoAccount = await UserInfo.fetch(connection, userInfo); if (userInfoAccount === null) { - return log.error( + log.error( "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." ); + return process.exit(0); } - - let userInfoData = await programClient.account.userInfo.fetch(userInfo); - - let numberOfStorageAccounts = userInfoData.accountCounter - 1; - + let rawAccounts = await drive.getStorageAccounts(); let [formattedAccounts] = await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts + rawAccounts ); formattedAccounts = formattedAccounts.sort( @@ -1563,7 +1321,7 @@ programCommand("refresh-stake") if (typeof pickedAccount.option === "undefined") { log.error("You must pick a storage account to refresh."); - return; + return process.exit(0); } const storageAccount = formattedAccounts[pickedAccount.option].pubkey; @@ -1573,38 +1331,28 @@ programCommand("refresh-stake") connection ); if (!storageAccountType || storageAccountType === null) { - return log.error( + log.error( `Storage account ${storageAccount.toString()} is not a valid Shadow Drive Storage Account.` ); + return process.exit(0); } log.info(`Picked account ${storageAccount}`); const txnSpinner = ora( "Sending refresh stake transaction request. Subject to solana traffic conditions (w/ 120s timeout)." ).start(); try { - if (storageAccountType === "V1") { - const claimStake = await drive.refreshStake( - storageAccount, - "v1" - ); - log.info(claimStake); - } - if (storageAccountType === "V2") { - const claimStake = await drive.refreshStake( - storageAccount, - "v2" - ); - log.info(claimStake); - } + const refreshStake = await drive.refreshStake(storageAccount); + log.info(refreshStake); txnSpinner.succeed( `You have refreshed stake for storage account ${storageAccount}.` ); - return; - } catch (e) { + return process.exit(0); + } catch (e: any) { txnSpinner.fail( "Error sending transaction. See below for details:" ); - return; + log.error(e.message); + return process.exit(0); } }); programCommand("top-up") @@ -1624,24 +1372,17 @@ programCommand("top-up") const drive = await new ShdwDrive(connection, wallet).init(); const userInfo = drive.userInfo; - const [programClient, provider] = getAnchorEnvironment( - keypair, - connection - ); - const userInfoAccount = await connection.getAccountInfo(userInfo); + const userInfoAccount = await UserInfo.fetch(connection, userInfo); if (userInfoAccount === null) { - return log.error( + log.error( "You have not created a storage account on Shadow Drive yet. Please see the 'create-storage-account' command to get started." ); + return process.exit(0); } - let userInfoData = await programClient.account.userInfo.fetch(userInfo); - - let numberOfStorageAccounts = userInfoData.accountCounter - 1; - + let rawAccounts = await drive.getStorageAccounts(); let [formattedAccounts] = await getFormattedStorageAccounts( - keypair.publicKey, - numberOfStorageAccounts + rawAccounts ); formattedAccounts = formattedAccounts.sort( @@ -1663,7 +1404,7 @@ programCommand("top-up") if (typeof pickedAccount.option === "undefined") { log.error("You must pick a storage account to top up."); - return; + return process.exit(0); } const storageAccount = formattedAccounts[pickedAccount.option].pubkey; @@ -1673,29 +1414,31 @@ programCommand("top-up") connection ); if (!storageAccountType || storageAccountType === null) { - return log.error( + log.error( `Storage account ${storageAccount.toString()} is not a valid Shadow Drive Storage Account.` ); + return process.exit(0); } log.info(`Picked account ${storageAccount}`); const txnSpinner = ora( "Sending top up transaction request. Subject to solana traffic conditions (w/ 120s timeout)." ).start(); try { - const claimStake = await drive.topUp( + const topUp = await drive.topUp( storageAccount, options.amount * 10 ** 9 ); - log.info(claimStake); + log.info(topUp); txnSpinner.succeed( `You have added stake for storage account ${storageAccount}.` ); - return; + return process.exit(0); } catch (e) { txnSpinner.fail( "Error sending transaction. See below for details:" ); - return; + log.error(e.message); + return process.exit(0); } }); function programCommand(name: string) { @@ -1724,4 +1467,4 @@ function setLogLevel(value: any, prev: any) { log.setLevel(value); } -program.parse(process.argv); +program.parseAsync(process.argv); diff --git a/tsconfig.json b/tsconfig.json index 77d9d63..11210d4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,28 +1,28 @@ { - "compilerOptions": { - "target": "es2017", - "module": "commonjs", - "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], - "skipLibCheck": true, - "sourceMap": true, - "outDir": "./dist", - "moduleResolution": "node", - "removeComments": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "noImplicitThis": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "resolveJsonModule": true, - "baseUrl": "." - }, - "exclude": ["node_modules"], - "include": ["./src/**/*.ts"] + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], + "skipLibCheck": true, + "sourceMap": true, + "outDir": "./dist", + "moduleResolution": "node", + "removeComments": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": false, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + "baseUrl": "." + }, + "exclude": ["node_modules"], + "include": ["./src/**/*.ts", "./package.json"] } diff --git a/yarn.lock b/yarn.lock index d989cb7..ef0606d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -56,6 +56,14 @@ bn.js "^5.1.2" buffer-layout "^1.2.0" +"@coral-xyz/borsh@^0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.28.0.tgz#fa368a2f2475bbf6f828f4657f40a52102e02b6d" + integrity sha512-/u1VTzw7XooK7rqeD7JLUSwOyRSesPUk0U37BV9zK0axJc1q0nRbKFGFLYCQ16OtdOJTTwGfGp11Lx9B45bRCQ== + dependencies: + bn.js "^5.1.2" + buffer-layout "^1.2.0" + "@ethersproject/bytes@^5.6.0": version "5.6.1" resolved "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.6.1.tgz" @@ -89,6 +97,18 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@juanelas/base64@^1.0.1": version "1.0.4" resolved "https://registry.npmjs.org/@juanelas/base64/-/base64-1.0.4.tgz" @@ -127,13 +147,20 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@shadow-drive/sdk@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@shadow-drive/sdk/-/sdk-4.0.2.tgz#cd01aa8c2428b0e2bc0042fcfac26ba96742a4f3" - integrity sha512-XbCk/p1pYkPwSWiasJVOfJbSdjH+j2QI7xu7zTIft3Mv9e0OAE4/B6pcvfkxH+lV9jLdaNNz/S7Tiijqg3F5Yw== +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@shadow-drive/sdk@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@shadow-drive/sdk/-/sdk-5.0.0.tgz#a550cf4f125aabc07eba1675dcdd73dc809eaf6b" + integrity sha512-IS/h7j9e3lAX5Irg8GjyfcjnsvOKhh0bSFjySVM6gIHV7bi2OQ85bMXBukw/bbRdFwV2CHF/nTwpuTttERoR/A== dependencies: "@coral-xyz/anchor" "^0.27.0" + "@coral-xyz/borsh" "^0.28.0" "@solana/spl-token" "^0.2.0" + "@types/bn.js" "^5.1.1" cross-fetch "^3.1.5" form-data "^4.0.0" node-fetch "2" @@ -225,6 +252,13 @@ rpc-websockets "^7.5.1" superstruct "^0.14.2" +"@types/bn.js@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" + integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== + dependencies: + "@types/node" "*" + "@types/cli-progress@^3.11.0": version "3.11.0" resolved "https://registry.yarnpkg.com/@types/cli-progress/-/cli-progress-3.11.0.tgz#ec79df99b26757c3d1c7170af8422e0fc95eef7e" @@ -341,6 +375,11 @@ ansi-regex@^5.0.1: resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" @@ -348,6 +387,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + aproba@^1.0.3: version "1.2.0" resolved "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" @@ -383,6 +427,11 @@ axios@^0.21.1: dependencies: follow-redirects "^1.14.0" +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + base-x@^3.0.2: version "3.0.9" resolved "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz" @@ -464,6 +513,13 @@ bottleneck@^2.19.5: resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" @@ -606,16 +662,16 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-11.0.0.tgz#43e19c25dbedc8256203538e8d7e9346877a6f67" + integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ== + commander@^2.20.3: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^9.2.0: - version "9.2.0" - resolved "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz" - integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w== - console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" @@ -633,7 +689,7 @@ cross-fetch@^3.1.4, cross-fetch@^3.1.5: dependencies: node-fetch "2.6.7" -cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -730,6 +786,11 @@ duplexer@~0.1.1: resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + elliptic@^6.5.4: version "6.5.4" resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz" @@ -748,6 +809,11 @@ emoji-regex@^8.0.0: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" @@ -887,6 +953,14 @@ follow-redirects@^1.14.0: resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz" integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + form-data@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz" @@ -974,6 +1048,17 @@ glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" +glob@^10.2.5: + version "10.3.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.3.tgz#8360a4ffdd6ed90df84aa8d52f21f452e86a123b" + integrity sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.0.3" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + globby@^11.0.4: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" @@ -1139,6 +1224,15 @@ isomorphic-ws@^4.0.1: resolved "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +jackspeak@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.0.tgz#aa228a94de830f31d4e4f0184427ce91c4ff1493" + integrity sha512-uKmsITSsF4rUWQHzqaRUuyAir3fZfW3f202Ee34lz/gZCi970CPZwyQXLGNgWJvvZbvFyzeyGq0+4fcG/mBKZg== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jayson@^3.4.4: version "3.6.6" resolved "https://registry.npmjs.org/jayson/-/jayson-3.6.6.tgz" @@ -1250,6 +1344,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +"lru-cache@^9.1.1 || ^10.0.0": + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== + map-stream@~0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz" @@ -1305,11 +1404,23 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= +minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: version "1.2.6" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974" + integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg== + mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" @@ -1465,6 +1576,14 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" @@ -1643,6 +1762,13 @@ reusify@^1.0.4: resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.1.tgz#0881323ab94ad45fec7c0221f27ea1a142f3f0d0" + integrity sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg== + dependencies: + glob "^10.2.5" + rpc-websockets@^7.4.2: version "7.4.18" resolved "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.4.18.tgz" @@ -1736,6 +1862,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" @@ -1807,6 +1938,15 @@ stream-meter@^1.0.4: dependencies: readable-stream "^2.1.4" +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" @@ -1825,6 +1965,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -1839,6 +1988,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" @@ -1853,6 +2009,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" @@ -2044,6 +2207,15 @@ word-wrap@~1.2.3: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" @@ -2053,6 +2225,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"