Skip to content

Commit

Permalink
feat: add chain export and import, also add ChainRegistry domain
Browse files Browse the repository at this point in the history
  • Loading branch information
eliasmpw committed Mar 31, 2023
1 parent 6bb3000 commit 3b1ed5a
Show file tree
Hide file tree
Showing 26 changed files with 424 additions and 77 deletions.
5 changes: 5 additions & 0 deletions modulor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "test",
"chainId": "super-1",
"contractsPath": "./contracts"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"bin": "archway",
"dirname": "archway",
"commands": "./dist/commands",
"helpClass": "./src/plugins/help-plugin/help.ts",
"helpClass": "./src/plugins/help-plugin/help",
"plugins": [],
"topicSeparator": " "
},
Expand Down
9 changes: 0 additions & 9 deletions src/commands/config/chains.ts

This file was deleted.

26 changes: 26 additions & 0 deletions src/commands/config/chains/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { BaseCommand } from '../../../lib/base';
import { DefaultChainsRelativePath } from '../../../config';
import path from 'node:path';
import { bold, green } from '../../../utils/style';
import { Args } from '@oclif/core';
import { BuiltInChains } from '../../../services/BuiltInChains';
import { ChainRegistry } from '../../../domain/ChainRegistry';
import { CosmosChain } from 'src/types/CosmosSchema';

export default class ConfigChainsExport extends BaseCommand<typeof ConfigChainsExport> {
static summary = `Exports a built-in chain registry file to ${bold(
path.join('{project-root}', DefaultChainsRelativePath, './{chain-id}.json')
)}.`;

static args = {
chain: Args.string({ name: 'chain', required: true, options: BuiltInChains.getChainIds() }),
};

public async run(): Promise<void> {
const chainRegistry = await ChainRegistry.init();

await chainRegistry.writeChainFile(BuiltInChains.getChainById(this.args.chain) as CosmosChain);

this.log(`✅ ${green('Exported chain to')} ${bold(path.join(chainRegistry.path, `./${this.args.chain}.json`))}`);
}
}
35 changes: 35 additions & 0 deletions src/commands/config/chains/import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { BaseCommand } from '../../../lib/base';
import { DefaultChainsRelativePath } from '../../../config';
import path from 'node:path';
import { bold, green, red } from '../../../utils/style';
import { Args } from '@oclif/core';
import fs from 'node:fs/promises';
import { CosmosChain } from '../../../types/CosmosSchema';
import { ChainRegistry } from '../../../domain/ChainRegistry';

export default class ConfigChainsImport extends BaseCommand<typeof ConfigChainsImport> {
static summary = `Import a chain registry file and save it to ${bold(
path.join('{project-root}', DefaultChainsRelativePath, './{chain-id}.json')
)}.`;

static args = {
file: Args.string({ name: 'file', required: false }),
};

public async run(): Promise<void> {
// to do add pipe support
// if (this.args.file && this.argv.length === 1) {
// this.error(`❌ ${red('Please specify only one file to import')}`);
// } else if (!this.args.file) {
// this.error(`❌ ${red('Please specify the file to import as an argument, or pass the chain info in a pipe')}`);
// }

const chainInfo: CosmosChain = JSON.parse(await fs.readFile(this.args.file as string, 'utf-8'));

const chainRegistry = await ChainRegistry.init();

await chainRegistry.writeChainFile(chainInfo);

this.log(`✅ ${green('Imported chain')} ${bold(chainInfo.chain_id)}`);
}
}
11 changes: 11 additions & 0 deletions src/commands/config/chains/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Command } from '@oclif/core';
import Help from '../../../plugins/help-plugin/help';

export default class ConfigChains extends Command {
static summary = 'Display help for the contracts command.';

public async run(): Promise<void> {
const help = new Help(this.config, { all: true });
await help.showCommandHelp(ConfigChains);
}
}
26 changes: 26 additions & 0 deletions src/commands/config/chains/use.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { BaseCommand } from '../../../lib/base';
import { DefaultConfigFileName } from '../../../config';
import { bold, green, red } from '../../../utils/style';
import { ChainRegistry } from '../../../domain/ChainRegistry';
import { ConfigFile } from '../../../domain/ConfigFile';
import { Flags } from '@oclif/core';

