@kadena/kadena-cli
Version:
Kadena CLI tool to interact with the Kadena blockchain (manage keys, transactions, etc.)
236 lines • 9.75 kB
JavaScript
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