From 4efab9049981beec93a4aa57a6cc91db67c0f59f Mon Sep 17 00:00:00 2001 From: Olexandr Berezan <116814585+prxgr4mm3r@users.noreply.github.com> Date: Mon, 18 Mar 2024 12:24:10 +0200 Subject: [PATCH] refactor: ensure checks and utility functions reorg Co-authored-by: Igor Papandinas --- src/commands/clear/index.ts | 7 ++- src/commands/contract/compile.ts | 34 ++++++-------- src/commands/contract/deploy.ts | 49 +++++++------------ src/commands/contract/explain.ts | 28 ++--------- src/commands/contract/new.ts | 4 +- src/commands/contract/test.ts | 48 ++++++------------- src/commands/contract/verify.ts | 38 +++++++-------- src/commands/generate/tests.ts | 42 ++++++----------- src/commands/generate/types.ts | 28 ++--------- src/lib/checks.ts | 80 ++++++++++++++++++++++++++++++++ src/lib/config.ts | 38 ++++++++++++++- src/lib/contract.ts | 4 +- src/lib/contractCall.ts | 44 +++++++----------- src/lib/tasks.ts | 80 -------------------------------- src/lib/templates.ts | 63 +++++++++++++++++++++++++ 15 files changed, 290 insertions(+), 297 deletions(-) create mode 100644 src/lib/checks.ts diff --git a/src/commands/clear/index.ts b/src/commands/clear/index.ts index ec90d667..67b15d2e 100644 --- a/src/commands/clear/index.ts +++ b/src/commands/clear/index.ts @@ -1,8 +1,9 @@ import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ConfigError, FileError } from "../../lib/errors.js"; +import { FileError } from "../../lib/errors.js"; import fs from "fs-extra"; import path from "node:path"; import { Args, Flags } from "@oclif/core"; +import { ensureContractNameOrAllFlagIsSet } from "../../lib/checks.js"; interface Folder { name: string, @@ -44,9 +45,7 @@ export default class Clear extends SwankyCommand { const { flags, args } = await this.parse(Clear); - if (args.contractName === undefined && !flags.all) { - throw new ConfigError("Specify a contract name or use the --all flag to delete all artifacts."); - } + ensureContractNameOrAllFlagIsSet(args, flags); const workDirectory = process.cwd(); const foldersToDelete: Folder[] = flags.all ? diff --git a/src/commands/contract/compile.ts b/src/commands/contract/compile.ts index 93a00538..967f5982 100644 --- a/src/commands/contract/compile.ts +++ b/src/commands/contract/compile.ts @@ -1,12 +1,17 @@ import { Args, Flags } from "@oclif/core"; -import path from "node:path"; import { spawn } from "node:child_process"; -import { pathExists } from "fs-extra/esm"; import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ensureCargoContractVersionCompatibility, extractCargoContractVersion, Spinner, storeArtifacts, configName, getSwankyConfig } from "../../lib/index.js"; -import { ConfigError, InputError, ProcessError } from "../../lib/errors.js"; +import { + extractCargoContractVersion, + Spinner, + storeArtifacts, + getSwankyConfig, + findContractRecord, +} from "../../lib/index.js"; +import { InputError, ProcessError } from "../../lib/errors.js"; import { BuildMode, SwankyConfig } from "../../index.js"; import { ConfigBuilder } from "../../lib/config-builder.js"; +import { ensureContractNameOrAllFlagIsSet, ensureContractPathExists, ensureCargoContractVersionCompatibility, } from "../../lib/checks.js"; export class CompileContract extends SwankyCommand { static description = "Compile the smart contract(s) in your contracts directory"; @@ -44,9 +49,7 @@ export class CompileContract extends SwankyCommand { const localConfig = getSwankyConfig("local") as SwankyConfig; - if (args.contractName === undefined && !flags.all) { - throw new InputError("No contracts were selected to compile", { winston: { stack: true } }); - } + ensureContractNameOrAllFlagIsSet(args, flags); const contractNames = flags.all ? Object.keys(this.swankyConfig.contracts) @@ -55,17 +58,10 @@ export class CompileContract extends SwankyCommand { for (const contractName of contractNames) { this.logger.info(`Started compiling contract [${contractName}]`); - const contractInfo = this.swankyConfig.contracts[contractName]; - if (!contractInfo) { - throw new ConfigError( - `Cannot find contract info for ${contractName} contract in "${configName()}"` - ); - } - const contractPath = path.resolve("contracts", contractInfo.name); - this.logger.info(`"Looking for contract ${contractInfo.name} in path: [${contractPath}]`); - if (!(await pathExists(contractPath))) { - throw new InputError(`Contract folder not found at expected path`); - } + + const contractRecord = findContractRecord(this.swankyConfig, contractName); + + await ensureContractPathExists(contractName); let buildMode = BuildMode.Debug; const compilationResult = await spinner.runCommand( @@ -130,7 +126,7 @@ export class CompileContract extends SwankyCommand { const artifactsPath = compilationResult as string; await spinner.runCommand(async () => { - return storeArtifacts(artifactsPath, contractInfo.name, contractInfo.moduleName); + return storeArtifacts(artifactsPath, contractRecord.name, contractRecord.moduleName); }, "Moving artifacts"); await this.spinner.runCommand(async () => { diff --git a/src/commands/contract/deploy.ts b/src/commands/contract/deploy.ts index 44d3cd8d..7a2e32df 100644 --- a/src/commands/contract/deploy.ts +++ b/src/commands/contract/deploy.ts @@ -1,13 +1,22 @@ import { Args, Flags } from "@oclif/core"; import { cryptoWaitReady } from "@polkadot/util-crypto/crypto"; -import { AbiType, ChainAccount, ChainApi, decrypt, resolveNetworkUrl, ensureAccountIsSet, configName, getSwankyConfig } from "../../lib/index.js"; +import { + AbiType, + ChainAccount, + ChainApi, + decrypt, + resolveNetworkUrl, + ensureAccountIsSet, + getSwankyConfig, + findContractRecord, +} from "../../lib/index.js"; import { BuildMode, Encrypted, SwankyConfig } from "../../types/index.js"; import inquirer from "inquirer"; import chalk from "chalk"; -import { Contract } from "../../lib/contract.js"; import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ApiError, ConfigError, FileError, InputError, ProcessError } from "../../lib/errors.js"; +import { ApiError, ProcessError } from "../../lib/errors.js"; import { ConfigBuilder } from "../../lib/config-builder.js"; +import { contractFromRecord, ensureArtifactsExist, ensureDevAccountNotInProduction } from "../../lib/checks.js"; export class DeployContract extends SwankyCommand { static description = "Deploy contract to a running node"; @@ -47,28 +56,12 @@ export class DeployContract extends SwankyCommand { const { args, flags } = await this.parse(DeployContract); const localConfig = getSwankyConfig("local") as SwankyConfig; - const contractRecord = localConfig.contracts[args.contractName]; - if (!contractRecord) { - throw new ConfigError( - `Cannot find a contract named ${args.contractName} in "${configName()}"` - ); - } - const contract = new Contract(contractRecord); + const contractRecord = findContractRecord(localConfig, args.contractName); - if (!(await contract.pathExists())) { - throw new FileError( - `Path to contract ${args.contractName} does not exist: ${contract.contractPath}` - ); - } + const contract = (await contractFromRecord(contractRecord)); - const artifactsCheck = await contract.artifactsExist(); - - if (!artifactsCheck.result) { - throw new FileError( - `No artifact file found at path: ${artifactsCheck.missingPaths.toString()}` - ); - } + await ensureArtifactsExist(contract); if (contract.buildMode === undefined) { throw new ProcessError( @@ -99,19 +92,11 @@ export class DeployContract extends SwankyCommand { ensureAccountIsSet(flags.account, this.swankyConfig); - const accountAlias = flags.account ?? this.swankyConfig.defaultAccount; - - if (accountAlias === null) { - throw new InputError(`An account is required to deploy ${args.contractName}`); - } + const accountAlias = (flags.account ?? this.swankyConfig.defaultAccount)!; const accountData = this.findAccountByAlias(accountAlias); - if (accountData.isDev && flags.network !== "local") { - throw new ConfigError( - `Account ${accountAlias} is a DEV account and can only be used with local network` - ); - } + ensureDevAccountNotInProduction(accountData, flags.network); const mnemonic = accountData.isDev ? (accountData.mnemonic as string) diff --git a/src/commands/contract/explain.ts b/src/commands/contract/explain.ts index 3efba84e..03c6d614 100644 --- a/src/commands/contract/explain.ts +++ b/src/commands/contract/explain.ts @@ -1,8 +1,7 @@ import { SwankyCommand } from "../../lib/swankyCommand.js"; import { Args } from "@oclif/core"; -import { Contract } from "../../lib/contract.js"; -import { ConfigError, FileError } from "../../lib/errors.js"; -import { configName } from "../../lib/index.js"; +import { findContractRecord } from "../../lib/index.js"; +import { contractFromRecord, ensureArtifactsExist } from "../../lib/checks.js"; export class ExplainContract extends SwankyCommand { static description = "Explain contract messages based on the contracts' metadata"; @@ -18,28 +17,11 @@ export class ExplainContract extends SwankyCommand { async run(): Promise { const { args } = await this.parse(ExplainContract); - const contractRecord = this.swankyConfig.contracts[args.contractName]; - if (!contractRecord) { - throw new ConfigError( - `Cannot find a contract named ${args.contractName} in "${configName()}"` - ); - } + const contractRecord = findContractRecord(this.swankyConfig, args.contractName); - const contract = new Contract(contractRecord); + const contract = (await contractFromRecord(contractRecord)); - if (!(await contract.pathExists())) { - throw new FileError( - `Path to contract ${args.contractName} does not exist: ${contract.contractPath}` - ); - } - - const artifactsCheck = await contract.artifactsExist(); - - if (!artifactsCheck.result) { - throw new FileError( - `No artifact file found at path: ${artifactsCheck.missingPaths.toString()}` - ); - } + await ensureArtifactsExist(contract); await contract.printInfo(); } diff --git a/src/commands/contract/new.ts b/src/commands/contract/new.ts index b74f01d3..27b3281f 100644 --- a/src/commands/contract/new.ts +++ b/src/commands/contract/new.ts @@ -7,7 +7,7 @@ import { processTemplates, getTemplates, prepareTestFiles, - getSwankyConfig, + getSwankyConfig, configName, } from "../../lib/index.js"; import { email, name, pickTemplate } from "../../lib/prompts.js"; import { kebabCase, pascalCase, snakeCase } from "change-case"; @@ -45,7 +45,7 @@ export class NewContract extends SwankyCommand { if (this.swankyConfig.contracts[args.contractName]) { throw new InputError( - `Contract with a name '${args.contractName}' already exists in swanky.config` + `Contract with a name '${args.contractName}' already exists in ${configName()}` ); } diff --git a/src/commands/contract/test.ts b/src/commands/contract/test.ts index 9fd1e804..eb61895b 100644 --- a/src/commands/contract/test.ts +++ b/src/commands/contract/test.ts @@ -6,9 +6,15 @@ import Mocha from "mocha"; import { emptyDir, pathExistsSync } from "fs-extra/esm"; import { Contract } from "../../lib/contract.js"; import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ConfigError, FileError, InputError, ProcessError, TestError } from "../../lib/errors.js"; +import { FileError, ProcessError, TestError } from "../../lib/errors.js"; import { spawn } from "node:child_process"; -import { configName, Spinner } from "../../lib/index.js"; +import { findContractRecord, Spinner } from "../../lib/index.js"; +import { + contractFromRecord, + ensureArtifactsExist, + ensureContractNameOrAllFlagIsSet, + ensureTypedContractExists, +} from "../../lib/checks.js"; declare global { var contractTypesPath: string; // eslint-disable-line no-var @@ -40,9 +46,7 @@ export class TestContract extends SwankyCommand { async run(): Promise { const { args, flags } = await this.parse(TestContract); - if (args.contractName === undefined && !flags.all) { - throw new InputError("No contracts were selected to compile"); - } + ensureContractNameOrAllFlagIsSet(args, flags); const contractNames = flags.all ? Object.keys(this.swankyConfig.contracts) @@ -51,20 +55,9 @@ export class TestContract extends SwankyCommand { const spinner = new Spinner(); for (const contractName of contractNames) { - const contractRecord = this.swankyConfig.contracts[contractName]; - if (!contractRecord) { - throw new ConfigError( - `Cannot find a contract named ${args.contractName} in "${configName()}"` - ); - } + const contractRecord = findContractRecord(this.swankyConfig, contractName); - const contract = new Contract(contractRecord); - - if (!(await contract.pathExists())) { - throw new FileError( - `Path to contract ${args.contractName} does not exist: ${contract.contractPath}` - ); - } + const contract = (await contractFromRecord(contractRecord)); console.log(`Testing contract: ${contractName}`); @@ -125,24 +118,9 @@ export class TestContract extends SwankyCommand { throw new FileError(`Test directory does not exist: ${testDir}`); } - const artifactsCheck = await contract.artifactsExist(); - - if (!artifactsCheck.result) { - throw new FileError( - `No artifact file found at path: ${artifactsCheck.missingPaths.toString()}` - ); - } - - const artifactPath = path.resolve("typedContracts", `${contract.name}`); - const typedContractCheck = await contract.typedContractExists(contract.name); - - this.log(`artifactPath: ${artifactPath}`); + await ensureArtifactsExist(contract); - if (!typedContractCheck.result) { - throw new FileError( - `No typed contract found at path: ${typedContractCheck.missingPaths.toString()}` - ); - } + await ensureTypedContractExists(contract); const reportDir = path.resolve(testDir, "testReports"); await emptyDir(reportDir); diff --git a/src/commands/contract/verify.ts b/src/commands/contract/verify.ts index a0d4b85f..1add044b 100644 --- a/src/commands/contract/verify.ts +++ b/src/commands/contract/verify.ts @@ -1,12 +1,16 @@ import { Args, Flags } from "@oclif/core"; -import path from "node:path"; -import { ensureCargoContractVersionCompatibility, extractCargoContractVersion, getSwankyConfig, Spinner } from "../../lib/index.js"; -import { pathExists } from "fs-extra/esm"; +import { + extractCargoContractVersion, + findContractRecord, + getSwankyConfig, + Spinner, +} from "../../lib/index.js"; import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ConfigError, InputError, ProcessError } from "../../lib/errors.js"; +import { InputError, ProcessError } from "../../lib/errors.js"; import { spawn } from "node:child_process"; import { ConfigBuilder } from "../../lib/config-builder.js"; import { BuildData, SwankyConfig } from "../../index.js"; +import { ensureContractNameOrAllFlagIsSet, ensureContractPathExists, ensureCargoContractVersionCompatibility } from "../../lib/checks.js"; export class VerifyContract extends SwankyCommand { static description = "Verify the smart contract(s) in your contracts directory"; @@ -43,9 +47,7 @@ export class VerifyContract extends SwankyCommand { "4.0.0-alpha", ]); - if (args.contractName === undefined && !flags.all) { - throw new InputError("No contracts were selected to verify", { winston: { stack: true } }); - } + ensureContractNameOrAllFlagIsSet(args, flags); const contractNames = flags.all ? Object.keys(this.swankyConfig.contracts) @@ -55,26 +57,20 @@ export class VerifyContract extends SwankyCommand { for (const contractName of contractNames) { this.logger.info(`Started compiling contract [${contractName}]`); - const contractInfo = this.swankyConfig.contracts[contractName]; - if (!contractInfo) { - throw new ConfigError( - `Cannot find contract info for ${contractName} contract in swanky.config.json` - ); - } - const contractPath = path.resolve("contracts", contractInfo.name); - this.logger.info(`"Looking for contract ${contractInfo.name} in path: [${contractPath}]`); - if (!(await pathExists(contractPath))) { - throw new InputError(`Contract folder not found at expected path`); - } - if(!contractInfo.build) { + const contractRecord = findContractRecord(this.swankyConfig, contractName); + + await ensureContractPathExists(contractName); + + + if(!contractRecord.build) { throw new InputError(`Contract ${contractName} is not compiled. Please compile it first`); } await spinner.runCommand( async () => { return new Promise((resolve, reject) => { - if(contractInfo.build!.isVerified) { + if(contractRecord.build!.isVerified) { this.logger.info(`Contract ${contractName} is already verified`); resolve(true); } @@ -119,7 +115,7 @@ export class VerifyContract extends SwankyCommand { await this.spinner.runCommand(async () => { const buildData = { - ...contractInfo.build, + ...contractRecord.build, isVerified: true } as BuildData; diff --git a/src/commands/generate/tests.ts b/src/commands/generate/tests.ts index a6fa3b6d..0c9a7b04 100644 --- a/src/commands/generate/tests.ts +++ b/src/commands/generate/tests.ts @@ -1,13 +1,13 @@ import { Args, Flags } from "@oclif/core"; -import { getTemplates, prepareTestFiles, processTemplates } from "../../lib/index.js"; -import { Contract } from "../../lib/contract.js"; +import { findContractRecord, getTemplates, prepareTestFiles, processTemplates } from "../../lib/index.js"; import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ConfigError, FileError, InputError } from "../../lib/errors.js"; +import { ConfigError, InputError } from "../../lib/errors.js"; import path from "node:path"; import { existsSync } from "node:fs"; import inquirer from "inquirer"; import { kebabCase, pascalCase } from "change-case"; import { TestType } from "../../index.js"; +import { contractFromRecord, ensureArtifactsExist } from "../../lib/checks.js"; export class GenerateTests extends SwankyCommand { static description = "Generate test files for the specified contract"; @@ -37,7 +37,7 @@ export class GenerateTests extends SwankyCommand { throw new InputError("The 'contractName' argument is required to generate mocha tests."); } - await this.checkContract(args.contractName) + await this.checkContract(args.contractName); } const testType: TestType = flags.mocha ? "mocha" : "e2e"; @@ -55,37 +55,23 @@ export class GenerateTests extends SwankyCommand { templates.templatesPath, process.cwd(), args.contractName, - templateName + templateName, ); } async checkContract(name: string) { - const contractRecord = this.swankyConfig.contracts[name]; - if (!contractRecord) { - throw new ConfigError( - `Cannot find a contract named ${name} in swanky.config.json` - ); - } - const contract = new Contract(contractRecord); - if (!(await contract.pathExists())) { - throw new FileError( - `Path to contract ${name} does not exist: ${contract.contractPath}` - ); - } + const contractRecord = findContractRecord(this.swankyConfig, name); - const artifactsCheck = await contract.artifactsExist(); - if (!artifactsCheck.result) { - throw new FileError( - `No artifact file found at path: ${artifactsCheck.missingPaths.toString()}` - ); - } + const contract = (await contractFromRecord(contractRecord)); + + await ensureArtifactsExist(contract); } async checkOverwrite( testPath: string, testType: TestType, - contractName?: string + contractName?: string, ): Promise { if (!existsSync(testPath)) return true; // No need to overwrite const message = @@ -134,17 +120,17 @@ export class GenerateTests extends SwankyCommand { templatesPath: string, projectPath: string, contractName?: string, - templateName?: string + templateName?: string, ): Promise { if (testType === "e2e") { await this.spinner.runCommand( () => prepareTestFiles("e2e", templatesPath, projectPath), - "Generating e2e test helpers" + "Generating e2e test helpers", ); } else { await this.spinner.runCommand( () => prepareTestFiles("mocha", templatesPath, projectPath, templateName, contractName), - `Generating tests for ${contractName} with mocha` + `Generating tests for ${contractName} with mocha`, ); } await this.spinner.runCommand( @@ -155,7 +141,7 @@ export class GenerateTests extends SwankyCommand { contract_name: contractName ?? "", contract_name_pascal: contractName ? pascalCase(contractName) : "", }), - "Processing templates" + "Processing templates", ); } } diff --git a/src/commands/generate/types.ts b/src/commands/generate/types.ts index a6c0ed24..746eb622 100644 --- a/src/commands/generate/types.ts +++ b/src/commands/generate/types.ts @@ -1,8 +1,7 @@ import { Args } from "@oclif/core"; -import { configName, generateTypes } from "../../lib/index.js"; -import { Contract } from "../../lib/contract.js"; +import { findContractRecord, generateTypes } from "../../lib/index.js"; import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ConfigError, FileError } from "../../lib/errors.js"; +import { contractFromRecord, ensureArtifactsExist } from "../../lib/checks.js"; export class GenerateTypes extends SwankyCommand { static description = "Generate types from compiled contract metadata"; @@ -18,28 +17,11 @@ export class GenerateTypes extends SwankyCommand { async run(): Promise { const { args } = await this.parse(GenerateTypes); - const contractRecord = this.swankyConfig.contracts[args.contractName]; - if (!contractRecord) { - throw new ConfigError( - `Cannot find a contract named ${args.contractName} in "${configName()}"`, - ); - } + const contractRecord = findContractRecord(this.swankyConfig, args.contractName); - const contract = new Contract(contractRecord); + const contract = (await contractFromRecord(contractRecord)); - if (!(await contract.pathExists())) { - throw new FileError( - `Path to contract ${args.contractName} does not exist: ${contract.contractPath}`, - ); - } - - const artifactsCheck = await contract.artifactsExist(); - - if (!artifactsCheck.result) { - throw new FileError( - `No artifact file found at path: ${artifactsCheck.missingPaths.toString()}`, - ); - } + await ensureArtifactsExist(contract); await this.spinner.runCommand(async () => { await generateTypes(contract.name); diff --git a/src/lib/checks.ts b/src/lib/checks.ts new file mode 100644 index 00000000..c709372b --- /dev/null +++ b/src/lib/checks.ts @@ -0,0 +1,80 @@ +import { ConfigError, FileError, InputError, ProcessError } from "./errors.js"; +import { pathExists } from "fs-extra"; +import chalk from "chalk"; +import path from "node:path"; +import semver from "semver"; +import { Contract } from "./contract.js"; +import { AccountData, ContractData } from "../types/index.js"; + +export function ensureContractNameOrAllFlagIsSet( + args: any, + flags: any, + errorMessage = "No contracts were selected. Specify a contract name or use the --all flag." +) { + if (args.contractName === undefined && !flags.all) { + throw new ConfigError(errorMessage); + } +} + +export async function ensureContractPathExists(contractName: string, projectPath = "") { + const contractPath = path.resolve(projectPath, "contracts", contractName); + if (!(await pathExists(contractPath))) { + throw new InputError(`Contract folder not found ${chalk.yellowBright(contractName)} at path: ${contractPath}`); + } +} + +export async function contractFromRecord(contractRecord: ContractData) { + const contract = new Contract(contractRecord); + + if (!(await contract.pathExists())) { + throw new FileError( + `Path to contract ${contractRecord.name} does not exist: ${contract.contractPath}`, + ); + } + return contract; +} + +export async function ensureArtifactsExist(contract: Contract) { + const artifactsCheck = await contract.artifactsExist(); + if (!artifactsCheck.result) { + throw new FileError( + `No artifact file found at path: ${artifactsCheck.missingPaths.toString()}`, + ); + } +} + +export async function ensureTypedContractExists(contract: Contract) { + const typedContractCheck = await contract.typedContractExists(); + + if (!typedContractCheck.result) { + throw new FileError( + `No typed contract found at path: ${typedContractCheck.missingPaths.toString()}` + ); + } +} + +export function ensureDevAccountNotInProduction(accountData: AccountData, network: string) { + if (accountData.isDev && network !== "local") { + throw new ConfigError( + `Account ${accountData.alias} is a DEV account and can only be used with local network` + ); + } +} + +export function ensureCargoContractVersionCompatibility( + cargoContractVersion: string, + minimalVersion: string, + invalidVersionsList?: string[] +) { + if (invalidVersionsList?.includes(cargoContractVersion)) { + throw new ProcessError( + `The cargo-contract version ${cargoContractVersion} is not supported. Please update or change the version.` + ); + } + + if (!semver.satisfies(cargoContractVersion.replace(/-.*$/, ""), `>=${minimalVersion}`)) { + throw new ProcessError( + `cargo-contract version >= ${minimalVersion} required, but found version ${cargoContractVersion}. Please update to a compatible version.` + ); + } +} \ No newline at end of file diff --git a/src/lib/config.ts b/src/lib/config.ts index fd98d235..193af1bc 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,4 +1,4 @@ -import { SwankyConfig } from "../index.js"; +import { configName, SwankyConfig } from "../index.js"; import { FileError } from "./errors.js"; export function ensureSwankyNodeInstalled(config: SwankyConfig) { @@ -6,3 +6,39 @@ export function ensureSwankyNodeInstalled(config: SwankyConfig) { throw new FileError('Swanky node is not installed. Please run `swanky node:install` first.'); } } + +// //deploy +// if (!contractRecord) { +// throw new ConfigError( +// `Cannot find a contract named ${args.contractName} in "${configName()}"` +// ); +// } +// +// //explain +// if (!contractRecord) { +// throw new ConfigError( +// `Cannot find a contract named ${args.contractName} in "${configName()}"` +// ); +// } +// +// //test +// if (!contractRecord) { +// throw new ConfigError( +// `Cannot find a contract named ${args.contractName} in "${configName()}"` +// ); +// } +// +// //contractCall +// if (!contractRecord) { +// throw new ConfigError( +// `Cannot find a contract named ${args.contractName} in "${configName()}"`, +// ); +// } + +export function findContractRecord(config: SwankyConfig, contractName: string) { + const contractRecord = config.contracts[contractName]; + if (!contractRecord) { + throw new FileError(`Cannot find a contract named ${contractName} in "${configName()}"`); + } + return contractRecord; +} diff --git a/src/lib/contract.ts b/src/lib/contract.ts index 62d7f01b..22dc127e 100644 --- a/src/lib/contract.ts +++ b/src/lib/contract.ts @@ -42,12 +42,12 @@ export class Contract { return { result, missingPaths }; } - async typedContractExists(contractName: string) { + async typedContractExists() { const result: { result: boolean; missingPaths: string[] } = { result: true, missingPaths: [], }; - const artifactPath = path.resolve("typedContracts", `${contractName}`); + const artifactPath = path.resolve("typedContracts", `${this.name}`); if(!(await pathExists(artifactPath))) { result.result = false; result.missingPaths.push(artifactPath); diff --git a/src/lib/contractCall.ts b/src/lib/contractCall.ts index a65f4894..b42c0ae1 100644 --- a/src/lib/contractCall.ts +++ b/src/lib/contractCall.ts @@ -1,12 +1,21 @@ -import { AbiType, ChainAccount, ChainApi, configName, ensureAccountIsSet, decrypt, resolveNetworkUrl } from "./index.js"; +import { + AbiType, + ChainAccount, + ChainApi, + configName, + ensureAccountIsSet, + decrypt, + resolveNetworkUrl, + findContractRecord, +} from "./index.js"; import { ContractData, DeploymentData, Encrypted } from "../types/index.js"; import { Args, Command, Flags, Interfaces } from "@oclif/core"; import inquirer from "inquirer"; import chalk from "chalk"; import { SwankyCommand } from "./swankyCommand.js"; import { cryptoWaitReady } from "@polkadot/util-crypto/crypto"; -import { Contract } from "./contract.js"; -import { ConfigError, FileError, NetworkError } from "./errors.js"; +import { NetworkError } from "./errors.js"; +import { contractFromRecord, ensureArtifactsExist, ensureDevAccountNotInProduction } from "./checks.js"; export type JoinedFlagsType = Interfaces.InferredFlags< (typeof ContractCall)["baseFlags"] & T["flags"] @@ -50,28 +59,11 @@ export abstract class ContractCall extends SwankyComma this.args = args; this.flags = flags as JoinedFlagsType; - const contractRecord = this.swankyConfig.contracts[args.contractName]; - if (!contractRecord) { - throw new ConfigError( - `Cannot find a contract named ${args.contractName} in "${configName()}"`, - ); - } - - const contract = new Contract(contractRecord); + const contractRecord = findContractRecord(this.swankyConfig, args.contractName); - if (!(await contract.pathExists())) { - throw new FileError( - `Path to contract ${args.contractName} does not exist: ${contract.contractPath}`, - ); - } + const contract = (await contractFromRecord(contractRecord)); - const artifactsCheck = await contract.artifactsExist(); - - if (!artifactsCheck.result) { - throw new FileError( - `No artifact file found at path: ${artifactsCheck.missingPaths.toString()}`, - ); - } + await ensureArtifactsExist(contract); const deploymentData = flags.address ? contract.deployments.find( @@ -89,11 +81,9 @@ export abstract class ContractCall extends SwankyComma ensureAccountIsSet(flags.account, this.swankyConfig); const accountAlias = flags.account ?? this.swankyConfig.defaultAccount; - const accountData = this.findAccountByAlias(flags.account || "alice"); + const accountData = this.findAccountByAlias(accountAlias); - if (accountData.isDev && (flags.network !== "local" || !flags.network)) { - throw new ConfigError(`Account ${chalk.redBright(accountAlias)} is a dev account and can only be used on the local network`); - } + ensureDevAccountNotInProduction(accountData, flags.network); const networkUrl = resolveNetworkUrl(this.swankyConfig, flags.network ?? ""); const api = await ChainApi.create(networkUrl); diff --git a/src/lib/tasks.ts b/src/lib/tasks.ts index a8de45f1..0755b9d8 100644 --- a/src/lib/tasks.ts +++ b/src/lib/tasks.ts @@ -1,12 +1,8 @@ import { execaCommand } from "execa"; import { copy, ensureDir, remove } from "fs-extra/esm"; -import { readFile, rename, rm, writeFile } from "fs/promises"; import path from "node:path"; -import { globby } from "globby"; -import handlebars from "handlebars"; import { DownloadEndedStats, DownloaderHelper } from "node-downloader-helper"; import process from "node:process"; -import semver from "semver"; import { nodeInfo } from "./nodeInfo.js"; import decompress from "decompress"; import { Spinner } from "./spinner.js"; @@ -17,7 +13,6 @@ import { zombienetConfig } from "../commands/zombienet/init.js"; import { readFileSync } from "fs"; import TOML from "@iarna/toml"; import { writeFileSync } from "node:fs"; -import { chopsticksConfig } from "../commands/node/chopsticks/init.js"; export async function checkCliDependencies(spinner: Spinner) { const dependencyList = [ @@ -84,33 +79,6 @@ export function osCheck() { return { platform, arch }; } -export async function copyCommonTemplateFiles(templatesPath: string, projectPath: string) { - await ensureDir(projectPath); - const commonFiles = await globby(`*`, { cwd: templatesPath }); - await Promise.all( - commonFiles.map(async (file) => { - await copy(path.resolve(templatesPath, file), path.resolve(projectPath, file)); - }), - ); - await rename(path.resolve(projectPath, "gitignore"), path.resolve(projectPath, ".gitignore")); - await rename( - path.resolve(projectPath, "mocharc.json"), - path.resolve(projectPath, ".mocharc.json"), - ); - await copy(path.resolve(templatesPath, "github"), path.resolve(projectPath, ".github")); -} - -export async function copyContractTemplateFiles( - contractTemplatePath: string, - contractName: string, - projectPath: string, -) { - await copy( - path.resolve(contractTemplatePath, "contract"), - path.resolve(projectPath, "contracts", contractName), - ); -} - export async function prepareTestFiles( testType: TestType, templatePath: string, @@ -146,20 +114,6 @@ export async function prepareTestFiles( } } -export async function processTemplates(projectPath: string, templateData: Record) { - const templateFiles = await globby(projectPath, { - expandDirectories: { extensions: ["hbs"] }, - }); - - for (const tplFilePath of templateFiles) { - const rawTemplate = await readFile(tplFilePath, "utf8"); - const template = handlebars.compile(rawTemplate); - const compiledFile = template(templateData); - await rm(tplFilePath); - await writeFile(tplFilePath.split(".hbs")[0], compiledFile); - } -} - export async function downloadNode(projectPath: string, nodeInfo: nodeInfo, spinner: Spinner) { const binPath = path.resolve(projectPath, "bin"); await ensureDir(binPath); @@ -211,22 +165,6 @@ export async function downloadNode(projectPath: string, nodeInfo: nodeInfo, spin return path.resolve(binPath, dlFileDetails.filePath); } -export async function copyZombienetTemplateFile(templatePath: string, configPath: string) { - await ensureDir(configPath); - await copy( - path.resolve(templatePath, zombienetConfig), - path.resolve(configPath, zombienetConfig), - ); -} - -export async function copyChopsticksTemplateFile(templatePath: string, configPath: string) { - await ensureDir(configPath); - await copy( - path.resolve(templatePath, chopsticksConfig), - path.resolve(configPath, chopsticksConfig), - ); -} - export async function downloadZombienetBinaries(binaries: string[], projectPath: string, swankyConfig: SwankyConfig, spinner: Spinner) { const binPath = path.resolve(projectPath, "zombienet", "bin"); await ensureDir(binPath); @@ -359,21 +297,3 @@ export async function installDeps(projectPath: string) { await execaCommand(installCommand, { cwd: projectPath }); } } - -export function ensureCargoContractVersionCompatibility( - cargoContractVersion: string, - minimalVersion: string, - invalidVersionsList?: string[] -) { - if (invalidVersionsList?.includes(cargoContractVersion)) { - throw new ProcessError( - `The cargo-contract version ${cargoContractVersion} is not supported. Please update or change the version.` - ); - } - - if (!semver.satisfies(cargoContractVersion.replace(/-.*$/, ""), `>=${minimalVersion}`)) { - throw new ProcessError( - `cargo-contract version >= ${minimalVersion} required, but found version ${cargoContractVersion}. Please update to a compatible version.` - ); - } -} diff --git a/src/lib/templates.ts b/src/lib/templates.ts index 225278ca..d0479e66 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -1,6 +1,12 @@ import { readdirSync } from "fs"; import { fileURLToPath } from "url"; import path from "node:path"; +import { globby } from "globby"; +import handlebars from "handlebars"; +import { ensureDir, copy } from "fs-extra"; +import { readFile, rename, rm, writeFile } from "fs/promises"; +import { chopsticksConfig } from "../commands/node/chopsticks/init.js"; +import { zombienetConfig } from "../commands/zombienet/init.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -25,3 +31,60 @@ export function getTemplates() { chopsticksTemplatesPath, }; } + +export async function copyContractTemplateFiles( + contractTemplatePath: string, + contractName: string, + projectPath: string, +) { + await copy( + path.resolve(contractTemplatePath, "contract"), + path.resolve(projectPath, "contracts", contractName), + ); +} + +export async function copyZombienetTemplateFile(templatePath: string, configPath: string) { + await ensureDir(configPath); + await copy( + path.resolve(templatePath, zombienetConfig), + path.resolve(configPath, zombienetConfig), + ); +} + +export async function copyChopsticksTemplateFile(templatePath: string, configPath: string) { + await ensureDir(configPath); + await copy( + path.resolve(templatePath, chopsticksConfig), + path.resolve(configPath, chopsticksConfig), + ); +} + +export async function copyCommonTemplateFiles(templatesPath: string, projectPath: string) { + await ensureDir(projectPath); + const commonFiles = await globby(`*`, { cwd: templatesPath }); + await Promise.all( + commonFiles.map(async (file) => { + await copy(path.resolve(templatesPath, file), path.resolve(projectPath, file)); + }), + ); + await rename(path.resolve(projectPath, "gitignore"), path.resolve(projectPath, ".gitignore")); + await rename( + path.resolve(projectPath, "mocharc.json"), + path.resolve(projectPath, ".mocharc.json"), + ); + await copy(path.resolve(templatesPath, "github"), path.resolve(projectPath, ".github")); +} + +export async function processTemplates(projectPath: string, templateData: Record) { + const templateFiles = await globby(projectPath, { + expandDirectories: { extensions: ["hbs"] }, + }); + + for (const tplFilePath of templateFiles) { + const rawTemplate = await readFile(tplFilePath, "utf8"); + const template = handlebars.compile(rawTemplate); + const compiledFile = template(templateData); + await rm(tplFilePath); + await writeFile(tplFilePath.split(".hbs")[0], compiledFile); + } +} \ No newline at end of file