export default class ConfigChainsUse extends BaseCommand<typeof ConfigChainsUse> {
static summary = `Switches the current chain in use and updates the ${bold(DefaultConfigFileName)} config file with his information.`;
static flags = {
chain: Flags.string({ required: true }),
};

public async run(): Promise<void> {
const configFile = await ConfigFile.open();
const chainRegistry = await ChainRegistry.init();

if (!chainRegistry.getChainById(this.flags.chain)) {
throw new Error(`❌ ${red('Chain id')} ${bold(this.flags.chain)} ${red('not found')}`);
}

configFile.update({ chainId: this.flags.chain }, true);

this.log(`✅ ${green('Switched chain to')} ${bold(this.flags.chain)}`);
}
}
28 changes: 14 additions & 14 deletions src/commands/config/init.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
import { Flags } from '@oclif/core';
import { Chain } from '../../services/Chain';
import { BuiltInChains } from '../../services/BuiltInChains';
import { BaseCommand } from '../../lib/base';
import { showPrompt } from '../../actions/Prompt';
import { ChainPrompt } from '../../domain/Chain';
import { ChainPrompt } from '../../services/Prompts';
import { ConfigFile } from '../../domain/ConfigFile';
import { bold, green, red } from '../../utils/style';
import { DefaultConfigFileName } from '../../config';

