UNPKG

@kadena/kadena-cli

Version:

Kadena CLI tool to interact with the Kadena blockchain (manage keys, transactions, etc.)

236 lines 9.75 kB
import yaml from 'js-yaml'; import { vol } from 'memfs'; import { statSync } from 'node:fs'; import path from 'node:path'; import sanitize from 'sanitize-filename'; import { ACCOUNT_DIR, HOME_KADENA_DIR, IS_TEST, WALLET_DIR, WORKING_DIRECTORY, YAML_EXT, } from '../../constants/config.js'; import { formatZodError, loadUnknownFile, notEmpty, safeYamlParse, } from '../../utils/globalHelpers.js'; import { arrayNotEmpty } from '../../utils/helpers.js'; import { log } from '../../utils/logger.js'; import { relativeToCwd } from '../../utils/path.util.js'; import { accountSchema } from '../account/account.schemas.js'; import { KadenaError } from '../service-error.js'; import { WALLET_SCHEMA_VERSION, walletSchema, } from '../wallet/wallet.schemas.js'; import { plainKeySchema } from './config.schemas.js'; // To avoid promises / asynchrounous operations in the constructor // instead of using a filesystem service, created "directoryExists" helper const directoryExists = (path) => { if (path === undefined) return false; try { const stat = IS_TEST ? vol.statSync.bind(vol) : statSync; return stat(path).isDirectory(); } catch (e) { return false; } }; // recursively look for `.kadena` directory in parent folders const findKadenaDirectory = (searchDir) => { const dir = path.join(searchDir, '.kadena'); if (directoryExists(dir)) return dir; const parent = path.join(searchDir, '..'); if (parent !== searchDir) return findKadenaDirectory(parent); return null; }; export class ConfigService { constructor(services, directory) { // eslint-disable-next-line @typescript-eslint/naming-convention this.directory = null; this.services = services; this._discoverDirectory(directory); } setDirectory(directory) { if (!directoryExists(directory)) { log.warning(`Config service initialized with directory that does not exist: ${directory}`); } this.directory = directory; } _discoverDirectory(directory) { // Priority 1: directory passed in constructor if (directory !== undefined) { this.directory = directory; return; } // Priority 2: ENV KADENA_DIR const ENV_KADENA_DIR = process.env.KADENA_DIR; if (ENV_KADENA_DIR !== undefined) { if (directoryExists(ENV_KADENA_DIR)) { this.directory = ENV_KADENA_DIR; return; } else { log.warning(`Warning: 'KADENA_DIR' environment variable is set to a non-existent directory: ${ENV_KADENA_DIR}\n`); } } // Priority 3: CWD .kadena dir in recursive parent search const kadenaDirectory = findKadenaDirectory(WORKING_DIRECTORY); if (kadenaDirectory !== null) { this.directory = kadenaDirectory; return; } // Priority 4: HOME .kadena dir if (directoryExists(HOME_KADENA_DIR)) { this.directory = HOME_KADENA_DIR; return; } // No directory found, instruct the user to run `kadena config init` this.directory = null; } getDirectory() { if (this.directory === null) throw new KadenaError('no_kadena_directory'); return this.directory; } async getPlainKey(filepath) { var _a; const file = await loadUnknownFile(filepath); const parsed = plainKeySchema.safeParse(file); if (!parsed.success) return null; const alias = path.basename(filepath); return { alias: alias, filepath, legacy: (_a = parsed.data.legacy) !== null && _a !== void 0 ? _a : false, publicKey: parsed.data.publicKey, secretKey: parsed.data.secretKey, }; } async getPlainKeys(directory) { const dir = directory !== null && directory !== void 0 ? directory : process.cwd(); const files = await this.services.filesystem.readDir(dir); const keys = await Promise.all(files.map(async (file) => this.getPlainKey(path.join(dir, file)))); return keys.filter(notEmpty); } async setPlainKey(key) { const filename = sanitize(key.alias); const filepath = path.join(process.cwd(), `${filename}${YAML_EXT}`); if (await this.services.filesystem.fileExists(filepath)) { throw new Error(`Plain Key "${relativeToCwd(filepath)}" already exists.`); } const data = { publicKey: key.publicKey, secretKey: key.secretKey, }; if (key.legacy) data.legacy = key.legacy; await this.services.filesystem.writeFile(filepath, yaml.dump(data, { lineWidth: -1 })); return filepath; } async getWallet(filepath) { var _a; const file = await this.services.filesystem.readFile(filepath); if (file === null) { throw new Error(`Wallet file "${relativeToCwd(filepath)}" not found.`); } const parsed = walletSchema.safeParse(safeYamlParse(file)); if (!parsed.success) return null; return { alias: parsed.data.alias, filepath, version: parsed.data.version, legacy: (_a = parsed.data.legacy) !== null && _a !== void 0 ? _a : false, seed: parsed.data.seed, keys: parsed.data.keys, }; } async setWallet(wallet, update) { const directory = this.getDirectory(); const filename = sanitize(wallet.alias); const filepath = path.join(directory, WALLET_DIR, `${filename}${YAML_EXT}`); const exists = await this.services.filesystem.fileExists(filepath); if (exists && update !== true) { throw new Error(`Wallet "${relativeToCwd(filepath)}" already exists.`); } const data = { alias: wallet.alias, seed: wallet.seed, version: WALLET_SCHEMA_VERSION, keys: wallet.keys, }; if (wallet.legacy) data.legacy = wallet.legacy; await this.services.filesystem.ensureDirectoryExists(path.join(directory, WALLET_DIR)); await this.services.filesystem.writeFile(filepath, yaml.dump(data, { lineWidth: -1 })); return filepath; } async getWallets() { const directory = this.getDirectory(); const walletPath = path.join(directory, WALLET_DIR); if (!(await this.services.filesystem.directoryExists(walletPath))) { return []; } const files = await this.services.filesystem.readDir(walletPath); const filepaths = files.map((file) => path.join(walletPath, file)); const wallets = await Promise.all(filepaths.map(async (filepath) => this.getWallet(filepath).catch(() => null))); return wallets.filter(notEmpty); } async deleteWallet(filepath) { await this.services.filesystem.deleteFile(filepath); } // Account async setAccount(account, update) { const directory = this.getDirectory(); const filename = sanitize(account.alias); const filepath = path.join(directory, ACCOUNT_DIR, `${filename}${YAML_EXT}`); const exists = await this.services.filesystem.fileExists(filepath); if (exists && update !== true) { throw new Error(`Account "${relativeToCwd(filepath)}" already exists.`); } if (!arrayNotEmpty(account.publicKeys)) { throw new Error('No public keys provided'); } const data = { alias: account.alias, name: account.name, fungible: account.fungible, predicate: account.predicate, publicKeys: account.publicKeys, }; await this.services.filesystem.ensureDirectoryExists(path.join(directory, ACCOUNT_DIR)); await this.services.filesystem.writeFile(filepath, yaml.dump(data, { lineWidth: -1 })); return filepath; } async getAccount(filepath) { const file = await this.services.filesystem.readFile(filepath); if (file === null) { throw new Error(`Account file "${relativeToCwd(filepath)}" not found.`); } const parsedYaml = safeYamlParse(file); // Alias was added later, insert here for backwards compatibility if (parsedYaml && parsedYaml.alias === undefined) { parsedYaml.alias = path.basename(filepath, YAML_EXT); } const parsed = accountSchema.safeParse(parsedYaml); if (!parsed.success) { throw new Error(`Error parsing account file: ${formatZodError(parsed.error)}`); } return { alias: parsed.data.alias, name: parsed.data.name, fungible: parsed.data.fungible, predicate: parsed.data.predicate, publicKeys: parsed.data.publicKeys, filepath, }; } async getAccounts() { const directory = this.getDirectory(); const accountDir = path.join(directory, ACCOUNT_DIR); if (!(await this.services.filesystem.directoryExists(accountDir))) { return []; } const files = await this.services.filesystem.readDir(accountDir); const filepaths = files.map((file) => path.join(accountDir, file)); const accounts = await Promise.all(filepaths.map(async (filepath) => this.getAccount(filepath).catch(() => null))); return accounts.filter(notEmpty); } async deleteAccount(filepath) { await this.services.filesystem.deleteFile(filepath); } } //# sourceMappingURL=config.service.js.map