diff --git a/src/commands/account/create.ts b/src/commands/account/create.ts index a240e2f0..0ba54ac9 100644 --- a/src/commands/account/create.ts +++ b/src/commands/account/create.ts @@ -1,6 +1,6 @@ import { Flags } from "@oclif/core"; import chalk from "chalk"; -import { ChainAccount, encrypt } from "../../lib/index.js"; +import { ChainAccount, encrypt, isLocalConfigCheck } from "../../lib/index.js"; import { AccountData } from "../../types/index.js"; import inquirer from "inquirer"; import { SwankyCommand } from "../../lib/swankyCommand.js"; @@ -8,6 +8,9 @@ export class CreateAccount extends SwankyCommand { static description = "Create a new dev account in config"; static flags = { + global: Flags.boolean({ + description: "Create account globally", + }), generate: Flags.boolean({ char: "g", }), @@ -79,7 +82,15 @@ export class CreateAccount extends SwankyCommand { if(this.swankyConfig.defaultAccount === null) { this.swankyConfig.defaultAccount = accountData.alias; } - await this.storeSystemConfig(); + + if(flags.global) { + await this.storeSystemConfig(); + } + else if(isLocalConfigCheck()) { + await this.storeConfig(process.cwd()); + } else { + throw new Error("Cannot store account to config. Please run this command in a swanky project directory"); + } this.log( `${chalk.greenBright("✔")} Account with alias ${chalk.yellowBright( diff --git a/src/commands/account/default.ts b/src/commands/account/default.ts index 0c2a49e3..27d90cb1 100644 --- a/src/commands/account/default.ts +++ b/src/commands/account/default.ts @@ -1,12 +1,20 @@ -import { Args } from "@oclif/core"; +import { Args, Flags } from "@oclif/core"; import chalk from "chalk"; import { AccountData } from "../../types/index.js"; import inquirer from "inquirer"; import { SwankyCommand } from "../../lib/swankyCommand.js"; import { ConfigError } from "../../lib/errors.js"; +import { isLocalConfigCheck } from "../../lib/index.js"; export class DefaultAccount extends SwankyCommand { static description = "Set default account to use"; + static flags = { + global: Flags.boolean({ + char: "g", + description: "Set default account globally", + }), + } + static args = { accountAlias: Args.string({ name: "accountAlias", @@ -16,7 +24,7 @@ export class DefaultAccount extends SwankyCommand { }; async run(): Promise { - const { args } = await this.parse(DefaultAccount); + const { args, flags } = await this.parse(DefaultAccount); if(args.accountAlias) { const accountData = this.swankyConfig.accounts.find( @@ -43,7 +51,16 @@ export class DefaultAccount extends SwankyCommand { this.swankyConfig.defaultAccount = answers.defaultAccount; }); } - await this.storeSystemConfig(); + + if(flags.global) { + await this.storeSystemConfig(); + } + else if(isLocalConfigCheck()) { + await this.storeConfig(process.cwd()); + } else { + throw new Error("Cannot store account to config. Please run this command in a swanky project directory"); + } + console.log(chalk.greenBright(`Default account set to ${chalk.yellowBright(this.swankyConfig.defaultAccount)}`)); } } diff --git a/src/commands/check/index.ts b/src/commands/check/index.ts index 598b6b9b..2012bacf 100644 --- a/src/commands/check/index.ts +++ b/src/commands/check/index.ts @@ -1,5 +1,5 @@ import { Listr } from "listr2"; -import { commandStdoutOrNull } from "../../lib/index.js"; +import { commandStdoutOrNull, isLocalConfigCheck } from "../../lib/index.js"; import { SwankyConfig } from "../../types/index.js"; import { pathExistsSync} from "fs-extra/esm"; import { readFileSync } from "fs"; @@ -7,6 +7,7 @@ import path from "node:path"; import TOML from "@iarna/toml"; import semver from "semver"; import { SwankyCommand } from "../../lib/swankyCommand.js"; +import { FileError } from "../../lib/errors.js"; interface Ctx { versions: { @@ -60,11 +61,20 @@ export default class Check extends SwankyCommand { }, }, { - title: "Read ink dependencies", + title: "Check Swanky Config", task: async (ctx) => { + if (this.swankyConfig == undefined){ + throw new FileError("Swanky config not found") + } ctx.swankyConfig = this.swankyConfig; - - for (const contract in ctx.swankyConfig.contracts) { + } + }, + { + title: "Read ink dependencies", + enabled: isLocalConfigCheck(), + skip: (ctx) => ctx.swankyConfig == undefined || Object.keys(ctx.swankyConfig.contracts).length == 0, + task: async (ctx) => { + for (const contract in ctx.swankyConfig!.contracts) { const tomlPath = path.resolve(`contracts/${contract}/Cargo.toml`); const doesCargoTomlExist = pathExistsSync(tomlPath); if (!doesCargoTomlExist) { @@ -89,6 +99,8 @@ export default class Check extends SwankyCommand { }, { title: "Verify ink version", + enabled: isLocalConfigCheck(), + skip: (ctx) => ctx.swankyConfig == undefined || Object.keys(ctx.swankyConfig.contracts).length == 0, task: async (ctx) => { const supportedInk = ctx.swankyConfig?.node.supportedInk; diff --git a/src/commands/contract/deploy.ts b/src/commands/contract/deploy.ts index 831dbce4..84350e0c 100644 --- a/src/commands/contract/deploy.ts +++ b/src/commands/contract/deploy.ts @@ -1,19 +1,20 @@ import { Args, Flags } from "@oclif/core"; import { cryptoWaitReady } from "@polkadot/util-crypto/crypto"; -import { resolveNetworkUrl, ChainApi, ChainAccount, decrypt, AbiType } from "../../lib/index.js"; +import { resolveNetworkUrl, ChainApi, ChainAccount, decrypt, AbiType, ensureAccountIsSet } from "../../lib/index.js"; import { AccountData, Encrypted } 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 } from "../../lib/errors.js"; +import { DEFAULT_ACCOUNT } from "../../lib/consts.js"; export class DeployContract extends SwankyCommand { static description = "Deploy contract to a running node"; static flags = { account: Flags.string({ - default: "", + default: DEFAULT_ACCOUNT, description: "Alias of account to be used", }), gas: Flags.integer({ @@ -30,6 +31,7 @@ export class DeployContract extends SwankyCommand { }), network: Flags.string({ char: "n", + default: "local", description: "Network name to connect to", }), }; @@ -45,6 +47,8 @@ export class DeployContract extends SwankyCommand { async run(): Promise { const { args, flags } = await this.parse(DeployContract); + console.log("flags", flags); + const contractRecord = this.swankyConfig.contracts[args.contractName]; if (!contractRecord) { throw new ConfigError( @@ -68,9 +72,7 @@ export class DeployContract extends SwankyCommand { ); } - if(!flags.account && this.swankyConfig.defaultAccount === null) { - throw new ConfigError("No default account set. Please set one or provide an account alias with --account"); - } + ensureAccountIsSet(flags.account, this.swankyConfig); const accountAlias = flags.account ?? this.swankyConfig.defaultAccount; @@ -154,7 +156,7 @@ export class DeployContract extends SwankyCommand { }, ]; - await this.storeLocalConfig(process.cwd()); + await this.storeConfig(process.cwd()); }, "Writing config"); this.log(`Contract deployed!`); diff --git a/src/commands/contract/new.ts b/src/commands/contract/new.ts index b6b05f9f..f8b767fa 100644 --- a/src/commands/contract/new.ts +++ b/src/commands/contract/new.ts @@ -102,7 +102,7 @@ export class NewContract extends SwankyCommand { deployments: [], }; - await this.storeLocalConfig(process.cwd())}, "Writing config"); + await this.storeConfig(process.cwd())}, "Writing config"); this.log("😎 New contract successfully generated! 😎"); } diff --git a/src/commands/init/index.ts b/src/commands/init/index.ts index 2e4e5591..b1d0eb22 100644 --- a/src/commands/init/index.ts +++ b/src/commands/init/index.ts @@ -13,23 +13,15 @@ import { copyContractTemplateFiles, downloadNode, installDeps, - ChainAccount, processTemplates, swankyNode, getTemplates, } from "../../lib/index.js"; -import { - DEFAULT_ASTAR_NETWORK_URL, - DEFAULT_NETWORK_URL, - DEFAULT_SHIBUYA_NETWORK_URL, - DEFAULT_SHIDEN_NETWORK_URL, -} from "../../lib/consts.js"; import { SwankyCommand } from "../../lib/swankyCommand.js"; import { InputError, UnknownError } from "../../lib/errors.js"; import { GlobEntry, globby } from "globby"; import { merge } from "lodash-es"; import inquirerFuzzyPath from "inquirer-fuzzy-path"; -import { SwankyConfig } from "../../types/index.js"; import chalk from "chalk"; type TaskFunction = (...args: any[]) => any; @@ -93,22 +85,6 @@ export class Init extends SwankyCommand { } projectPath = ""; - configBuilder: Partial = { - node: { - localPath: "", - polkadotPalletVersions: swankyNode.polkadotPalletVersions, - supportedInk: swankyNode.supportedInk, - }, - accounts: [], - networks: { - local: { url: DEFAULT_NETWORK_URL }, - astar: { url: DEFAULT_ASTAR_NETWORK_URL }, - shiden: { url: DEFAULT_SHIDEN_NETWORK_URL }, - shibuya: { url: DEFAULT_SHIBUYA_NETWORK_URL }, - }, - contracts: {}, - }; - taskQueue: Task[] = []; async run(): Promise { @@ -166,42 +142,19 @@ export class Init extends SwankyCommand { args: [this.projectPath, swankyNode, this.spinner], runningMessage: "Downloading Swanky node", callback: (result) => - this.configBuilder.node ? (this.configBuilder.node.localPath = result) : null, + this.swankyConfig.node ? (this.swankyConfig.node.localPath = result) : null, }); } } - this.configBuilder.accounts = [ - { - alias: "alice", - mnemonic: "//Alice", - isDev: true, - address: new ChainAccount("//Alice").pair.address, - }, - { - alias: "bob", - mnemonic: "//Bob", - isDev: true, - address: new ChainAccount("//Bob").pair.address, - }, - ]; - - this.configBuilder.defaultAccount = "alice"; - - Object.keys(this.configBuilder.contracts!).forEach(async (contractName) => { + Object.keys(this.swankyConfig.contracts).forEach(async (contractName) => { await ensureDir(path.resolve(this.projectPath, "artifacts", contractName)); await ensureDir(path.resolve(this.projectPath, "tests", contractName)); }); this.taskQueue.push({ task: () => { - if (Object.keys(this.swankyConfig.networks).length === 0 || this.swankyConfig.accounts.length === 0) { - this.swankyConfig = this.configBuilder as SwankyConfig; - } else { - this.swankyConfig.node = this.configBuilder.node!; - this.swankyConfig.contracts = this.configBuilder.contracts!; - } - this.storeLocalConfig(this.projectPath); + this.storeConfig(this.projectPath); this.storeSystemConfig(); }, args: [], @@ -294,7 +247,7 @@ export class Init extends SwankyCommand { runningMessage: "Processing templates", }); - this.configBuilder.contracts = { + this.swankyConfig.contracts = { [contractName as string]: { name: contractName, moduleName: snakeCase(contractName), @@ -370,10 +323,10 @@ export class Init extends SwankyCommand { }, }); - if (!this.configBuilder.contracts) this.configBuilder.contracts = {}; + if (!this.swankyConfig.contracts) this.swankyConfig.contracts = {}; for (const contract of confirmedCopyList.contracts) { - this.configBuilder.contracts[contract.name] = { + this.swankyConfig.contracts[contract.name] = { name: contract.name, moduleName: contract.moduleName!, deployments: [], diff --git a/src/commands/node/install.ts b/src/commands/node/install.ts index a2b38aec..36eac4b9 100644 --- a/src/commands/node/install.ts +++ b/src/commands/node/install.ts @@ -36,7 +36,7 @@ export class InstallNode extends SwankyCommand { await this.spinner.runCommand( () => - this.storeLocalConfig(projectPath), + this.storeConfig(projectPath), "Updating swanky config" ); diff --git a/src/lib/command-utils.ts b/src/lib/command-utils.ts index de164b56..30388e7d 100644 --- a/src/lib/command-utils.ts +++ b/src/lib/command-utils.ts @@ -1,10 +1,18 @@ import { execaCommand } from "execa"; import { copy, emptyDir, ensureDir, readJSON } from "fs-extra/esm"; import path from "node:path"; -import { DEFAULT_NETWORK_URL, ARTIFACTS_PATH, TYPED_CONTRACTS_PATH } from "./consts.js"; -import { SwankyConfig, SwankyLocalConfig, SwankySystemConfig } from "../types/index.js"; +import { + DEFAULT_NETWORK_URL, + ARTIFACTS_PATH, + TYPED_CONTRACTS_PATH, + DEFAULT_SHIBUYA_NETWORK_URL, + DEFAULT_SHIDEN_NETWORK_URL, DEFAULT_ASTAR_NETWORK_URL, DEFAULT_ACCOUNT, +} from "./consts.js"; +import { SwankyConfig, SwankySystemConfig } from "../types/index.js"; import { ConfigError, FileError, InputError } from "./errors.js"; import { userInfo } from "os"; +import { swankyNode } from "./nodeInfo.js"; +import { existsSync } from "fs"; export async function commandStdoutOrNull(command: string): Promise { try { @@ -15,9 +23,10 @@ export async function commandStdoutOrNull(command: string): Promise { +export async function getSwankyConfig(): Promise { + const configPath = process.env.SWANKY_CONFIG ?? "swanky.config.json"; try { - const config = await readJSON("swanky.config.json"); + const config = await readJSON(configPath); return config; } catch (cause) { throw new InputError("Error reading swanky.config.json in the current directory!", { cause }); @@ -137,3 +146,32 @@ export async function generateTypes(contractName: string) { `npx typechain-polkadot --in ${relativeInputPath} --out ${relativeOutputPath}` ); } +export function ensureAccountIsSet(account: string | undefined, config: SwankyConfig) { + if(!account && config.defaultAccount === null) { + throw new ConfigError("No default account set. Please set one or provide an account alias with --account"); + } +} + +export function buildSwankyConfig() { + return { + node: { + localPath: "", + polkadotPalletVersions: swankyNode.polkadotPalletVersions, + supportedInk: swankyNode.supportedInk, + }, + defaultAccount: DEFAULT_ACCOUNT, + accounts: [], + networks: { + local: { url: DEFAULT_NETWORK_URL }, + astar: { url: DEFAULT_ASTAR_NETWORK_URL }, + shiden: { url: DEFAULT_SHIDEN_NETWORK_URL }, + shibuya: { url: DEFAULT_SHIBUYA_NETWORK_URL }, + }, + contracts: {}, + }; +} + +export function isLocalConfigCheck() { + console.log("process.env.SWANKY_CONFIG", process.env.SWANKY_CONFIG); + return process.env.SWANKY_CONFIG != "" || existsSync(process.cwd() + "/swanky.config.json"); +} \ No newline at end of file diff --git a/src/lib/consts.ts b/src/lib/consts.ts index d48b2b67..90c48ff3 100644 --- a/src/lib/consts.ts +++ b/src/lib/consts.ts @@ -3,5 +3,7 @@ export const DEFAULT_ASTAR_NETWORK_URL = "wss://rpc.astar.network"; export const DEFAULT_SHIDEN_NETWORK_URL = "wss://rpc.shiden.astar.network"; export const DEFAULT_SHIBUYA_NETWORK_URL = "wss://shibuya.public.blastapi.io"; +export const DEFAULT_ACCOUNT = "alice"; + export const ARTIFACTS_PATH = "artifacts"; export const TYPED_CONTRACTS_PATH = "typedContracts"; diff --git a/src/lib/contractCall.ts b/src/lib/contractCall.ts index fafbd2d6..a1d56606 100644 --- a/src/lib/contractCall.ts +++ b/src/lib/contractCall.ts @@ -1,4 +1,4 @@ -import { AbiType, ChainAccount, ChainApi, decrypt, resolveNetworkUrl } from "./index.js"; +import { AbiType, ChainAccount, ChainApi, decrypt, ensureAccountIsSet, resolveNetworkUrl } from "./index.js"; import { AccountData, ContractData, DeploymentData, Encrypted } from "../types/index.js"; import { Args, Command, Flags, Interfaces } from "@oclif/core"; import inquirer from "inquirer"; @@ -77,9 +77,7 @@ export abstract class ContractCall extends SwankyComma this.deploymentInfo = deploymentData; - if(!flags.account && this.swankyConfig.defaultAccount === null) { - throw new ConfigError("No default account set in swanky.config.json and no account provided"); - } + ensureAccountIsSet(flags.account, this.swankyConfig); const accountAlias = flags.account ?? this.swankyConfig.defaultAccount; diff --git a/src/lib/swankyCommand.ts b/src/lib/swankyCommand.ts index fa7a6709..bb0f8c3f 100644 --- a/src/lib/swankyCommand.ts +++ b/src/lib/swankyCommand.ts @@ -1,9 +1,15 @@ import { Command, Flags, Interfaces } from "@oclif/core"; -import { getSwankySystemConfig, getSwankyLocalConfig, Spinner, findSwankySystemConfigPath } from "./index.js"; -import { SwankyConfig, SwankyLocalConfig, SwankySystemConfig } from "../types/index.js"; +import { + getSwankySystemConfig, + getSwankyConfig, + Spinner, + findSwankySystemConfigPath, + buildSwankyConfig, +} from "./index.js"; +import { SwankyConfig, SwankySystemConfig } from "../types/index.js"; import { writeJSON } from "fs-extra/esm"; import { mkdirSync, existsSync } from "fs"; -import { BaseError, UnknownError } from "./errors.js"; +import { BaseError, ConfigError, UnknownError } from "./errors.js"; import { swankyLogger } from "./logger.js"; import { Logger } from "winston"; import path from "node:path"; @@ -36,36 +42,31 @@ export abstract class SwankyCommand extends Command { this.args = args as Args; this.logger = swankyLogger; - this.swankyConfig = { - node: { - polkadotPalletVersions: "", - localPath: "", - supportedInk: "", - }, - contracts: {}, - defaultAccount: null, - accounts: [], - networks: {}, - }; + this.swankyConfig = buildSwankyConfig(); try { - const localConfig = await getSwankyLocalConfig(); + const systemConfig = await getSwankySystemConfig(); this.swankyConfig = { ...this.swankyConfig, - ...localConfig, + ...systemConfig, }; } catch (error) { - this.logger.warn("No local config found") + this.logger.warn("No system config found") } try { - const systemConfig = await getSwankySystemConfig(); + const localConfig = await getSwankyConfig(); this.swankyConfig = { ...this.swankyConfig, - ...systemConfig, + ...localConfig, }; } catch (error) { - this.logger.warn("No system config found") + this.logger.warn("No local config found") + if (error instanceof Error && + error.message.includes("swanky.config.json") && + (this.constructor as typeof SwankyCommand).ENSURE_SWANKY_CONFIG + ) + throw new ConfigError(`Cannot find ${process.env.SWANKY_CONFIG ?? "swanky.config.json"}`, { cause: error }); } this.logger.info(`Running command: ${this.ctor.name} @@ -74,12 +75,12 @@ export abstract class SwankyCommand extends Command { Full command: ${JSON.stringify(process.argv)}`); } - protected async storeLocalConfig(projectPath: string) { - const localConfig : SwankyLocalConfig = { - node: this.swankyConfig.node, - contracts: this.swankyConfig.contracts + protected async storeConfig(projectPath: string) { + const configPath = process.env.SWANKY_CONFIG ?? path.resolve(projectPath, "swanky.config.json"); + const localConfig : SwankyConfig = { + ...this.swankyConfig, } - await writeJSON(path.resolve(projectPath, "swanky.config.json"), localConfig, { spaces: 2 }); + await writeJSON(configPath, localConfig, { spaces: 2 }); } protected async storeSystemConfig() { @@ -105,6 +106,7 @@ export abstract class SwankyCommand extends Command { protected async finally(_: Error | undefined): Promise { // called after run and catch regardless of whether or not the command errored + // console.log("Swanky Config: ", this.swankyConfig); return super.finally(_); } } diff --git a/src/types/index.ts b/src/types/index.ts index c8883a59..e74543b3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -38,19 +38,7 @@ export interface DeploymentData { deployerAlias: string; address: string; } -export interface SwankyConfig { - node: { - polkadotPalletVersions: string; - localPath: string; - supportedInk: string; - }; - defaultAccount: string | null; - accounts: AccountData[]; - contracts: Record | Record; - networks: Record -} - -export interface SwankyLocalConfig { +export interface SwankyConfig extends SwankySystemConfig{ node: { polkadotPalletVersions: string; localPath: string;