@cosmwasm/ts-codegen
Version:
@cosmwasm/ts-codegen converts your CosmWasm smart contracts into dev-friendly TypeScript classes so you can focus on shipping code.
129 lines (128 loc) • 4.88 kB
JavaScript
import generate from '@babel/generator';
import * as t from '@babel/types';
import { BuilderContext, defaultOptions, } from '@cosmwasm/ts-codegen-ast';
import { pascal } from 'case';
import deepmerge from 'deepmerge';
import { writeFileSync } from 'fs';
import { sync as mkdirp } from 'mkdirp';
import { join } from 'path';
import { basename } from 'path';
import { createFileBundle, recursiveModuleBundle } from '../bundler';
import { createHelpers } from '../helpers/create-helpers';
import { ClientPlugin } from '../plugins/client';
import { MessageBuilderPlugin } from '../plugins/message-builder';
import { MessageComposerPlugin } from '../plugins/message-composer';
import { ContractsContextProviderPlugin } from '../plugins/provider';
import { ContractsProviderBundlePlugin } from '../plugins/provider-bundle';
import { ReactQueryPlugin } from '../plugins/react-query';
import { RecoilPlugin } from '../plugins/recoil';
import { TypesPlugin } from '../plugins/types';
import { readSchemas } from '../utils';
import { createDefaultContractInfo } from '../utils/contracts';
import { header } from '../utils/header';
const defaultOpts = {
bundle: {
enabled: true,
scope: 'contracts',
bundleFile: 'bundle.ts',
},
useShorthandCtor: true,
};
function getContract(contractOpt) {
if (typeof contractOpt === 'string') {
const name = basename(contractOpt);
const contractName = pascal(name);
return {
name: contractName,
dir: contractOpt,
};
}
return {
name: pascal(contractOpt.name),
dir: contractOpt.dir,
};
}
export class TSBuilder {
contracts;
outPath;
options;
plugins = [];
builderContext = new BuilderContext();
files = [];
loadDefaultPlugins() {
this.plugins.push(new TypesPlugin(this.options), new ClientPlugin(this.options), new MessageComposerPlugin(this.options), new ReactQueryPlugin(this.options), new RecoilPlugin(this.options), new MessageBuilderPlugin(this.options), new ContractsContextProviderPlugin(this.options), new ContractsProviderBundlePlugin(this.options));
}
constructor({ contracts, outPath, options, plugins }) {
this.contracts = contracts;
this.outPath = outPath;
this.options = deepmerge(deepmerge(defaultOptions, defaultOpts), options ?? {});
this.loadDefaultPlugins();
if (plugins && plugins.length) {
this.plugins.push(...plugins);
}
this.plugins.forEach((plugin) => plugin.setBuilder(this));
}
async build() {
await this.process();
await this.after();
}
// lifecycle functions
async process() {
for (const contractOpt of this.contracts) {
const contract = getContract(contractOpt);
//resolve contract schema.
const contractInfo = await readSchemas({
schemaDir: contract.dir,
});
//lifecycle and plugins.
await this.render('main', contract.name, contractInfo);
}
}
async render(lifecycle, name, contractInfo) {
const plugins = lifecycle
? this.plugins.filter((p) => p.lifecycle === lifecycle)
: this.plugins;
for (const plugin of plugins) {
let files = await plugin.render(this.outPath, name, contractInfo ?? createDefaultContractInfo());
if (files && files.length) {
this.files.push(...files);
}
}
}
async after() {
await this.render('after');
const helpers = createHelpers({
outPath: this.outPath,
contracts: this.contracts,
options: this.options,
plugins: this.plugins,
}, this.builderContext);
if (helpers && helpers.length) {
this.files.push(...helpers);
}
if (this.options.bundle.enabled) {
this.bundle();
}
}
async bundle() {
const allFiles = this.files;
const bundleFile = this.options.bundle.bundleFile;
const bundlePath = join(this.options?.bundle?.bundlePath ?? this.outPath, bundleFile);
const bundleVariables = {};
const importPaths = [];
allFiles.forEach((file) => {
createFileBundle(`${this.options.bundle.scope}.${file.contract}`, file.filename, bundlePath, importPaths, bundleVariables);
});
const ast = recursiveModuleBundle(bundleVariables);
const nodes = [...importPaths, ...ast];
// @ts-ignore
let code = generate(t.program(nodes)).code;
if (this.options?.bundle?.bundlePath) {
mkdirp(this.options?.bundle?.bundlePath);
}
mkdirp(this.outPath);
if (code.trim() === '')
code = 'export {};';
writeFileSync(bundlePath, header + code);
}
}