export default class ConfigInit extends BaseCommand<typeof ConfigInit> {
static summary = 'Initializes a config file for the current project.';
static flags = {
chain: Flags.string({ options: Chain.getChainIds() }),
chain: Flags.string({ options: BuiltInChains.getChainIds() }),
};

public async run(): Promise<void> {
// Get flags
const { flags } = await this.parse(ConfigInit);
let chain = flags.chain;

// If chain flag is not set, prompt user input in list
if (await ConfigFile.exists()) {
this.error(`❌ ${red('The file')} ${bold(DefaultConfigFileName)} ${red('already exists in this repository')}`);
}

// If chain flag is not set, prompt user
if (!chain) {
const response = await showPrompt(ChainPrompt);

chain = response?.chain;
chain = response.chain as string;
}

const configFile = ConfigFile.init({
const configFile = await ConfigFile.init({
name: 'test',
chainId: chain || '',
chainId: chain,
});

if (await ConfigFile.exists()) {
throw new Error('the file modulor.json already exists in this repository');
}

await configFile.write();

this.log('Config file created: modulor.json');
this.log(`✅ ${green('Config file')} ${bold(DefaultConfigFileName)} ${green('created')}`);
}
}
9 changes: 8 additions & 1 deletion src/commands/config/show.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { BaseCommand } from '../../lib/base';
import { ConfigFile } from '../../domain/ConfigFile';

export default class ConfigShow extends BaseCommand<typeof ConfigShow> {
static summary = 'Shows the config file for the current project.';

public async run(): Promise<void> {
this.log('commands/config/show.ts');
const configFile = await ConfigFile.open();

this.log(await configFile.formattedStatus());

if (this.jsonEnabled()) {
this.logJson(await configFile.dataWithContracts());
}
}
}
4 changes: 4 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const DefaultChainId = 'constantine-1';
export const DefaultConfigFileName = 'modulor.json';
export const DefaultContractsRelativePath = './contracts';
export const DefaultChainsRelativePath = './.modulor/chains';
27 changes: 0 additions & 27 deletions src/domain/Chain.ts

This file was deleted.

61 changes: 61 additions & 0 deletions src/domain/ChainRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { getWokspaceRoot } from '../utils/paths';
import path from 'node:path';
import { DefaultChainsRelativePath } from '../config';
import { CosmosChain } from '../types/CosmosSchema';
import fs from 'node:fs/promises';
import { BuiltInChains } from '../services/BuiltInChains';

export class ChainRegistry {
private _data: CosmosChain[];
private _path: string;

constructor(data: CosmosChain[], path: string) {
this._data = data;
this._path = path;
}

get data(): CosmosChain[] {
return this._data;
}

get path(): string {
return this._path;
}

static async init(chainsDirectory?: string): Promise<ChainRegistry> {
const directoryPath = chainsDirectory || path.join(await getWokspaceRoot(), DefaultChainsRelativePath);

let filesList = await fs.readdir(directoryPath);
filesList = filesList.filter(item => path.extname(item) === '.json');

const filesRead = await Promise.all<string>(filesList.map(item => fs.readFile(path.join(directoryPath, item), 'utf8')));

// List of built-in chains that could be added to final result
const builtInToAdd = { ...BuiltInChains.chainMap };

// Parse file contents, and check if they override built-in chain info
const parsedList: CosmosChain[] = [];
for (const file of filesRead) {
const parsed: CosmosChain = JSON.parse(file);
if (BuiltInChains.getChainIds().includes(parsed.chain_id)) {
delete builtInToAdd[parsed.chain_id];
}

parsedList.push(parsed);
}

// Create chain registry with the parsed chains and the built-in chains pending to add
return new ChainRegistry([...parsedList, ...Object.values(builtInToAdd)], directoryPath);
}

getChainById(chainId: string): CosmosChain | undefined {
return this._data.find(item => item.chain_id === chainId);
}

async writeChainFile(chain: CosmosChain): Promise<void> {
const json = JSON.stringify(chain, null, 2);

fs.writeFile(path.join(this._path, `./${chain.chain_id}.json`), json);
this._data.push(chain);
}
}
73 changes: 56 additions & 17 deletions src/domain/ConfigFile.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { ConfigData } from '../types/Config/ConfigData';
import { ConfigData, ConfigDataWithContracts } from '../types/ConfigData';
import fs from 'node:fs/promises';
import path from 'node:path';
import _ from 'lodash';

export const DefaultConfigFileName = 'modulor.json';
export const DefaultContractsPath = './contracts';
import { getWokspaceRoot } from '../utils/paths';
import { MergeMode } from '../types/utils';
import { mergeCustomizer } from '../utils';
import { DefaultConfigFileName, DefaultContractsRelativePath } from '../config';
import { bold } from '../utils/style';
import { Contracts } from './Contracts';

export class ConfigFile {
private _data: ConfigData;
Expand All @@ -19,19 +22,18 @@ export class ConfigFile {
return this._data;
}

static init(data: ConfigData, workspaceRoot = './'): ConfigFile {
const configData = _.defaultsDeep(data, { contractPaths: DefaultContractsPath });
const configPath = this.getFilePath(workspaceRoot);
return new ConfigFile(configData, configPath);
get path(): string {
return this._path;
}

async write(): Promise<void> {
const json = JSON.stringify(this._data, null, 2);
await fs.writeFile(this._path, json);
static async init(data: ConfigData, workspaceRoot?: string): Promise<ConfigFile> {
const configData = _.defaultsDeep(data, { contractsPath: DefaultContractsRelativePath });
const configPath = await this.getFilePath(workspaceRoot);
return new ConfigFile(configData, configPath);
}

static async exists(workspaceRoot = './'): Promise<boolean> {
const configPath = this.getFilePath(workspaceRoot);
static async exists(workspaceRoot?: string): Promise<boolean> {
const configPath = await this.getFilePath(workspaceRoot);
try {
await fs.access(configPath);
return true;
Expand All @@ -40,15 +42,52 @@ export class ConfigFile {
}
}

static async open(workspaceRoot = './'): Promise<ConfigFile> {
const configPath = this.getFilePath(workspaceRoot);
static async open(workspaceRoot?: string): Promise<ConfigFile> {
const configPath = await this.getFilePath(workspaceRoot);
await fs.access(configPath);
const data: ConfigData = await import(configPath);
const data: ConfigData = JSON.parse(await fs.readFile(configPath, 'utf8'));

return new ConfigFile(data, configPath);
}

static getFilePath(workspaceRoot = './'): string {
static async getFilePath(workspaceRoot?: string): Promise<string> {
if (!workspaceRoot) {
workspaceRoot = await getWokspaceRoot();
}

return path.join(workspaceRoot, DefaultConfigFileName);
}

async write(): Promise<void> {
const json = JSON.stringify(this._data, null, 2);
await fs.writeFile(this._path, json);
}

async update(newData: Partial<ConfigData>, writeAfterUpdate = false, arrayMergeMode = MergeMode.OVERWRITE): Promise<void> {
_.mergeWith(this.data, newData, mergeCustomizer(arrayMergeMode));

if (writeAfterUpdate) {
await this.write();
}
}

async formattedStatus(withContracts = true): Promise<string> {
let contractsStatus = '';

if (withContracts) {
const contracts = await Contracts.open(this._data.contractsPath);
contractsStatus = `\n\n${await contracts.formattedStatus()}`;
}

return `${bold('Project: ')}${this._data.name}\n${bold('Selected chain: ')}${this._data.chainId}` + contractsStatus;
}

async dataWithContracts(): Promise<ConfigDataWithContracts> {
const contracts = await Contracts.open(this._data.contractsPath);

return {
...this._data,
contracts: contracts.data,
};
}
}
Loading

0 comments on commit 3b1ed5a

Please sign in to comment.