-
-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
395 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import path from "node:path"; | ||
|
||
import tiged from "tiged"; | ||
|
||
export const cloneRegistry = async (src?: string) => { | ||
// eslint-disable-next-line no-param-reassign | ||
src = src || process.env.GRAZ_REGISTRY_SRC || "github:cosmos/chain-registry"; | ||
const emitter = tiged(src, { force: true, mode: "tar" }); | ||
await emitter.clone(path.resolve(__dirname, "../../registry")); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import path from "node:path"; | ||
|
||
import type { Options as GlobbyOptions } from "globby"; | ||
import { globby } from "globby"; | ||
|
||
export const getChainPaths = async ({ | ||
mainnetFilter: mf, | ||
testnetFilter: tf, | ||
}: { | ||
mainnetFilter?: string[]; | ||
testnetFilter?: string[]; | ||
} = {}) => { | ||
const globOpts: GlobbyOptions = { | ||
cwd: path.resolve(__dirname, "../../registry"), | ||
onlyDirectories: true, | ||
}; | ||
|
||
const [mainnetPaths, testnetPaths] = await Promise.all([ | ||
globby([...(mf && mf.length > 0 ? mf : ["*"]), "!_*", "!testnets"], globOpts), | ||
globby([...(tf && tf.length > 0 ? tf.map((f) => `testnets/${f}`) : ["testnets/*"]), "!testnets/_*"], globOpts), | ||
]); | ||
|
||
return { mainnetPaths, testnetPaths }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,73 @@ | ||
// TODO | ||
import fs from "node:fs/promises"; | ||
import os from "node:os"; | ||
|
||
import * as p from "@clack/prompts"; | ||
import { Command } from "commander"; | ||
import pMap from "p-map"; | ||
|
||
import { cloneRegistry } from "./clone-registry"; | ||
import { getChainPaths } from "./get-chain-paths"; | ||
import { makeRootSources } from "./make-root-sources"; | ||
import { makeSources } from "./make-sources"; | ||
|
||
const cli = async () => { | ||
const program = new Command(); | ||
|
||
program | ||
.name("graz") | ||
.description("React hooks for Cosmos") | ||
.addHelpText("afterAll", "\nhttps://github.com/strangelove-ventures/graz\n"); | ||
|
||
program | ||
.command("generate") | ||
.description('generate typescript chain definitions and export to "graz/chains"') | ||
.option( | ||
"-R, --registry <url>", | ||
"specify a custom chain registry namespace (e.g. org/repo, github:org/repo, gitlab:org/repo)", | ||
) | ||
.option( | ||
"-M, --mainnet <chainPaths...>", | ||
'generate given mainnet chain paths separated by spaces (e.g. "axelar cosmoshub juno")', | ||
) | ||
.option( | ||
"-T, --testnet <chainPaths...>", | ||
'generate given testnet chain paths separated by spaces (e.g. "atlantic bitcannadev cheqdtestnet")', | ||
) | ||
.action(async (options) => { | ||
const customRegistry = options.registry as string | undefined; | ||
const mainnetFilter = options.mainnet as string[] | undefined; | ||
const testnetFilter = options.testnet as string[] | undefined; | ||
|
||
p.intro("graz generate"); | ||
const s = p.spinner(); | ||
|
||
// p.log.step("Cloning chain registry..."); | ||
s.start(`Cloning chain registry`); | ||
await cloneRegistry(customRegistry); | ||
s.stop("Cloned chain registry ✅"); | ||
|
||
// p.log.step("Retrieving chain paths..."); | ||
s.start("Retrieving chain paths"); | ||
const { mainnetPaths, testnetPaths } = await getChainPaths({ mainnetFilter, testnetFilter }); | ||
s.stop("Retrieved chain paths ✅"); | ||
|
||
// p.log.step("Generating chain sources..."); | ||
s.start("Generating chain sources"); | ||
await fs.rm("chains/", { recursive: true, force: true }); | ||
await pMap([...mainnetPaths, ...testnetPaths], makeSources, { | ||
concurrency: Math.max(1, (os.cpus() || { length: 1 }).length - 1), | ||
}); | ||
s.stop("Generated chain sources ✅"); | ||
|
||
// p.log.step("Generating chain index..."); | ||
s.start("Generating chain index"); | ||
await makeRootSources({ mainnetPaths, testnetPaths }); | ||
s.stop("Generated chain index ✅"); | ||
|
||
p.outro('Generate complete! You can import `mainnetChains` and `testnetChains` from "graz/chains". 🎉'); | ||
}); | ||
|
||
await program.parseAsync(); | ||
}; | ||
|
||
void cli(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import fs from "node:fs/promises"; | ||
import path from "node:path"; | ||
|
||
import { CodeGenerator } from "@babel/generator"; | ||
import { parse } from "@babel/parser"; | ||
import traverse from "@babel/traverse"; | ||
import * as t from "@babel/types"; | ||
|
||
export const makeRootSources = async ({ | ||
mainnetPaths, | ||
testnetPaths, | ||
}: { | ||
mainnetPaths: string[]; | ||
testnetPaths: string[]; | ||
}) => { | ||
// eslint-disable-next-line no-param-reassign | ||
testnetPaths = testnetPaths.map((p) => p.replace("testnets/", "")); | ||
|
||
const chainsGeneratedStub = await fs.readFile( | ||
path.resolve(__dirname, "../../stubs/chains-generated.ts.stub"), | ||
"utf-8", | ||
); | ||
const chainsGeneratedAst = parse(chainsGeneratedStub, { sourceType: "module", plugins: ["typescript"] }); | ||
|
||
const mainnetAstKeyvals = mainnetPaths.map((chainPath) => { | ||
return t.objectMethod( | ||
"get", | ||
t.stringLiteral(chainPath), | ||
[], | ||
t.blockStatement([ | ||
t.returnStatement( | ||
t.memberExpression( | ||
t.callExpression(t.identifier("require"), [t.stringLiteral(`./${chainPath}`)]), | ||
t.identifier("default"), | ||
), | ||
), | ||
]), | ||
); | ||
}); | ||
|
||
const testnetAstKeyvals = testnetPaths.map((chainPath) => { | ||
return t.objectMethod( | ||
"get", | ||
t.stringLiteral(chainPath), | ||
[], | ||
t.blockStatement([ | ||
t.returnStatement( | ||
t.memberExpression( | ||
t.callExpression(t.identifier("require"), [t.stringLiteral(`./${chainPath}`)]), | ||
t.identifier("default"), | ||
), | ||
), | ||
]), | ||
); | ||
}); | ||
|
||
traverse(chainsGeneratedAst, { | ||
TSTypeAliasDeclaration: (current) => { | ||
if (t.isIdentifier(current.node.id, { name: "MainnetChainName" })) { | ||
current.node.typeAnnotation = t.tsUnionType(mainnetPaths.map((p) => t.tsLiteralType(t.stringLiteral(p)))); | ||
current.skip(); | ||
} | ||
if (t.isIdentifier(current.node.id, { name: "TestnetChainName" })) { | ||
current.node.typeAnnotation = t.tsUnionType(testnetPaths.map((p) => t.tsLiteralType(t.stringLiteral(p)))); | ||
current.skip(); | ||
} | ||
}, | ||
VariableDeclarator: (current) => { | ||
if (t.isIdentifier(current.node.id, { name: "mainnetChains" })) { | ||
current.node.init = t.objectExpression(mainnetAstKeyvals.sort()); | ||
current.skip(); | ||
} | ||
if (t.isIdentifier(current.node.id, { name: "testnetChains" })) { | ||
current.node.init = t.objectExpression(testnetAstKeyvals.sort()); | ||
current.skip(); | ||
} | ||
if (t.isIdentifier(current.node.id, { name: "chains" })) { | ||
current.node.init = t.objectExpression([...mainnetAstKeyvals, ...testnetAstKeyvals].sort()); | ||
current.skip(); | ||
} | ||
if (t.isIdentifier(current.node.id, { name: "mainnetChainNames" })) { | ||
current.node.init = t.arrayExpression(mainnetPaths.map((p) => t.stringLiteral(p))); | ||
current.skip(); | ||
} | ||
if (t.isIdentifier(current.node.id, { name: "testnetChainNames" })) { | ||
current.node.init = t.arrayExpression(testnetPaths.map((p) => t.stringLiteral(p))); | ||
current.skip(); | ||
} | ||
if (t.isIdentifier(current.node.id, { name: "chainNames" })) { | ||
current.node.init = t.arrayExpression([...mainnetPaths, ...testnetPaths].map((p) => t.stringLiteral(p))); | ||
current.skip(); | ||
} | ||
}, | ||
}); | ||
|
||
const { code: chainsGeneratedCode } = new CodeGenerator(chainsGeneratedAst).generate(); | ||
await fs.writeFile(path.resolve(__dirname, "../../chains/generated.ts"), chainsGeneratedCode, "utf-8"); | ||
|
||
await fs.copyFile( | ||
path.resolve(__dirname, "../../stubs/chains-index.ts.stub"), | ||
path.resolve(__dirname, "../../chains/index.ts"), | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import fs from "node:fs/promises"; | ||
import path from "node:path"; | ||
|
||
import { CodeGenerator } from "@babel/generator"; | ||
import * as t from "@babel/types"; | ||
import type { ChainInfo } from "@keplr-wallet/types"; | ||
|
||
import { registryToChainInfo } from "../registry/keplr"; | ||
import type { AssetList, Chain } from "../types/registry"; | ||
|
||
export const makeSources = async (chainPath: string) => { | ||
const actualChainPath = chainPath.replace("testnets/", ""); | ||
|
||
await fs.mkdir(path.resolve(__dirname, `../../chains/${actualChainPath}`), { recursive: true }); | ||
|
||
let assetlist: AssetList; | ||
try { | ||
const assetlistContent = await fs.readFile( | ||
path.resolve(__dirname, `../../registry/${chainPath}/assetlist.json`), | ||
"utf-8", | ||
); | ||
assetlist = JSON.parse(assetlistContent) as AssetList; | ||
} catch { | ||
assetlist = { | ||
assets: [], | ||
chain_name: chainPath, | ||
}; | ||
} | ||
|
||
/** | ||
* chains/[chainPath]/assetlist.ts | ||
* ```js | ||
* import { defineAssetList } from "../../dist"; | ||
* export default defineAssetList({ ... }); | ||
* ``` | ||
*/ | ||
const assetlistAst = t.program([ | ||
t.importDeclaration( | ||
[t.importSpecifier(t.identifier("defineAssetList"), t.identifier("defineAssetList"))], | ||
t.stringLiteral("../../dist"), | ||
), | ||
t.exportDefaultDeclaration(t.callExpression(t.identifier("defineAssetList"), [t.valueToNode(assetlist)])), | ||
]); | ||
|
||
const { code: assetlistCode } = new CodeGenerator(assetlistAst).generate(); | ||
await fs.writeFile( | ||
path.resolve(__dirname, `../../chains/${actualChainPath}/assetlist.ts`), | ||
`/* eslint-disable */\n${assetlistCode}`, | ||
"utf-8", | ||
); | ||
|
||
const chainContent = await fs.readFile(path.resolve(__dirname, `../../registry/${chainPath}/chain.json`), "utf-8"); | ||
const chain = JSON.parse(chainContent) as Chain; | ||
|
||
/** | ||
* chains/[chainPath]/chain.ts | ||
* ```js | ||
* import { defineRegistryChain } from "../../dist"; | ||
* export default defineRegistryChain({ ... }); | ||
* ``` | ||
*/ | ||
const chainAst = t.program([ | ||
t.importDeclaration( | ||
[t.importSpecifier(t.identifier("defineRegistryChain"), t.identifier("defineRegistryChain"))], | ||
t.stringLiteral("../../dist"), | ||
), | ||
t.exportDefaultDeclaration(t.callExpression(t.identifier("defineRegistryChain"), [t.valueToNode(chain)])), | ||
]); | ||
|
||
const { code: chainCode } = new CodeGenerator(chainAst).generate(); | ||
await fs.writeFile( | ||
path.resolve(__dirname, `../../chains/${actualChainPath}/chain.ts`), | ||
`/* eslint-disable */\n${chainCode}`, | ||
"utf-8", | ||
); | ||
|
||
let chainInfo: ChainInfo | undefined; | ||
if (assetlist.assets.length > 0) { | ||
chainInfo = registryToChainInfo({ assetlist, chain }); | ||
} | ||
|
||
/** | ||
* chains/[chainPath]/index.ts | ||
* ```js | ||
* import { defineChainInfo } from "../../dist"; | ||
* export default defineChainInfo({ ... }); | ||
* ``` | ||
*/ | ||
const indexAst = t.program( | ||
chainInfo | ||
? [ | ||
t.importDeclaration( | ||
[t.importSpecifier(t.identifier("defineChainInfo"), t.identifier("defineChainInfo"))], | ||
t.stringLiteral("../../dist"), | ||
), | ||
t.exportDefaultDeclaration(t.callExpression(t.identifier("defineChainInfo"), [t.valueToNode(chainInfo)])), | ||
] | ||
: [ | ||
t.expressionStatement( | ||
t.callExpression(t.memberExpression(t.identifier("console"), t.identifier("error")), [ | ||
t.stringLiteral(`chain info for '${chain.chain_name}' is not generated due to invalid assetlist`), | ||
]), | ||
), | ||
t.exportDefaultDeclaration(t.objectExpression([])), | ||
], | ||
); | ||
|
||
const { code: indexCode } = new CodeGenerator(indexAst).generate(); | ||
await fs.writeFile( | ||
path.resolve(__dirname, `../../chains/${actualChainPath}/index.ts`), | ||
`/* eslint-disable */\n${indexCode}`, | ||
"utf-8", | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type { ChainInfo } from "@keplr-wallet/types"; | ||
|
||
import type { AssetList, Chain } from "../dist"; | ||
|
||
export type MainnetChainName = "REPLACE_MAINNET_CHAIN_NAME"; | ||
export type TestnetChainName = "REPLACE_TESTNET_CHAIN_NAME"; | ||
export type ChainName = MainnetChainName | TestnetChainName; | ||
export interface ChainData { | ||
assetlist: Promise<AssetList>; | ||
chain: Promise<Chain>; | ||
chainInfo: Promise<ChainInfo>; | ||
} | ||
export type ReturnTuple<T> = T extends readonly [ChainName, ...infer Rest] ? [ChainData, ...ReturnTuple<Rest>] : []; | ||
|
||
export const mainnetChains: Record<MainnetChainName, Chain> = {}; | ||
export const testnetChains: Record<TestnetChainName, Chain> = {}; | ||
export const chains: Record<ChainName, Chain> = {}; | ||
export const mainnetChainNames: MainnetChainName[] = []; | ||
export const testnetChainNames: TestnetChainName[] = []; | ||
export const chainNames: ChainName[] = []; |
Oops, something went wrong.