From 9edd1787bc2781224c9a70b1d5f8856bb2ba45c4 Mon Sep 17 00:00:00 2001 From: dk1a Date: Sun, 19 Feb 2023 04:15:12 +0300 Subject: [PATCH 01/13] feat(cli): refactor to esm --- packages/cli/package.json | 12 +- packages/cli/src/commands/bulkupload.ts | 76 +-- packages/cli/src/commands/call-system.ts | 92 ++-- .../cli/src/commands/codegen-libdeploy.ts | 33 +- packages/cli/src/commands/create.ts | 39 +- packages/cli/src/commands/deploy-contracts.ts | 178 ++++--- packages/cli/src/commands/deploy.ts | 438 ------------------ packages/cli/src/commands/devnode.ts | 44 +- packages/cli/src/commands/diamond-abi.ts | 58 --- packages/cli/src/commands/faucet.ts | 78 ++-- packages/cli/src/commands/gas-report.ts | 101 ++-- packages/cli/src/commands/hello.ts | 33 +- packages/cli/src/commands/index.ts | 32 ++ packages/cli/src/commands/sync-art.ts | 47 -- packages/cli/src/commands/system-types.ts | 35 +- packages/cli/src/commands/test.ts | 77 +-- packages/cli/src/commands/trace.ts | 109 ++--- packages/cli/src/commands/types.ts | 44 +- packages/cli/src/index.ts | 17 +- packages/cli/src/mud.ts | 21 + packages/cli/src/utils/codegen.ts | 6 +- packages/cli/src/utils/config.ts | 24 + packages/cli/src/utils/deferred.ts | 14 - packages/cli/src/utils/deploy.ts | 15 +- packages/cli/src/utils/exec.ts | 43 +- packages/cli/src/utils/ids.ts | 2 +- packages/cli/src/utils/index.ts | 17 +- .../cli/src/utils/{types.ts => typegen.ts} | 55 +-- packages/cli/tsconfig.json | 8 +- packages/cli/tsup.config.js | 13 + yarn.lock | 167 ++++++- 31 files changed, 831 insertions(+), 1097 deletions(-) delete mode 100644 packages/cli/src/commands/deploy.ts delete mode 100644 packages/cli/src/commands/diamond-abi.ts create mode 100644 packages/cli/src/commands/index.ts delete mode 100644 packages/cli/src/commands/sync-art.ts mode change 100644 => 100755 packages/cli/src/index.ts create mode 100755 packages/cli/src/mud.ts create mode 100644 packages/cli/src/utils/config.ts delete mode 100644 packages/cli/src/utils/deferred.ts rename packages/cli/src/utils/{types.ts => typegen.ts} (78%) create mode 100644 packages/cli/tsup.config.js diff --git a/packages/cli/package.json b/packages/cli/package.json index e3eab4e553..3aea738825 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -2,10 +2,12 @@ "name": "@latticexyz/cli", "version": "1.37.1", "description": "Command line interface for mud", - "main": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "type": "module", "license": "MIT", "bin": { - "mud": "./dist/index.js" + "mud": "./dist/mud.js" }, "repository": { "type": "git", @@ -14,9 +16,9 @@ }, "scripts": { "prepare": "yarn build && chmod u+x git-install.sh", - "build": "rimraf dist && esbuild ./src/index.ts ./src/commands/* --bundle --platform=node --outdir=dist --external:fsevents && chmod u+x dist/index.js", + "build": "tsup", "link": "yarn link", - "test": "tsc && echo 'todo: add tests'", + "test": "tsc --noEmit && echo 'todo: add tests'", "git:install": "bash git-install.sh", "release": "npm publish || echo 'version already published'" }, @@ -34,6 +36,7 @@ "pkg": "^5.7.0", "rimraf": "^3.0.2", "ts-node": "^10.7.0", + "tsup": "^6.6.3", "typescript": "^4.6.4" }, "dependencies": { @@ -42,7 +45,6 @@ "@latticexyz/services": "^1.37.1", "@latticexyz/solecs": "^1.37.1", "@latticexyz/std-contracts": "^1.37.1", - "@latticexyz/utils": "^1.37.1", "@typechain/ethers-v5": "^10.1.1", "chalk": "^5.0.1", "chokidar": "^3.5.3", diff --git a/packages/cli/src/commands/bulkupload.ts b/packages/cli/src/commands/bulkupload.ts index 1bbca6d4ee..ac840b4d54 100644 --- a/packages/cli/src/commands/bulkupload.ts +++ b/packages/cli/src/commands/bulkupload.ts @@ -1,6 +1,8 @@ -import { Arguments, CommandBuilder } from "yargs"; +import { execa } from "execa"; +import path from "path"; +import type { CommandModule } from "yargs"; -const importExeca = eval('import("execa")') as Promise; +const contractsDirectory = new URL("../src/contracts", import.meta.url).pathname; type Options = { statePath: string; @@ -8,37 +10,41 @@ type Options = { rpc: string; }; -export const command = "bulkupload"; -export const desc = "Uploads the provided ECS state to the provided World"; - -export const builder: CommandBuilder = (yargs) => - yargs.options({ - statePath: { type: "string", demandOption: true, desc: "Path to the ECS state to upload" }, - worldAddress: { type: "string", demandOption: true, desc: "Contract address of the World to upload to" }, - rpc: { type: "string", demandOption: true, desc: "JSON RPC endpoint" }, - }); - -export const handler = async (argv: Arguments): Promise => { - const { execa } = await importExeca; - const { statePath, worldAddress, rpc } = argv; - console.log("Uploading state at ", statePath, "to", worldAddress, "on", rpc); - const url = __dirname + "/../../src/contracts/BulkUpload.sol"; - console.log("Using BulkUpload script from", url); - - try { - await execa("forge", [ - "script", - "--sig", - '"run(string, address)"', - "--rpc-url", - rpc, - `${url}:BulkUpload`, - statePath, - worldAddress, - ]); - } catch (e) { - console.error(e); - } - - process.exit(0); +const commandModule: CommandModule = { + command: "bulkupload", + + describe: "Uploads the provided ECS state to the provided World", + + builder(yargs) { + return yargs.options({ + statePath: { type: "string", demandOption: true, desc: "Path to the ECS state to upload" }, + worldAddress: { type: "string", demandOption: true, desc: "Contract address of the World to upload to" }, + rpc: { type: "string", demandOption: true, desc: "JSON RPC endpoint" }, + }); + }, + + async handler({ statePath, worldAddress, rpc }) { + console.log("Uploading state at ", statePath, "to", worldAddress, "on", rpc); + const url = path.join(contractsDirectory, "BulkUpload.sol"); + console.log("Using BulkUpload script from", url); + + try { + await execa("forge", [ + "script", + "--sig", + '"run(string, address)"', + "--rpc-url", + rpc, + `${url}:BulkUpload`, + statePath, + worldAddress, + ]); + } catch (e) { + console.error(e); + } + + process.exit(0); + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/call-system.ts b/packages/cli/src/commands/call-system.ts index a0739d19b8..9b78a5af19 100644 --- a/packages/cli/src/commands/call-system.ts +++ b/packages/cli/src/commands/call-system.ts @@ -1,7 +1,7 @@ -import { defaultAbiCoder as abi } from "ethers/lib/utils"; +import { defaultAbiCoder as abi } from "ethers/lib/utils.js"; import path from "path"; -import { Arguments, CommandBuilder } from "yargs"; -import { execLog } from "../utils"; +import type { CommandModule } from "yargs"; +import { execLog } from "../utils/index.js"; import { getTestDirectory } from "../utils/forgeConfig"; type Options = { @@ -18,49 +18,53 @@ type Options = { debug?: boolean; }; -export const command = "call-system"; -export const desc = "Execute a mud system"; +const commandModule: CommandModule = { + command: "call-system", -export const builder: CommandBuilder = (yargs) => - yargs.options({ - rpc: { type: "string", description: "json rpc endpoint, defaults to http://localhost:8545" }, - caller: { type: "string", description: "caller address" }, - world: { type: "string", required: true, description: "world contract address" }, - systemId: { type: "string", description: "system id preimage (eg mud.system.Move)" }, - systemAddress: { type: "string", description: "system address (alternative to system id)" }, - argTypes: { type: "array", description: "system argument types for abi encoding" }, - args: { type: "array", description: "system arguments" }, - calldata: { type: "string", description: "abi encoded system arguments (instead of args/argTypes)" }, - broadcast: { type: "boolean", description: "send txs to the chain" }, - callerPrivateKey: { - type: "string", - description: "must be set if broadcast is set, must correspond to caller address", - }, - debug: { type: "boolean", description: "open debugger" }, - }); + describe: "Execute a mud system", -export const handler = async (argv: Arguments): Promise => { - const { rpc, caller, world, systemId, argTypes, args, calldata, broadcast, callerPrivateKey, debug } = argv; - const encodedArgs = calldata ?? (argTypes && args && abi.encode(argTypes, args)) ?? ""; - const testDir = await getTestDirectory(); + builder(yargs) { + return yargs.options({ + rpc: { type: "string", description: "json rpc endpoint, defaults to http://localhost:8545" }, + caller: { type: "string", description: "caller address" }, + world: { type: "string", required: true, description: "world contract address" }, + systemId: { type: "string", description: "system id preimage (eg mud.system.Move)" }, + systemAddress: { type: "string", description: "system address (alternative to system id)" }, + argTypes: { type: "array", description: "system argument types for abi encoding" }, + args: { type: "array", description: "system arguments" }, + calldata: { type: "string", description: "abi encoded system arguments (instead of args/argTypes)" }, + broadcast: { type: "boolean", description: "send txs to the chain" }, + callerPrivateKey: { + type: "string", + description: "must be set if broadcast is set, must correspond to caller address", + }, + debug: { type: "boolean", description: "open debugger" }, + }); + }, - await execLog("forge", [ - "script", - "--fork-url", - rpc ?? "http://localhost:8545", // default anvil rpc - "--sig", - "debug(address,address,string,bytes,bool)", - path.join(testDir, "utils/Debug.sol"), // the cli expects the Debug.sol file at this path - caller ?? "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", // default anvil deployer - world, - systemId || "", - encodedArgs, - broadcast ? "true" : "false", - "-vvvvv", - broadcast ? "--broadcast" : "", - callerPrivateKey ? `--private-key ${callerPrivateKey}` : "", - debug ? "--debug" : "", - ]); + async handler({ rpc, caller, world, systemId, argTypes, args, calldata, broadcast, callerPrivateKey, debug }) { + const encodedArgs = calldata ?? (argTypes && args && abi.encode(argTypes, args)) ?? ""; + const testDir = await getTestDirectory(); + await execLog("forge", [ + "script", + "--fork-url", + rpc ?? "http://localhost:8545", // default anvil rpc + "--sig", + "debug(address,address,string,bytes,bool)", + path.join(testDir, "utils/Debug.sol"), // the cli expects the Debug.sol file at this path + caller ?? "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", // default anvil deployer + world, + systemId || "", + encodedArgs, + broadcast ? "true" : "false", + "-vvvvv", + broadcast ? "--broadcast" : "", + callerPrivateKey ? `--private-key ${callerPrivateKey}` : "", + debug ? "--debug" : "", + ]); - process.exit(0); + process.exit(0); + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/codegen-libdeploy.ts b/packages/cli/src/commands/codegen-libdeploy.ts index 333947e54a..94a112434d 100644 --- a/packages/cli/src/commands/codegen-libdeploy.ts +++ b/packages/cli/src/commands/codegen-libdeploy.ts @@ -1,5 +1,5 @@ -import type { Arguments, CommandBuilder } from "yargs"; -import { generateLibDeploy } from "../utils"; +import type { CommandModule } from "yargs"; +import { generateLibDeploy } from "../utils/index.js"; type Options = { config: string; @@ -7,18 +7,23 @@ type Options = { systems?: string; }; -export const command = "codegen-libdeploy"; -export const desc = "Generate LibDeploy.sol from given deploy config"; +const commandModule: CommandModule = { + command: "codegen-libdeploy", -export const builder: CommandBuilder = (yargs) => - yargs.options({ - config: { type: "string", default: "./deploy.json", desc: "Component and system deployment configuration" }, - out: { type: "string", default: ".", desc: "Output directory for LibDeploy.sol" }, - systems: { type: "string", desc: "Only generate deploy code for the given systems" }, - }); + describe: "Generate LibDeploy.sol from given deploy config", -export const handler = async (args: Arguments): Promise => { - const { config, out, systems } = args; - await generateLibDeploy(config, out, systems); - process.exit(0); + builder(yargs) { + return yargs.options({ + config: { type: "string", default: "./deploy.json", desc: "Component and system deployment configuration" }, + out: { type: "string", default: ".", desc: "Output directory for LibDeploy.sol" }, + systems: { type: "string", desc: "Only generate deploy code for the given systems" }, + }); + }, + + async handler({ config, out, systems }) { + await generateLibDeploy(config, out, systems); + process.exit(0); + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/create.ts b/packages/cli/src/commands/create.ts index 310443f742..035a41bd1c 100644 --- a/packages/cli/src/commands/create.ts +++ b/packages/cli/src/commands/create.ts @@ -1,5 +1,5 @@ -import { Arguments, CommandBuilder } from "yargs"; -import { execLog } from "../utils"; +import type { CommandModule } from "yargs"; +import { execLog } from "../utils/index.js"; import chalk from "chalk"; type Options = { @@ -7,24 +7,29 @@ type Options = { template: string; }; -export const command = "create "; -export const desc = "Sets up a mud project into . Requires yarn."; +const commandModule: CommandModule = { + command: "create ", -export const deprecated = true; + deprecated: true, -export const builder: CommandBuilder = (yargs) => - yargs - .options({ - template: { type: "string", desc: "Template to be used (available: [minimal, react])", default: "minimal" }, - }) - .positional("name", { type: "string", default: "mud-app" }); + describe: "Sets up a mud project into . Requires yarn.", -export const handler = async (argv: Arguments): Promise => { - const { name, template } = argv; - console.log(chalk.red.bold("⚠️ This command will be removed soon. Use `yarn create mud` instead.")); + builder(yargs) { + return yargs + .options({ + template: { type: "string", desc: "Template to be used (available: [minimal, react])", default: "minimal" }, + }) + .positional("name", { type: "string", default: "mud-app" }); + }, - const result = await execLog("yarn", ["create", "mud", name, "--template", template]); - if (result.exitCode != 0) process.exit(result.exitCode); + async handler({ name, template }) { + console.log(chalk.red.bold("⚠️ This command will be removed soon. Use `yarn create mud` instead.")); - process.exit(0); + const child = await execLog("yarn", ["create", "mud", name, "--template", template]); + if (child.exitCode != 0) process.exit(child.exitCode); + + process.exit(0); + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/deploy-contracts.ts b/packages/cli/src/commands/deploy-contracts.ts index 50e82b0c4d..ad9dbbd047 100644 --- a/packages/cli/src/commands/deploy-contracts.ts +++ b/packages/cli/src/commands/deploy-contracts.ts @@ -1,5 +1,5 @@ -import type { Arguments, CommandBuilder } from "yargs"; -import { DeployOptions, generateAndDeploy, hsr } from "../utils"; +import type { CommandModule } from "yargs"; +import { DeployOptions, generateAndDeploy, hsr } from "../utils/index.js"; import openurl from "openurl"; import chalk from "chalk"; import { getSrcDirectory } from "../utils/forgeConfig"; @@ -10,87 +10,111 @@ type Options = DeployOptions & { openUrl?: string; }; -export const command = "deploy-contracts"; -export const desc = "Deploy mud contracts"; +const commandModule: CommandModule = { + command: "deploy-contracts", -export const builder: CommandBuilder = (yargs) => - yargs.options({ - config: { type: "string", default: "./deploy.json", desc: "Component and system deployment configuration" }, - deployerPrivateKey: { type: "string", desc: "Deployer private key. If omitted, deployment is not broadcasted." }, - worldAddress: { type: "string", desc: "World address to deploy to. If omitted, a new World is deployed." }, - rpc: { type: "string", default: "http://localhost:8545", desc: "RPC URL of the network to deploy to." }, - systems: { type: "string", desc: "Only upgrade the given systems. Requires World address." }, - watch: { type: "boolean", desc: "Automatically redeploy changed systems" }, - dev: { type: "boolean", desc: "Automatically use funded dev private key for local development" }, - openUrl: { type: "string", desc: "Opens a browser at the provided url with the worldAddress url param prefilled" }, - gasPrice: { type: "number", desc: "Gas price to set for deploy transactions" }, - }); + describe: "Deploy mud contracts", -export const handler = async (args: Arguments): Promise => { - if (args.system != null && !args.worldAddress) { - console.error("Error: Upgrading systems requires a World address."); - process.exit(1); - } - - const deployerPrivateKey = - args.deployerPrivateKey ?? - (args.dev - ? "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" // Default anvil private key - : undefined); - - // Deploy world, components and systems - let genDeployResult: Awaited>; - try { - genDeployResult = await generateAndDeploy({ - ...args, - deployerPrivateKey, - clear: true, + builder(yargs) { + return yargs.options({ + config: { type: "string", default: "./deploy.json", desc: "Component and system deployment configuration" }, + deployerPrivateKey: { type: "string", desc: "Deployer private key. If omitted, deployment is not broadcasted." }, + worldAddress: { type: "string", desc: "World address to deploy to. If omitted, a new World is deployed." }, + rpc: { type: "string", default: "http://localhost:8545", desc: "RPC URL of the network to deploy to." }, + systems: { type: "string", desc: "Only upgrade the given systems. Requires World address." }, + reuseComponents: { type: "boolean", desc: "Skip deploying components and initialization." }, + watch: { type: "boolean", desc: "Automatically redeploy changed systems" }, + dev: { type: "boolean", desc: "Automatically use funded dev private key for local development" }, + openUrl: { + type: "string", + desc: "Opens a browser at the provided url with the worldAddress url param prefilled", + }, + gasPrice: { type: "number", desc: "Gas price to set for deploy transactions" }, }); - } catch (e: any) { - if (!e.stderr) { - // log error if it wasn't piped - console.log(e); + }, + + async handler({ + config, + deployerPrivateKey, + worldAddress, + rpc, + systems, + reuseComponents, + watch, + dev, + openUrl, + gasPrice, + }) { + if (systems != null && !worldAddress) { + console.error("Error: Upgrading systems requires a World address."); + process.exit(1); } - console.log(chalk.red("\n-----------\nError during generateAndDeploy (see above)")); - process.exit(); - } - const { deployedWorldAddress: worldAddress, initialBlockNumber } = genDeployResult; - console.log("World deployed at", worldAddress, "at block", initialBlockNumber); - if (worldAddress && args.openUrl) { - const url = new URL(args.openUrl); - url.searchParams.set("worldAddress", worldAddress); - console.log(""); - console.log(chalk.cyan("Opening client URL to", url.toString())); - console.log(""); - openurl.open(url.toString()); - } + deployerPrivateKey = + deployerPrivateKey ?? + (dev + ? "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" // Default anvil private key + : undefined); - // Set up watcher for system files to redeploy on change - if (args.watch) { - const { config, rpc, gasPrice } = args; - const srcDir = await getSrcDirectory(); + // Deploy world, components and systems + let genDeployResult: Awaited>; + try { + genDeployResult = await generateAndDeploy({ + config, + deployerPrivateKey, + worldAddress, + rpc, + systems, + reuseComponents, + clear: true, + gasPrice, + }); + } catch (e: any) { + if (!e.stderr) { + // log error if it wasn't piped + console.log(e); + } + console.log(chalk.red("\n-----------\nError during generateAndDeploy (see above)")); + process.exit(); + } + const { deployedWorldAddress, initialBlockNumber } = genDeployResult; + console.log("World deployed at", worldAddress, "at block", initialBlockNumber); - hsr(srcDir, async (systems: string[]) => { - try { - return await generateAndDeploy({ - config, - deployerPrivateKey, - worldAddress, - rpc, - systems, - gasPrice, - reuseComponents: true, - }); - } catch (e: any) { - if (!e.stderr) { - // log error if it wasn't piped - console.log(e); + if (deployedWorldAddress && openUrl) { + const url = new URL(openUrl); + url.searchParams.set("worldAddress", deployedWorldAddress); + console.log(""); + console.log(chalk.cyan("Opening client URL to", url.toString())); + console.log(""); + openurl.open(url.toString()); + } + + // Set up watcher for system files to redeploy on change + if (watch) { + const srcDir = await getSrcDirectory(); + hsr(srcDir, async (systems: string[]) => { + try { + return await generateAndDeploy({ + config, + deployerPrivateKey, + worldAddress, + rpc, + systems, + gasPrice, + reuseComponents: true, + }); + } catch (e: any) { + if (!e.stderr) { + // log error if it wasn't piped + console.log(e); + } + console.log(chalk.red("\n-----------\nError during generateAndDeploy in HSR (see above)")); } - console.log(chalk.red("\n-----------\nError during generateAndDeploy in HSR (see above)")); - } - }); - } else { - process.exit(0); - } + }); + } else { + process.exit(0); + } + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts deleted file mode 100644 index 9d3924a729..0000000000 --- a/packages/cli/src/commands/deploy.ts +++ /dev/null @@ -1,438 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -// eslint-disable-next-line @typescript-eslint/no-var-requires -import { ethers } from "ethers"; -import inquirer from "inquirer"; -import { v4 } from "uuid"; -import { Listr, Logger } from "listr2"; -import { exit } from "process"; -import fs from "fs"; -import openurl from "openurl"; -import ips from "inquirer-prompt-suggest"; -import { Arguments, CommandBuilder } from "yargs"; -import { generateAndDeploy } from "../utils"; - -inquirer.registerPrompt("suggest", ips); - -// @dev Note: this deployment command is deprecated and will be removed in a future version. Use `mud deploy-contracts` instead. - -// Workaround to prevent tsc to transpile dynamic imports with require, which causes an error upstream -// https://github.com/microsoft/TypeScript/issues/43329#issuecomment-922544562 -const importNetlify = eval('import("netlify")') as Promise; -const importChalk = eval('import("chalk")') as Promise; -const importExeca = eval('import("execa")') as Promise; -const importFetch = eval('import("node-fetch")') as Promise; - -interface Options { - i?: boolean; - chainSpec?: string; - chainId?: number; - rpc?: string; - wsRpc?: string; - world?: string; - reuseComponents?: boolean; - deployerPrivateKey?: string; - deployClient?: boolean; - clientUrl?: string; - netlifySlug?: string; - netlifyPersonalToken?: string; - codespace?: boolean; - dry?: boolean; - dev?: boolean; -} - -export const command = "deploy"; -export const desc = "Deploys the local mud contracts and optionally the client"; - -export const builder: CommandBuilder = (yargs) => - yargs.options({ - i: { type: "boolean" }, - chainSpec: { type: "string" }, - chainId: { type: "number" }, - rpc: { type: "string" }, - wsRpc: { type: "string" }, - world: { type: "string" }, - reuseComponents: { type: "boolean" }, - deployerPrivateKey: { type: "string" }, - deployClient: { type: "boolean" }, - clientUrl: { type: "string" }, - netlifySlug: { type: "string" }, - netlifyPersonalToken: { type: "string" }, - codespace: { type: "boolean" }, - dry: { type: "boolean" }, - dev: { type: "boolean", default: true }, - }); - -export const handler = async (args: Arguments): Promise => { - const info = await getDeployInfo(args); - await deploy(info); -}; - -function isValidHttpUrl(s: string): boolean { - let url: URL | undefined; - - try { - url = new URL(s); - } catch (_) { - return false; - } - - return url.protocol === "http:" || url.protocol === "https:"; -} - -const getDeployInfo: (args: Arguments) => Promise = async (args) => { - const { default: chalk } = await importChalk; - console.log(); - console.log(chalk.bgWhite.black.bold(" == Mud Deployer == ")); - console.log(); - - let config: Options = {}; - try { - config = JSON.parse(fs.readFileSync("mud.config.json", "utf8")); - } catch (e) { - console.log("No mud.config.json found, using command line args"); - } - - const getNetlifyAccounts = async (token: string) => { - const { NetlifyAPI: netlify } = await importNetlify; - const netlifyAPI = new netlify(token); - console.log("Netlify api"); - const accounts = await netlifyAPI.listAccountsForUser(); - console.log("Accounts"); - return accounts.map((a: { slug: string }) => a.slug); - }; - - const defaultOptions: Options = { - chainSpec: "chainSpec.json", - chainId: 31337, - rpc: "http://localhost:8545", - wsRpc: "ws://localhost:8545", - reuseComponents: false, - deployClient: false, - clientUrl: "http://localhost:3000", - dev: true, - }; - - const { default: fetch } = await importFetch; - - // DISABLED UNTIL NEW REGISTRY IS READY - // Fetch deployed lattice chains - // const latticeChains = args.i - // ? ((await (await fetch("https://registry.lattice.xyz/api?update=true")).json()) as - // | { specUrl: string }[] - // | undefined) - // : []; - - // const chainSpecs = latticeChains?.map((e) => e.specUrl) || []; - // console.log("Available Lattice chains"); - // console.log(JSON.stringify(latticeChains, null, 2)); - - const answers: Options = - args.upgradeSystems && !args.world - ? await inquirer.prompt([ - { - type: "input", - name: "world", - message: "Provide the address of the World contract to upgrade the systems on.", - when: () => args.world == null && config.world == null, - validate: (i) => { - if (!i || (i[0] == "0" && i[1] == "x" && i.length === 42)) return true; - return "Invalid address"; - }, - }, - ]) - : args.i - ? await inquirer.prompt([ - { - type: "suggest", - name: "chainSpec", - message: "Provide a chainSpec.json location (local or remote)", - suggestions: ["https://spec.testnet-chain.linfra.xyz/chainSpec.json"], - when: () => args.chainSpec == null && config.chainSpec == null, - }, - { - type: "number", - name: "chainId", - default: defaultOptions.chainId, - message: "Provide a chainId for the deployment", - when: (answers) => answers.chainSpec == null && args.chainId == null && config.chainSpec == null, - }, - { - type: "input", - name: "wsRpc", - default: defaultOptions.wsRpc, - message: "Provide a WebSocket RPC endpoint for your deployment", - when: (answers) => answers.chainSpec == null && args.wsRpc == null && config.wsRpc == null, - }, - { - type: "input", - name: "rpc", - default: defaultOptions.rpc, - message: "Provide a JSON RPC endpoint for your deployment", - when: (answers) => answers.chainSpec == null && args.rpc == null && config.rpc == null, - validate: (i) => { - if (isValidHttpUrl(i)) return true; - return "Invalid URL"; - }, - }, - { - type: "input", - name: "world", - message: - "Provide the address of an existing World contract. (If none is given, a new World will be deployed.)", - when: () => args.world == null && config.world == null, - validate: (i) => { - if (!i || (i[0] == "0" && i[1] == "x" && i.length === 42)) return true; - return "Invalid address"; - }, - }, - { - type: "list", - name: "reuseComponents", - message: "Reuse existing components?", - choices: [ - { name: "Yes", value: true }, - { name: "No", value: false }, - ], - default: defaultOptions.reuseComponents, - when: (answers) => - !answers.upgradeSystems && - !args.upgradeSystems && - args.reuseComponents == null && - config.reuseComponents == null, - }, - { - type: "input", - name: "deployerPrivateKey", - message: "Enter private key of the deployer account:", - when: () => !args.deployerPrivateKey && !config.deployerPrivateKey, - validate: (i) => { - if (i[0] == "0" && i[1] == "x" && i.length === 66) return true; - return "Invalid private key"; - }, - }, - { - type: "list", - message: "Deploy the client?", - choices: [ - { name: "Yes", value: true }, - { name: "No", value: false }, - ], - default: defaultOptions.deployClient, - name: "deployClient", - when: () => args.deployClient == null && config.deployClient == null, - }, - { - type: "input", - name: "netlifyPersonalToken", - message: "Enter a netlify personal token for deploying the client:", - when: (answers) => answers.deployClient && !args.netlifyPersonalToken && !config.netlifyPersonalToken, - }, - { - type: "list", - message: "From which netlify account?", - choices: async (answers) => - await getNetlifyAccounts( - args.netlifyPersonalToken ?? config.netlifyPersonalToken ?? answers.netlifyPersonalToken! - ), - name: "netlifySlug", - when: (answers) => answers.deployClient && !args.netlifySlug && !config.netlifySlug, - }, - { - type: "input", - name: "clientUrl", - message: "Enter URL of an already deployed client:", - when: (answers) => !answers.deployClient && !args.clientUrl && !config.clientUrl, - default: "http://localhost:3000", - validate: (i) => { - if (isValidHttpUrl(i)) { - if (i[i.length - 1] === "/") { - return "No trailing slash"; - } - return true; - } else { - return "Not a valid URL"; - } - }, - }, - ]) - : ({} as Options); - - const chainSpecUrl = args.chainSpec ?? config.chainSpec ?? answers.chainSpec; - const chainSpec = !chainSpecUrl - ? null - : isValidHttpUrl(chainSpecUrl) - ? await (await fetch(chainSpecUrl)).json() - : JSON.parse(fs.readFileSync(chainSpecUrl, "utf8")); - - // Priority of config source: command line args >> chainSpec >> local config >> interactive answers >> defaults - // -> Command line args can override every other config, interactive questions are only asked if no other config given for this option - - return { - chainSpec: args.chainSpec ?? config.chainSpec ?? answers.chainSpec ?? defaultOptions.chainSpec, - chainId: args.chainId ?? chainSpec?.chainId ?? config.chainId ?? answers.chainId ?? defaultOptions.chainId, - rpc: args.rpc ?? chainSpec?.rpc ?? config.rpc ?? answers.rpc ?? defaultOptions.rpc, - wsRpc: args.wsRpc ?? chainSpec?.wsRpc ?? config.wsRpc ?? answers.wsRpc ?? defaultOptions.wsRpc, - world: args.world ?? chainSpec?.world ?? config.world ?? answers.world, - reuseComponents: - args.reuseComponents ?? config.reuseComponents ?? answers.reuseComponents ?? defaultOptions.reuseComponents, - deployerPrivateKey: args.deployerPrivateKey ?? config.deployerPrivateKey ?? answers.deployerPrivateKey, - deployClient: args.deployClient ?? config.deployClient ?? answers.deployClient ?? defaultOptions.deployClient, - clientUrl: args.clientUrl ?? config.clientUrl ?? answers.clientUrl ?? defaultOptions.clientUrl, - netlifySlug: args.netlifySlug ?? config.netlifySlug ?? answers.netlifySlug, - netlifyPersonalToken: args.netlifyPersonalToken ?? config.netlifyPersonalToken ?? answers.netlifyPersonalToken, - codespace: args.codespace, - dry: args.dry, - dev: args.dev, - }; -}; - -export const deploy = async (options: Options) => { - const { default: chalk } = await importChalk; - const { execa } = await importExeca; - console.log(); - console.log(chalk.yellow(`>> Deploying contracts <<`)); - - console.log("Options"); - console.log(options); - - const wallet = new ethers.Wallet(options.deployerPrivateKey!); - console.log(chalk.red(`>> Deployer address: ${chalk.bgYellow.black.bold(" " + wallet.address + " ")} <<`)); - console.log(); - - const logger = new Logger({ useIcons: true }); - - const { NetlifyAPI: netlify } = await importNetlify; - - const netlifyAPI = options.deployClient && new netlify(options.netlifyPersonalToken); - const id = v4().substring(0, 6); - - let launcherUrl; - let worldAddress; - - try { - const tasks = new Listr([ - { - title: "Deploying", - task: () => { - return new Listr( - [ - { - title: "Contracts", - task: async (ctx, task) => { - const result = await generateAndDeploy({ - config: "./deploy.json", - rpc: options.rpc!, - worldAddress: options.world, - deployerPrivateKey: wallet.privateKey, - reuseComponents: options.reuseComponents, - }); - - ctx.worldAddress = worldAddress = result.deployedWorldAddress; - ctx.initialBlockNumber = result.initialBlockNumber; - - task.output = chalk.yellow(`World deployed at: ${chalk.bgYellow.black(ctx.worldAddress)}`); - }, - options: { bottomBar: 3 }, - }, - { - title: "Client", - task: () => { - return new Listr( - [ - { - title: "Building", - task: async (_, task) => { - const time = Date.now(); - task.output = "Building local client..."; - const child = execa("yarn", ["workspace", "client", "build"]); - await child; - const duration = Date.now() - time; - task.output = "Client built in " + Math.round(duration / 1000) + "s"; - }, - skip: () => !options.deployClient, - options: { bottomBar: 3 }, - }, - { - title: "Creating", - task: async (ctx, task) => { - const site = await netlifyAPI.createSite({ - body: { - name: `mud-deployment-${wallet.address.substring(2, 8)}-${id}`, - account_slug: options.netlifySlug, - ssl: true, - force_ssl: true, - }, - }); - ctx.siteId = site.id; - ctx.clientUrl = site.ssl_url; - task.output = "Netlify site created with id: " + chalk.bgYellow.black(site.id); - }, - skip: () => !options.deployClient, - options: { bottomBar: 1 }, - }, - { - title: "Deploying", - task: async (ctx, task) => { - const child = execa( - "yarn", - ["workspace", "client", "run", "netlify", "deploy", "--prod", "--dir", "dist"], - { - env: { - NETLIFY_AUTH_TOKEN: options.netlifyPersonalToken, - NETLIFY_SITE_ID: ctx.siteId, - }, - } - ); - child.stdout?.pipe(task.stdout()); - await child; - task.output = chalk.yellow("Netlify site deployed!"); - }, - skip: () => !options.deployClient, - options: { bottomBar: 3 }, - }, - ], - { concurrent: false } - ); - }, - }, - { - title: "Open Launcher", - task: async (ctx) => { - function getCodespaceUrl(port: number, protocol = "https") { - return `${protocol}://${process.env["CODESPACE_NAME"]}-${port}.app.online.visualstudio.com`; - } - - let clientUrl = options.deployClient ? ctx.clientUrl : options.clientUrl; - - if (options.codespace) { - clientUrl = getCodespaceUrl(3000); - options.rpc = getCodespaceUrl(8545); - } - - launcherUrl = `${clientUrl}?chainId=${options.chainId}&worldAddress=${ctx.worldAddress}&rpc=${ - options.rpc - }&wsRpc=${options.wsRpc}&initialBlockNumber=${ - ctx.initialBlockNumber - }&snapshot=&stream=&relay=&faucet=${options.dev ? "&dev=true" : ""}`; - - openurl.open(launcherUrl); - }, - options: { bottomBar: 3 }, - }, - ], - { concurrent: false } - ); - }, - }, - ]); - await tasks.run(); - console.log(chalk.bgGreen.black.bold(" Congratulations! Deployment successful")); - console.log(); - console.log(chalk.green(`World address ${worldAddress}`)); - console.log(chalk.green(`Open launcher at ${launcherUrl}`)); - console.log(); - } catch (e) { - logger.fail((e as Error).message); - } - exit(0); -}; diff --git a/packages/cli/src/commands/devnode.ts b/packages/cli/src/commands/devnode.ts index 3c8e6316e3..2ee4051e6c 100644 --- a/packages/cli/src/commands/devnode.ts +++ b/packages/cli/src/commands/devnode.ts @@ -1,31 +1,37 @@ import { rmSync } from "fs"; import { homedir } from "os"; import path from "path"; -import type { Arguments, CommandBuilder } from "yargs"; -import { execLog } from "../utils"; +import type { CommandModule } from "yargs"; +import { execLog } from "../utils/index.js"; type Options = { blocktime: number; }; -export const command = "devnode"; -export const desc = "Start a local Ethereum node for development"; +const commandModule: CommandModule = { + command: "devnode", -export const builder: CommandBuilder = (yargs) => - yargs.options({ - blocktime: { type: "number", default: 1, decs: "Interval in which new blocks are produced" }, - }); + describe: "Start a local Ethereum node for development", -export const handler = async (argv: Arguments): Promise => { - const { blocktime } = argv; - console.log("Clearing devnode history"); - const userHomeDir = homedir(); - rmSync(path.join(userHomeDir, ".foundry", "anvil", "tmp"), { recursive: true, force: true }); - const { child } = await execLog("anvil", ["-b", String(blocktime), "--block-base-fee-per-gas", "0"]); + builder(yargs) { + return yargs.options({ + blocktime: { type: "number", default: 1, decs: "Interval in which new blocks are produced" }, + }); + }, - process.on("SIGINT", () => { - console.log("\ngracefully shutting down from SIGINT (Crtl-C)"); - child.kill(); - process.exit(); - }); + async handler({ blocktime }) { + console.log("Clearing devnode history"); + const userHomeDir = homedir(); + rmSync(path.join(userHomeDir, ".foundry", "anvil", "tmp"), { recursive: true, force: true }); + const child = execLog("anvil", ["-b", String(blocktime), "--block-base-fee-per-gas", "0"]); + + process.on("SIGINT", () => { + console.log("\ngracefully shutting down from SIGINT (Crtl-C)"); + child.kill(); + process.exit(); + }); + await child; + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/diamond-abi.ts b/packages/cli/src/commands/diamond-abi.ts deleted file mode 100644 index 0501489fe7..0000000000 --- a/packages/cli/src/commands/diamond-abi.ts +++ /dev/null @@ -1,58 +0,0 @@ -import fs from "fs"; -import glob from "glob"; -import type { Arguments, CommandBuilder } from "yargs"; -import { deferred } from "../utils"; - -type Options = { - include: (string | number)[] | undefined; - exclude: (string | number)[] | undefined; - out: string | undefined; -}; - -export const command = "diamond-abi"; -export const desc = "Merges the abis of different facets of a diamond to a single diamond abi"; - -export const builder: CommandBuilder = (yargs) => - yargs.options({ - include: { type: "array" }, - exclude: { type: "array" }, - out: { type: "string" }, - }); - -export const handler = async (argv: Arguments): Promise => { - const { include: _include, exclude: _exclude, out: _out } = argv; - const wd = process.cwd(); - console.log("Current working directory:", wd); - - const include = (_include as string[]) || [`${wd}/abi/*Facet.json`]; - const exclude = - (_exclude as string[]) || - ["DiamondCutFacet", "DiamondLoupeFacet", "OwnershipFacet"].map((file) => `./abi/${file}.json`); - const out = _out || `${wd}/abi/CombinedFacets.json`; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const abi: any[] = []; - - for (const path of include) { - const [resolve, , promise] = deferred(); - glob(path, {}, (_, facets) => { - // Merge all abis matching the path glob - const pathAbi = facets - .filter((facet) => !exclude.includes(facet)) - .map((facet) => require(facet)) - .map((abis) => abis.abi) - .flat(1); - - abi.push(...pathAbi); - resolve(); - }); - - // Make the callback syncronous - await promise; - } - - fs.writeFileSync(out, JSON.stringify({ abi })); - - console.log(`Created diamond abi at ${out}`); - process.exit(0); -}; diff --git a/packages/cli/src/commands/faucet.ts b/packages/cli/src/commands/faucet.ts index fc59a015c1..9eb6f90e66 100644 --- a/packages/cli/src/commands/faucet.ts +++ b/packages/cli/src/commands/faucet.ts @@ -1,5 +1,5 @@ -import type { Arguments, CommandBuilder } from "yargs"; -import { FaucetServiceDefinition } from "@latticexyz/services/protobuf/ts/faucet/faucet"; +import type { CommandModule } from "yargs"; +import { FaucetServiceDefinition } from "@latticexyz/services/protobuf/ts/faucet/faucet.js"; import { createChannel, createClient } from "nice-grpc-web"; import chalk from "chalk"; import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport"; @@ -10,47 +10,51 @@ type Options = { address: string; }; -export const command = "faucet"; -export const desc = "Interact with a MUD faucet"; - /** * Create a FaucetServiceClient * @param url FaucetService URL * @returns FaucetServiceClient */ -export function createFaucetService(url: string) { +function createFaucetService(url: string) { return createClient(FaucetServiceDefinition, createChannel(url, NodeHttpTransport())); } -export const builder: CommandBuilder = (yargs) => - yargs.options({ - dripDev: { - type: "boolean", - desc: "Request a drip from the dev endpoint (requires faucet to have dev mode enabled)", - default: true, - }, - faucetUrl: { - type: "string", - desc: "URL of the MUD faucet", - default: "https://faucet.testnet-mud-services.linfra.xyz", - }, - address: { - type: "string", - desc: "Ethereum address to fund", - required: true, - }, - }); - -export const handler = async (argv: Arguments): Promise => { - const { dripDev, faucetUrl, address } = argv; - - const faucet = createFaucetService(faucetUrl); - - if (dripDev) { - console.log(chalk.yellow("Dripping to", address)); - await faucet.dripDev({ address }); - console.log(chalk.yellow("Success")); - } - - process.exit(0); +const commandModule: CommandModule = { + command: "faucet", + + describe: "Interact with a MUD faucet", + + builder(yargs) { + return yargs.options({ + dripDev: { + type: "boolean", + desc: "Request a drip from the dev endpoint (requires faucet to have dev mode enabled)", + default: true, + }, + faucetUrl: { + type: "string", + desc: "URL of the MUD faucet", + default: "https://faucet.testnet-mud-services.linfra.xyz", + }, + address: { + type: "string", + desc: "Ethereum address to fund", + required: true, + }, + }); + }, + + async handler({ dripDev, faucetUrl, address }) { + const faucet = createFaucetService(faucetUrl); + + if (dripDev) { + console.log(chalk.yellow("Dripping to", address)); + await faucet.dripDev({ address }); + console.log(chalk.yellow("Success")); + } + + process.exit(0); + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/gas-report.ts b/packages/cli/src/commands/gas-report.ts index f66c23a4e1..2062e5a197 100644 --- a/packages/cli/src/commands/gas-report.ts +++ b/packages/cli/src/commands/gas-report.ts @@ -1,4 +1,4 @@ -import type { Arguments, CommandBuilder } from "yargs"; +import type { CommandModule } from "yargs"; import { readFileSync, writeFileSync, rmSync } from "fs"; import { execa } from "execa"; import chalk from "chalk"; @@ -39,64 +39,68 @@ type GasReportEntry = { type GasReport = GasReportEntry[]; -export const command = "gas-report"; -export const desc = "Create a gas report"; +const commandModule: CommandModule = { + command: "gas-report", -export const builder: CommandBuilder = (yargs) => - yargs.options({ - path: { type: "array", default: ["Gas.t.sol"], desc: "File containing the gas tests" }, - save: { type: "string", desc: "Save the gas report to a file" }, - compare: { type: "string", desc: "Compare to an existing gas report" }, - }); + describe: "Create a gas report", -export const handler = async (args: Arguments): Promise => { - const { path, save } = args; - let { compare } = args; - let gasReport: GasReport = []; + builder(yargs) { + return yargs.options({ + path: { type: "array", string: true, default: ["Gas.t.sol"], desc: "File containing the gas tests" }, + save: { type: "string", desc: "Save the gas report to a file" }, + compare: { type: "string", desc: "Compare to an existing gas report" }, + }); + }, - // Iterate through all files provided in the path - for (const file of path) { - gasReport = gasReport.concat(await runGasReport(file)); - } + async handler({ path, save, compare }) { + let gasReport: GasReport = []; + + // Iterate through all files provided in the path + for (const file of path) { + gasReport = gasReport.concat(await runGasReport(file)); + } - // If this gas report should be compared to an existing one, load the existing one - const compareGasReport: GasReport = []; - if (compare) { - try { - const compareFileContents = readFileSync(compare, "utf8"); - // Create a regex to extract the name, function call and gas used - const compareGasReportRegex = new RegExp(/\((.*)\) \| (.*) \[(.*)\]: (.*)/g); - // Loop through the matches and add the resuls to the compareGasReport - let compareGasReportMatch; - while ((compareGasReportMatch = compareGasReportRegex.exec(compareFileContents)) !== null) { - const source = compareGasReportMatch[1]; - const name = compareGasReportMatch[2]; - const functionCall = compareGasReportMatch[3]; - const gasUsed = compareGasReportMatch[4]; - - compareGasReport.push({ source, name, functionCall, gasUsed: parseInt(gasUsed) }); + // If this gas report should be compared to an existing one, load the existing one + const compareGasReport: GasReport = []; + if (compare) { + try { + const compareFileContents = readFileSync(compare, "utf8"); + // Create a regex to extract the name, function call and gas used + const compareGasReportRegex = new RegExp(/\((.*)\) \| (.*) \[(.*)\]: (.*)/g); + // Loop through the matches and add the resuls to the compareGasReport + let compareGasReportMatch; + while ((compareGasReportMatch = compareGasReportRegex.exec(compareFileContents)) !== null) { + const source = compareGasReportMatch[1]; + const name = compareGasReportMatch[2]; + const functionCall = compareGasReportMatch[3]; + const gasUsed = compareGasReportMatch[4]; + + compareGasReport.push({ source, name, functionCall, gasUsed: parseInt(gasUsed) }); + } + } catch { + console.log(chalk.red(`Gas report to compare not found: ${compare}`)); + compare = undefined; } - } catch { - console.log(chalk.red(`Gas report to compare not found: ${compare}`)); - compare = undefined; } - } - // Merge the previous gas report with the new one - gasReport = gasReport.map((entry) => { - const prevEntry = compareGasReport.find((e) => e.name === entry.name && e.functionCall === entry.functionCall); - return { ...entry, prevGasUsed: prevEntry?.gasUsed }; - }); + // Merge the previous gas report with the new one + gasReport = gasReport.map((entry) => { + const prevEntry = compareGasReport.find((e) => e.name === entry.name && e.functionCall === entry.functionCall); + return { ...entry, prevGasUsed: prevEntry?.gasUsed }; + }); - // Print gas report - printGasReport(gasReport, compare); + // Print gas report + printGasReport(gasReport, compare); - // Save gas report to file if requested - if (save) saveGasReport(gasReport, save); + // Save gas report to file if requested + if (save) saveGasReport(gasReport, save); - process.exit(0); + process.exit(0); + }, }; +export default commandModule; + async function runGasReport(path: string): Promise { if (!path.endsWith(".t.sol")) { console.log("Skipping gas report for", chalk.bold(path), "(not a test file)"); @@ -125,7 +129,6 @@ async function runGasReport(path: string): Promise { // Apply the regex and loop through the matches, // and create a new file with the gasreport comments replaced by the gas report let match; - let i = 0; while ((match = regex.exec(fileContents)) !== null) { const name = match[1]; const functionCall = match[2].trim(); @@ -138,8 +141,6 @@ ${functionCall} _gasreport = _gasreport - gasleft(); console.log("GAS REPORT: ${name} [${functionCall.replaceAll('"', '\\"')}]:", _gasreport);` ); - - i++; } // Remove all occurrences of `pure` with `view` diff --git a/packages/cli/src/commands/hello.ts b/packages/cli/src/commands/hello.ts index 7099cded1e..1849744769 100644 --- a/packages/cli/src/commands/hello.ts +++ b/packages/cli/src/commands/hello.ts @@ -1,23 +1,28 @@ -import type { Arguments, CommandBuilder } from "yargs"; +import type { CommandModule } from "yargs"; type Options = { name: string; upper: boolean | undefined; }; -export const command = "hello "; -export const desc = "Greet with Hello"; +const commandModule: CommandModule = { + command: "hello ", -export const builder: CommandBuilder = (yargs) => - yargs - .options({ - upper: { type: "boolean" }, - }) - .positional("name", { type: "string", demandOption: true }); + describe: "Greet with Hello", -export const handler = (argv: Arguments): void => { - const { name } = argv; - const greeting = `Gm, ${name}!`; - console.log(greeting); - process.exit(0); + builder(yargs) { + return yargs + .options({ + upper: { type: "boolean" }, + }) + .positional("name", { type: "string", demandOption: true }); + }, + + handler({ name }) { + const greeting = `Gm, ${name}!`; + console.log(greeting); + process.exit(0); + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts new file mode 100644 index 0000000000..4ca2a72512 --- /dev/null +++ b/packages/cli/src/commands/index.ts @@ -0,0 +1,32 @@ +import { CommandModule } from "yargs"; + +import bulkupload from "./bulkupload.js"; +import callSystem from "./call-system.js"; +import codegenLibdeploy from "./codegen-libdeploy.js"; +import create from "./create.js"; +import deployContracts from "./deploy-contracts.js"; +import devnode from "./devnode.js"; +import faucet from "./faucet.js"; +import gasReport from "./gas-report.js"; +import hello from "./hello.js"; +import systemTypes from "./system-types.js"; +import test from "./test.js"; +import trace from "./trace.js"; +import types from "./types.js"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Each command has different options +export const commands: CommandModule[] = [ + bulkupload, + callSystem, + codegenLibdeploy, + create, + deployContracts, + devnode, + faucet, + gasReport, + hello, + systemTypes, + test, + trace, + types, +]; diff --git a/packages/cli/src/commands/sync-art.ts b/packages/cli/src/commands/sync-art.ts deleted file mode 100644 index c1aa1cffe9..0000000000 --- a/packages/cli/src/commands/sync-art.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Arguments, CommandBuilder } from "yargs"; -import { exec } from "../utils"; - -type Options = { - repo: string; - commitHash?: string; -}; - -export const command = "sync-art "; -export const desc = "Syncs art from a MUD-compatible art repo, found in "; - -export const builder: CommandBuilder = (yargs) => - yargs.positional("repo", { type: "string", demandOption: true }).options({ - commitHash: { type: "string" }, - }); - -export const handler = async (argv: Arguments): Promise => { - const { repo, commitHash } = argv; - console.log("Syncing art repo from", repo); - const clean = await exec(`git diff --quiet --exit-code`); - if (clean !== 0) { - console.log("Directory is not clean! Please git add and commit"); - process.exit(0); - } - - console.log("Cloning..."); - await exec(`git clone ${repo} _artmudtemp`); - if (commitHash) { - await exec(`cd _artmudtemp && git reset --hard ${commitHash} && cd -`); - } - - console.log("Moving atlases..."); - await exec(`cp -r _artmudtemp/atlases src/public`); - - console.log("Moving tilesets..."); - await exec(`cp -r _artmudtemp/tilesets src/layers/Renderer/assets`); - - console.log("Moving tileset types..."); - await exec(`cp -r _artmudtemp/types/ src/layers/Renderer/Phaser/tilesets/`); - - console.log("Cleaning up..."); - await exec(`rm -rf _artmudtemp`); - - console.log("Committing..."); - await exec(`git add src/public && git add src/layers/Renderer && git commit -m "feat(art): adding art from ${repo}"`); - process.exit(0); -}; diff --git a/packages/cli/src/commands/system-types.ts b/packages/cli/src/commands/system-types.ts index e97a850a67..2aab2bf946 100644 --- a/packages/cli/src/commands/system-types.ts +++ b/packages/cli/src/commands/system-types.ts @@ -1,24 +1,29 @@ -import type { Arguments, CommandBuilder } from "yargs"; -import { generateSystemTypes } from "../utils"; +import type { CommandModule } from "yargs"; +import { generateSystemTypes } from "../utils/index.js"; import { systemsDir } from "../utils/constants"; type Options = { outputDir: string; }; -export const command = "system-types"; -export const desc = `Generates system type file. Note: assumes contracts of all systems in /${systemsDir} folder, ABIs of all systems in ./abi and typechain generated types in ./types/ethers-contracts`; +const commandModule: CommandModule = { + command: "system-types", -export const builder: CommandBuilder = (yargs) => - yargs.options({ - outputDir: { - type: "string", - description: "generated types directory, defaults to ./types", - default: "./types", - }, - }); + describe: `Generates system type file. Note: assumes contracts of all systems in /${systemsDir} folder, ABIs of all systems in ./abi and typechain generated types in ./types/ethers-contracts`, -export const handler = async (args: Arguments): Promise => { - const { outputDir } = args; - await generateSystemTypes(outputDir); + builder(yargs) { + return yargs.options({ + outputDir: { + type: "string", + description: "generated types directory, defaults to ./types", + default: "./types", + }, + }); + }, + + async handler({ outputDir }) { + await generateSystemTypes(outputDir); + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index 5fa5224e38..7fa77362c1 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -1,5 +1,5 @@ -import type { Arguments, CommandBuilder } from "yargs"; -import { execLog, generateLibDeploy, resetLibDeploy } from "../utils"; +import type { CommandModule } from "yargs"; +import { execLog, generateLibDeploy, resetLibDeploy } from "../utils/index.js"; import { getTestDirectory } from "../utils/forgeConfig"; type Options = { @@ -8,38 +8,43 @@ type Options = { v: number; }; -export const command = "test"; -export const desc = "Run contract tests"; - -export const builder: CommandBuilder = (yargs) => - yargs.options({ - forgeOpts: { type: "string", desc: "Options passed to `forge test` command" }, - config: { type: "string", default: "./deploy.json", desc: "Component and system deployment configuration" }, - v: { type: "number", default: 2, desc: "Verbosity for forge test" }, - }); - -export const handler = async (argv: Arguments): Promise => { - const { forgeOpts, config, v } = argv; - const testDir = await getTestDirectory(); - - // Generate LibDeploy.sol - console.log("Generate LibDeploy.sol"); - await generateLibDeploy(config, testDir); - - // Call forge test - const { child } = await execLog("forge", [ - "test", - ...(v ? ["-" + [...new Array(v)].map(() => "v").join("")] : []), - ...(forgeOpts?.split(" ") || []), - ]); - - // Reset LibDeploy.sol - console.log("Reset LibDeploy.sol"); - await resetLibDeploy(testDir); - - process.on("SIGINT", () => { - console.log("\ngracefully shutting down from SIGINT (Crtl-C)"); - child.kill(); - process.exit(); - }); +const commandModule: CommandModule = { + command: "test", + + describe: "Run contract tests", + + builder(yargs) { + return yargs.options({ + forgeOpts: { type: "string", desc: "Options passed to `forge test` command" }, + config: { type: "string", default: "./deploy.json", desc: "Component and system deployment configuration" }, + v: { type: "number", default: 2, desc: "Verbosity for forge test" }, + }); + }, + + async handler({ forgeOpts, config, v }) { + const testDir = await getTestDirectory(); + + // Generate LibDeploy.sol + console.log("Generate LibDeploy.sol"); + await generateLibDeploy(config, testDir); + + // Call forge test + const child = execLog("forge", [ + "test", + ...(v ? ["-" + [...new Array(v)].map(() => "v").join("")] : []), + ...(forgeOpts?.split(" ") || []), + ]); + + // Reset LibDeploy.sol + console.log("Reset LibDeploy.sol"); + await resetLibDeploy(testDir); + + process.on("SIGINT", () => { + console.log("\ngracefully shutting down from SIGINT (Crtl-C)"); + child.kill(); + process.exit(); + }); + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/trace.ts b/packages/cli/src/commands/trace.ts index bed33a6c4e..b82abf9295 100644 --- a/packages/cli/src/commands/trace.ts +++ b/packages/cli/src/commands/trace.ts @@ -1,9 +1,9 @@ -import { Arguments, CommandBuilder } from "yargs"; -import { execLog, extractIdFromFile, keccak256 } from "../utils"; +import type { CommandModule } from "yargs"; +import { execLog, extractIdFromFile, keccak256 } from "../utils/index.js"; import { readFileSync } from "fs"; import { Contract } from "ethers"; import { JsonRpcProvider } from "@ethersproject/providers"; -import { abi as WorldAbi } from "@latticexyz/solecs/abi/World.json"; +import WorldAbi from "@latticexyz/solecs/abi/World.json" assert { type: "json" }; import { getSrcDirectory } from "../utils/forgeConfig"; import path from "path"; import { componentsDir, systemsDir } from "../utils/constants"; @@ -16,61 +16,66 @@ type Options = { debug?: boolean; }; -export const command = "trace"; -export const desc = "Display the trace of a transaction"; +const commandModule: CommandModule = { + command: "trace", -export const builder: CommandBuilder = (yargs) => - yargs.options({ - config: { type: "string", description: "path to mud deploy config (deploy.json)" }, - world: { type: "string", required: true, description: "world contract address" }, - tx: { type: "string", required: true, description: "tx hash to replay" }, - rpc: { type: "string", description: "json rpc endpoint, defaults to http://localhost:8545" }, - debug: { type: "boolean", description: "open debugger" }, - }); + describe: "Display the trace of a transaction", -export const handler = async (argv: Arguments): Promise => { - const { config, world, rpc, tx, debug } = argv; - const wd = process.cwd(); - const deployData = config && JSON.parse(readFileSync(config, { encoding: "utf8" })); - const labels = []; + builder(yargs) { + return yargs.options({ + config: { type: "string", description: "path to mud deploy config (deploy.json)" }, + world: { type: "string", required: true, description: "world contract address" }, + tx: { type: "string", required: true, description: "tx hash to replay" }, + rpc: { type: "string", description: "json rpc endpoint, defaults to http://localhost:8545" }, + debug: { type: "boolean", description: "open debugger" }, + }); + }, - const rpcUrl = rpc || "http://localhost:8545"; - const provider = new JsonRpcProvider(rpcUrl); - const World = new Contract(world, WorldAbi, provider); + async handler({ config, world, rpc, tx, debug }) { + const wd = process.cwd(); + const deployData = config && JSON.parse(readFileSync(config, { encoding: "utf8" })); + const labels = []; - if (deployData) { - const srcDir = await getSrcDirectory(); + const rpcUrl = rpc || "http://localhost:8545"; + const provider = new JsonRpcProvider(rpcUrl); + const World = new Contract(world, WorldAbi.abi, provider); - // Create component labels - const componentPromises = deployData.components.map(async (component: string) => { - const filePath = path.join(wd, srcDir, componentsDir, `${component}.sol`); - const id = extractIdFromFile(filePath); - if (!id) return; - const address = await World.getComponent(keccak256(id)); - return [component, address]; - }); - // Create system labels - const systemPromises = deployData.systems.map(async (system: { name: string }) => { - const filePath = path.join(wd, srcDir, systemsDir, `${system.name}.sol`); - const id = extractIdFromFile(filePath); - if (!id) return; - const address = await World.getSystemAddress(keccak256(id)); - return [system.name, address]; - }); + if (deployData) { + const srcDir = await getSrcDirectory(); - const components = await Promise.all(componentPromises); - const systems = await Promise.all(systemPromises); + // Create component labels + const componentPromises = deployData.components.map(async (component: string) => { + const filePath = path.join(wd, srcDir, componentsDir, `${component}.sol`); + const id = extractIdFromFile(filePath); + if (!id) return; + const address = await World.getComponent(keccak256(id)); + return [component, address]; + }); + // Create system labels + const systemPromises = deployData.systems.map(async (system: { name: string }) => { + const filePath = path.join(wd, srcDir, systemsDir, `${system.name}.sol`); + const id = extractIdFromFile(filePath); + if (!id) return; + const address = await World.getSystemAddress(keccak256(id)); + return [system.name, address]; + }); - labels.push(...components, ...systems); - } - await execLog("cast", [ - "run", - ...labels.map((label) => ["--label", `${label[1]}:${label[0]}`]).flat(), - ...(debug ? ["--debug"] : []), - `--rpc-url`, - `${rpcUrl}`, - `${tx}`, - ]); + const components = await Promise.all(componentPromises); + const systems = await Promise.all(systemPromises); - process.exit(0); + labels.push(...components, ...systems); + } + await execLog("cast", [ + "run", + ...labels.map((label) => ["--label", `${label[1]}:${label[0]}`]).flat(), + ...(debug ? ["--debug"] : []), + `--rpc-url`, + `${rpcUrl}`, + `${tx}`, + ]); + + process.exit(0); + }, }; + +export default commandModule; diff --git a/packages/cli/src/commands/types.ts b/packages/cli/src/commands/types.ts index 2a025b599d..4286ab0981 100644 --- a/packages/cli/src/commands/types.ts +++ b/packages/cli/src/commands/types.ts @@ -1,29 +1,33 @@ -import path from "path"; -import { Arguments, CommandBuilder } from "yargs"; -import { filterAbi, forgeBuild, generateAbiTypes, generateSystemTypes, generateTypes } from "../utils"; +import type { CommandModule } from "yargs"; +import { generateTypes } from "../utils/index.js"; type Options = { abiDir?: string; outputDir: string; }; -export const command = "types"; -export const desc = "Generates typescript types for contracts and systems."; +const commandModule: CommandModule = { + command: "types", -export const builder: CommandBuilder = (yargs) => - yargs.options({ - abiDir: { - type: "string", - description: "Input directory of existing ABI to use to generate types. If not provided, contracts are built.", - }, - outputDir: { - type: "string", - description: "Output directory for generated types. Defaults to ./types", - default: "./types", - }, - }); + describe: "Generates typescript types for contracts and systems.", -export const handler = async (args: Arguments): Promise => { - const { abiDir, outputDir } = args; - await generateTypes(abiDir, outputDir, { clear: true }); + builder(yargs) { + return yargs.options({ + abiDir: { + type: "string", + description: "Input directory of existing ABI to use to generate types. If not provided, contracts are built.", + }, + outputDir: { + type: "string", + description: "Output directory for generated types. Defaults to ./types", + default: "./types", + }, + }); + }, + + async handler({ abiDir, outputDir }) { + await generateTypes(abiDir, outputDir, { clear: true }); + }, }; + +export default commandModule; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts old mode 100644 new mode 100755 index 2185bfc993..3432784b80 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,16 +1 @@ -#!/usr/bin/env node -/* eslint-disable @typescript-eslint/no-var-requires */ - -// Using require to avoid "YError: loading a directory of commands is not supported yet for ESM" error -const yargs = require("yargs"); -const { hideBin } = require("yargs/helpers"); - -global.self = (1, eval)("this"); // https://stackoverflow.com/questions/9107240/1-evalthis-vs-evalthis-in-javascript - -yargs(hideBin(process.argv)) - // Use the commands directory to scaffold. - .commandDir("commands") - // Enable strict mode. - .strict() - // Useful aliases. - .alias({ h: "help" }).argv; +export type { StoreUserConfig, StoreConfig } from "./utils/config.js"; diff --git a/packages/cli/src/mud.ts b/packages/cli/src/mud.ts new file mode 100755 index 0000000000..c4c563cd09 --- /dev/null +++ b/packages/cli/src/mud.ts @@ -0,0 +1,21 @@ +#!/usr/bin/env ts-node-esm + +// #!/usr/bin/env -S NODE_NO_WARNINGS=1 ts-node-esm + +// `-S NODE_NO_WARNINGS=1` suppresses an experimental warning about esm json imports. +// TODO It could be removed if `-S` causes issues for users, although it should be well-supported now. + +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { commands } from "./commands/index.js"; + +yargs(hideBin(process.argv)) + // Explicit name to display in help (by default it's the entry file, which may not be "mud" for e.g. ts-node) + .scriptName("mud") + // Use the commands directory to scaffold + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- command array overload isn't typed, see https://github.com/yargs/yargs/blob/main/docs/advanced.md#esm-hierarchy + .command(commands as any) + // Enable strict mode. + .strict() + // Useful aliases. + .alias({ h: "help" }).argv; diff --git a/packages/cli/src/utils/codegen.ts b/packages/cli/src/utils/codegen.ts index 403cd78bc9..0f88da4fc2 100644 --- a/packages/cli/src/utils/codegen.ts +++ b/packages/cli/src/utils/codegen.ts @@ -2,9 +2,9 @@ import { readFile, writeFile } from "fs/promises"; import ejs from "ejs"; import path from "path"; -const contractsDir = path.join(__dirname, "../../src/contracts"); +const contractsDirectory = new URL("../src/contracts", import.meta.url).pathname; -const stubLibDeploy = readFile(path.join(contractsDir, "LibDeployStub.sol")); +const stubLibDeploy = readFile(path.join(contractsDirectory, "LibDeployStub.sol")); /** * Generate LibDeploy.sol from deploy.json @@ -30,7 +30,7 @@ export async function generateLibDeploy(configPath: string, out: string, systems // Generate LibDeploy console.log("Generating deployment script"); - const LibDeploy = await ejs.renderFile(path.join(contractsDir, "LibDeploy.ejs"), config, { async: true }); + const LibDeploy = await ejs.renderFile(path.join(contractsDirectory, "LibDeploy.ejs"), config, { async: true }); const libDeployPath = path.join(out, "LibDeploy.sol"); await writeFile(libDeployPath, LibDeploy); diff --git a/packages/cli/src/utils/config.ts b/packages/cli/src/utils/config.ts new file mode 100644 index 0000000000..51c0f8e751 --- /dev/null +++ b/packages/cli/src/utils/config.ts @@ -0,0 +1,24 @@ +// TODO unfinished placeholder +export interface StoreUserConfig { + storePath?: string; + tables: { + [name: string]: { + keys?: string[]; + schema: { + [valueName: string]: number; + }; + }; + }; +} + +export interface StoreConfig extends StoreUserConfig { + storePath: string; + tables: { + [name: string]: { + keys: string[]; + schema: { + [valueName: string]: number; + }; + }; + }; +} diff --git a/packages/cli/src/utils/deferred.ts b/packages/cli/src/utils/deferred.ts deleted file mode 100644 index 37dd2afabe..0000000000 --- a/packages/cli/src/utils/deferred.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * A convenient way to create a promise with resolve and reject functions. - * @returns Tuple with resolve function, reject function and promise. - */ -export function deferred(): [(t: T) => void, (t: Error) => void, Promise] { - let resolve: ((t: T) => void) | null = null; - let reject: ((t: Error) => void) | null = null; - const promise = new Promise((r, rj) => { - resolve = (t: T) => r(t); - reject = (e: Error) => rj(e); - }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return [resolve as any, reject as any, promise]; -} diff --git a/packages/cli/src/utils/deploy.ts b/packages/cli/src/utils/deploy.ts index 880a021797..3a57528c89 100644 --- a/packages/cli/src/utils/deploy.ts +++ b/packages/cli/src/utils/deploy.ts @@ -1,11 +1,12 @@ import { constants, Wallet } from "ethers"; -import { generateLibDeploy, resetLibDeploy } from "./codegen"; -import { findLog } from "./findLog"; -import { generateTypes } from "./types"; +import { generateLibDeploy, resetLibDeploy } from "./codegen.js"; +import { findLog } from "./findLog.js"; +import { generateTypes } from "./typegen.js"; import { execa } from "execa"; import { StaticJsonRpcProvider } from "@ethersproject/providers"; +import path from "path"; -const contractsDir = __dirname + "/../../src/contracts"; +const contractsDirectory = new URL("../src/contracts", import.meta.url).pathname; /** * Deploy world, components and systems from deploy.json @@ -39,7 +40,7 @@ export async function deploy( "forge", [ "script", - contractsDir + "/Deploy.sol", + path.join(contractsDirectory, "/Deploy.sol"), "--target-contract", "Deploy", "-vvv", @@ -85,7 +86,7 @@ export async function generateAndDeploy(args: DeployOptions) { try { // Generate LibDeploy - libDeployPath = await generateLibDeploy(args.config, contractsDir, args.systems); + libDeployPath = await generateLibDeploy(args.config, contractsDirectory, args.systems); // Build and generate fresh types await generateTypes(undefined, "./types", { clear: args.clear }); @@ -103,7 +104,7 @@ export async function generateAndDeploy(args: DeployOptions) { } finally { // Remove generated LibDeploy console.log("Cleaning up deployment script"); - if (libDeployPath) await resetLibDeploy(contractsDir); + if (libDeployPath) await resetLibDeploy(contractsDirectory); } return { deployedWorldAddress, initialBlockNumber }; diff --git a/packages/cli/src/utils/exec.ts b/packages/cli/src/utils/exec.ts index 12374fb377..388b1b71c6 100644 --- a/packages/cli/src/utils/exec.ts +++ b/packages/cli/src/utils/exec.ts @@ -1,40 +1,17 @@ -import { ChildProcess, exec as nodeExec, spawn } from "child_process"; -import { deferred } from "./deferred"; -/** - * Await execution of bash scripts - * @param command Bash script to execute - * @returns Promise that resolves with exit code when script finished executing - */ -export async function exec(command: string): Promise { - const [resolve, , promise] = deferred(); - - const child = nodeExec(command, (error, stdout, stderr) => { - if (error || stderr) { - console.error(error); - console.error(stderr); - } - console.log(stdout); - }); - - child.on("exit", (code) => resolve(code ?? 0)); - - return promise; -} +import { execa } from "execa"; /** - * Await execution of bash scripts - * @param command Bash script to execute - * @param options Args to pass to the script - * @returns Promise that resolves with exit code when script finished executing + * Wrapper for execa that logs the full command. + * @param command - The program/script to execute. + * @param options - Arguments to pass to `command` on execution. + * @returns A [`child_process` instance](https://nodejs.org/api/child_process.html#child_process_class_childprocess), which is enhanced to also be a `Promise` for a result `Object` with `stdout` and `stderr` properties. */ -export async function execLog(command: string, options: string[]): Promise<{ exitCode: number; child: ChildProcess }> { +export function execLog(command: string, options: string[]) { console.log("Cmd:"); console.log([command, ...options].join(" ")); - const [resolve, , promise] = deferred<{ exitCode: number; child: ChildProcess }>(); - - const child = spawn(command, options, { stdio: [process.stdin, process.stdout, process.stderr] }); - child.on("exit", (code) => resolve({ exitCode: code ?? 0, child })); - - return promise; + // TODO piping outputs and doing custom logging would be better for readability + return execa(command, options, { + stdio: ["inherit", "inherit", "inherit"], + }); } diff --git a/packages/cli/src/utils/ids.ts b/packages/cli/src/utils/ids.ts index 3f0ef5b816..fc1f221ac2 100644 --- a/packages/cli/src/utils/ids.ts +++ b/packages/cli/src/utils/ids.ts @@ -1,4 +1,4 @@ -import { keccak256 as keccak256Bytes, toUtf8Bytes } from "ethers/lib/utils"; +import { keccak256 as keccak256Bytes, toUtf8Bytes } from "ethers/lib/utils.js"; import { readFileSync } from "fs"; export const IDregex = new RegExp(/(?<=uint256 constant ID = uint256\(keccak256\(")(.*)(?="\))/); diff --git a/packages/cli/src/utils/index.ts b/packages/cli/src/utils/index.ts index bcf3a162a7..367c59f5b4 100644 --- a/packages/cli/src/utils/index.ts +++ b/packages/cli/src/utils/index.ts @@ -1,9 +1,8 @@ -export * from "./exec"; -export * from "./deferred"; -export * from "./ids"; -export * from "./codegen"; -export * from "./deploy"; -export * from "./hsr"; -export * from "./findLog"; -export * from "./types"; -export * from "./build"; +export * from "./exec.js"; +export * from "./ids.js"; +export * from "./codegen.js"; +export * from "./deploy.js"; +export * from "./hsr.js"; +export * from "./findLog.js"; +export * from "./typegen.js"; +export * from "./build.js"; diff --git a/packages/cli/src/utils/types.ts b/packages/cli/src/utils/typegen.ts similarity index 78% rename from packages/cli/src/utils/types.ts rename to packages/cli/src/utils/typegen.ts index 7217e2c203..6c3e7143d3 100644 --- a/packages/cli/src/utils/types.ts +++ b/packages/cli/src/utils/typegen.ts @@ -1,10 +1,9 @@ import { runTypeChain, glob as typechainGlob } from "typechain"; -import { deferred } from "./deferred"; import glob from "glob"; -import { extractIdFromFile } from "./ids"; +import { extractIdFromFile } from "./ids.js"; import { rmSync, writeFileSync } from "fs"; import path from "path"; -import { filterAbi, forgeBuild } from "./build"; +import { filterAbi, forgeBuild } from "./build.js"; import { getOutDirectory, getSrcDirectory } from "./forgeConfig"; import { systemsDir } from "./constants"; @@ -50,36 +49,30 @@ export async function generateSystemTypes(outputDir: string, options?: { clear?: const srcDir = await getSrcDirectory(); const systemsPath = path.join(srcDir, systemsDir, "*.sol"); - const [resolve, , promise] = deferred(); - glob(systemsPath, {}, (_, matches) => { - systems = matches.map((path) => { - const fragments = path.split("/"); - return fragments[fragments.length - 1].split(".sol")[0]; - }); - - ids = matches.map((path, index) => { - const id = extractIdFromFile(path); - if (!id) { - console.log("Path:", path); - console.log("ID:", id); - throw new Error( - "No ID found for" + - matches[index] + - ". Make sure your system source file includes a ID definition (uint256 constant ID = uint256(keccak256());)" - ); - } - return id; - }); - - abis = systems.map((system) => `../abi/${system}.json`); - - typePaths = systems.map((system) => `./ethers-contracts/${system}.ts`); - - resolve(); + const matches = glob.sync(systemsPath); + + systems = matches.map((path) => { + const fragments = path.split("/"); + return fragments[fragments.length - 1].split(".sol")[0]; + }); + + ids = matches.map((path, index) => { + const id = extractIdFromFile(path); + if (!id) { + console.log("Path:", path); + console.log("ID:", id); + throw new Error( + "No ID found for" + + matches[index] + + ". Make sure your system source file includes a ID definition (uint256 constant ID = uint256(keccak256());)" + ); + } + return id; }); - // Make the callback synchronous - await promise; + abis = systems.map((system) => `../abi/${system}.json`); + + typePaths = systems.map((system) => `./ethers-contracts/${system}.ts`); console.log("Matches", systems); console.log("Solidity", ids); diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 5a15b828a1..e0770a9302 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -24,9 +24,9 @@ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ /* Modules */ - "module": "commonjs" /* Specify what module code is generated. */, + "module": "esnext" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ @@ -67,8 +67,8 @@ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, diff --git a/packages/cli/tsup.config.js b/packages/cli/tsup.config.js new file mode 100644 index 0000000000..096451a795 --- /dev/null +++ b/packages/cli/tsup.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts", "src/mud.ts"], + outDir: "dist", + platform: "node", + format: ["esm"], + sourcemap: false, + clean: true, + bundle: true, + dts: "src/index.ts", + noExternal: ["@latticexyz/services", "@latticexyz/solecs"], +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 04642ed950..54ad8696f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -679,6 +679,11 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@esbuild/android-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.8.tgz#b3d5b65a3b2e073a6c7ee36b1f3c30c8f000315b" + integrity sha512-oa/N5j6v1svZQs7EIRPqR8f+Bf8g6HBDjD/xHC02radE/NjKHK7oQmtmLxPs1iVwYyvE+Kolo6lbpfEQ9xnhxQ== + "@esbuild/android-arm@0.15.16": version "0.15.16" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.16.tgz#0642926178b15e3d1545efae6eee05c4f3451d15" @@ -689,6 +694,51 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz#266d40b8fdcf87962df8af05b76219bc786b4f80" integrity sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw== +"@esbuild/android-arm@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.8.tgz#c41e496af541e175369d48164d0cf01a5f656cf6" + integrity sha512-0/rb91GYKhrtbeglJXOhAv9RuYimgI8h623TplY2X+vA4EXnk3Zj1fXZreJ0J3OJJu1bwmb0W7g+2cT/d8/l/w== + +"@esbuild/android-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.8.tgz#080fa67c29be77f5a3ca5ee4cc78d5bf927e3a3b" + integrity sha512-bTliMLqD7pTOoPg4zZkXqCDuzIUguEWLpeqkNfC41ODBHwoUgZ2w5JBeYimv4oP6TDVocoYmEhZrCLQTrH89bg== + +"@esbuild/darwin-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.8.tgz#053622bf9a82f43d5c075b7818e02618f7b4a397" + integrity sha512-ghAbV3ia2zybEefXRRm7+lx8J/rnupZT0gp9CaGy/3iolEXkJ6LYRq4IpQVI9zR97ID80KJVoUlo3LSeA/sMAg== + +"@esbuild/darwin-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.8.tgz#8a1aadb358d537d8efad817bb1a5bff91b84734b" + integrity sha512-n5WOpyvZ9TIdv2V1K3/iIkkJeKmUpKaCTdun9buhGRWfH//osmUjlv4Z5mmWdPWind/VGcVxTHtLfLCOohsOXw== + +"@esbuild/freebsd-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.8.tgz#e6738d0081ba0721a5c6c674e84c6e7fcea61989" + integrity sha512-a/SATTaOhPIPFWvHZDoZYgxaZRVHn0/LX1fHLGfZ6C13JqFUZ3K6SMD6/HCtwOQ8HnsNaEeokdiDSFLuizqv5A== + +"@esbuild/freebsd-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.8.tgz#1855e562f2b730f4483f6e94086e9e2597feb4c3" + integrity sha512-xpFJb08dfXr5+rZc4E+ooZmayBW6R3q59daCpKZ/cDU96/kvDM+vkYzNeTJCGd8rtO6fHWMq5Rcv/1cY6p6/0Q== + +"@esbuild/linux-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.8.tgz#481da38952721a3fdb77c17a36ceaacc4270b5c5" + integrity sha512-v3iwDQuDljLTxpsqQDl3fl/yihjPAyOguxuloON9kFHYwopeJEf1BkDXODzYyXEI19gisEsQlG1bM65YqKSIww== + +"@esbuild/linux-arm@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.8.tgz#18127072b270bb6321c6d11be20bfd30e0d6ad17" + integrity sha512-6Ij8gfuGszcEwZpi5jQIJCVIACLS8Tz2chnEBfYjlmMzVsfqBP1iGmHQPp7JSnZg5xxK9tjCc+pJ2WtAmPRFVA== + +"@esbuild/linux-ia32@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.8.tgz#ee400af7b3bc69e8ca2e593ca35156ffb9abd54f" + integrity sha512-8svILYKhE5XetuFk/B6raFYIyIqydQi+GngEXJgdPdI7OMKUbSd7uzR02wSY4kb53xBrClLkhH4Xs8P61Q2BaA== + "@esbuild/linux-loong64@0.14.54": version "0.14.54" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" @@ -704,6 +754,66 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239" integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ== +"@esbuild/linux-loong64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.8.tgz#8c509d8a454693d39824b83b3f66c400872fce82" + integrity sha512-B6FyMeRJeV0NpyEOYlm5qtQfxbdlgmiGdD+QsipzKfFky0K5HW5Td6dyK3L3ypu1eY4kOmo7wW0o94SBqlqBSA== + +"@esbuild/linux-mips64el@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.8.tgz#f2b0d36e63fb26bc3f95b203b6a80638292101ca" + integrity sha512-CCb67RKahNobjm/eeEqeD/oJfJlrWyw29fgiyB6vcgyq97YAf3gCOuP6qMShYSPXgnlZe/i4a8WFHBw6N8bYAA== + +"@esbuild/linux-ppc64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.8.tgz#1e628be003e036e90423716028cc884fe5ba25bd" + integrity sha512-bytLJOi55y55+mGSdgwZ5qBm0K9WOCh0rx+vavVPx+gqLLhxtSFU0XbeYy/dsAAD6xECGEv4IQeFILaSS2auXw== + +"@esbuild/linux-riscv64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.8.tgz#419a815cb4c3fb9f1b78ef5295f5b48b8bf6427a" + integrity sha512-2YpRyQJmKVBEHSBLa8kBAtbhucaclb6ex4wchfY0Tj3Kg39kpjeJ9vhRU7x4mUpq8ISLXRXH1L0dBYjAeqzZAw== + +"@esbuild/linux-s390x@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.8.tgz#291c49ae5c3d11d226352755c0835911fe1a9e5c" + integrity sha512-QgbNY/V3IFXvNf11SS6exkpVcX0LJcob+0RWCgV9OiDAmVElnxciHIisoSix9uzYzScPmS6dJFbZULdSAEkQVw== + +"@esbuild/linux-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.8.tgz#03199d91c76faf80bd54104f5cbf0a489bc39f6a" + integrity sha512-mM/9S0SbAFDBc4OPoyP6SEOo5324LpUxdpeIUUSrSTOfhHU9hEfqRngmKgqILqwx/0DVJBzeNW7HmLEWp9vcOA== + +"@esbuild/netbsd-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.8.tgz#b436d767e1b21852f9ed212e2bb57f77203b0ae2" + integrity sha512-eKUYcWaWTaYr9zbj8GertdVtlt1DTS1gNBWov+iQfWuWyuu59YN6gSEJvFzC5ESJ4kMcKR0uqWThKUn5o8We6Q== + +"@esbuild/openbsd-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.8.tgz#d1481d8539e21d4729cd04a0450a26c2c8789e89" + integrity sha512-Vc9J4dXOboDyMXKD0eCeW0SIeEzr8K9oTHJU+Ci1mZc5njPfhKAqkRt3B/fUNU7dP+mRyralPu8QUkiaQn7iIg== + +"@esbuild/sunos-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.8.tgz#2cfb8126e079b2c00fd1bf095541e9f5c47877e4" + integrity sha512-0xvOTNuPXI7ft1LYUgiaXtpCEjp90RuBBYovdd2lqAFxje4sEucurg30M1WIm03+3jxByd3mfo+VUmPtRSVuOw== + +"@esbuild/win32-arm64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.8.tgz#7c6ecfd097ca23b82119753bf7072bbaefe51e3a" + integrity sha512-G0JQwUI5WdEFEnYNKzklxtBheCPkuDdu1YrtRrjuQv30WsYbkkoixKxLLv8qhJmNI+ATEWquZe/N0d0rpr55Mg== + +"@esbuild/win32-ia32@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.8.tgz#cffec63c3cb0ef8563a04df4e09fa71056171d00" + integrity sha512-Fqy63515xl20OHGFykjJsMnoIWS+38fqfg88ClvPXyDbLtgXal2DTlhb1TfTX34qWi3u4I7Cq563QcHpqgLx8w== + +"@esbuild/win32-x64@0.17.8": + version "0.17.8" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.8.tgz#200a0965cf654ac28b971358ecdca9cc5b44c335" + integrity sha512-1iuezdyDNngPnz8rLRDO2C/ZZ/emJLb72OsZeqQ6gL6Avko/XCXZw+NuxBSNhBAP13Hie418V7VMt9et1FMvpg== + "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" @@ -5154,6 +5264,13 @@ bundle-require@^3.0.2, bundle-require@^3.1.2: dependencies: load-tsconfig "^0.2.0" +bundle-require@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-4.0.1.tgz#2cc1ad76428043d15e0e7f30990ee3d5404aa2e3" + integrity sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ== + dependencies: + load-tsconfig "^0.2.3" + byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" @@ -7188,6 +7305,34 @@ esbuild@^0.15.16: esbuild-windows-64 "0.15.16" esbuild-windows-arm64 "0.15.16" +esbuild@^0.17.6: + version "0.17.8" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.8.tgz#f7f799abc7cdce3f0f2e3e0c01f120d4d55193b4" + integrity sha512-g24ybC3fWhZddZK6R3uD2iF/RIPnRpwJAqLov6ouX3hMbY4+tKolP0VMF3zuIYCaXun+yHwS5IPQ91N2BT191g== + optionalDependencies: + "@esbuild/android-arm" "0.17.8" + "@esbuild/android-arm64" "0.17.8" + "@esbuild/android-x64" "0.17.8" + "@esbuild/darwin-arm64" "0.17.8" + "@esbuild/darwin-x64" "0.17.8" + "@esbuild/freebsd-arm64" "0.17.8" + "@esbuild/freebsd-x64" "0.17.8" + "@esbuild/linux-arm" "0.17.8" + "@esbuild/linux-arm64" "0.17.8" + "@esbuild/linux-ia32" "0.17.8" + "@esbuild/linux-loong64" "0.17.8" + "@esbuild/linux-mips64el" "0.17.8" + "@esbuild/linux-ppc64" "0.17.8" + "@esbuild/linux-riscv64" "0.17.8" + "@esbuild/linux-s390x" "0.17.8" + "@esbuild/linux-x64" "0.17.8" + "@esbuild/netbsd-x64" "0.17.8" + "@esbuild/openbsd-x64" "0.17.8" + "@esbuild/sunos-x64" "0.17.8" + "@esbuild/win32-arm64" "0.17.8" + "@esbuild/win32-ia32" "0.17.8" + "@esbuild/win32-x64" "0.17.8" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -11056,7 +11201,7 @@ load-json-file@^6.2.0: strip-bom "^4.0.0" type-fest "^0.6.0" -load-tsconfig@^0.2.0: +load-tsconfig@^0.2.0, load-tsconfig@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/load-tsconfig/-/load-tsconfig-0.2.3.tgz#08af3e7744943caab0c75f8af7f1703639c3ef1f" integrity sha512-iyT2MXws+dc2Wi6o3grCFtGXpeMvHmJqS27sMPGtV2eUu4PeFnG+33I8BlFK1t1NWMjOpcx9bridn5yxLDX2gQ== @@ -15460,6 +15605,26 @@ tsup@^6.5.0: sucrase "^3.20.3" tree-kill "^1.2.2" +tsup@^6.6.3: + version "6.6.3" + resolved "https://registry.yarnpkg.com/tsup/-/tsup-6.6.3.tgz#f6f975a8656cfd9b8e115f33b1aa0f0fd4df78e2" + integrity sha512-OLx/jFllYlVeZQ7sCHBuRVEQBBa1tFbouoc/gbYakyipjVQdWy/iQOvmExUA/ewap9iQ7tbJf9pW0PgcEFfJcQ== + dependencies: + bundle-require "^4.0.0" + cac "^6.7.12" + chokidar "^3.5.1" + debug "^4.3.1" + esbuild "^0.17.6" + execa "^5.0.0" + globby "^11.0.3" + joycon "^3.0.1" + postcss-load-config "^3.0.1" + resolve-from "^5.0.0" + rollup "^3.2.5" + source-map "0.8.0-beta.0" + sucrase "^3.20.3" + tree-kill "^1.2.2" + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" From c5030ef627e081e00020959a54bb61328426f697 Mon Sep 17 00:00:00 2001 From: dk1a Date: Sun, 19 Feb 2023 04:24:37 +0300 Subject: [PATCH 02/13] feat(services): make services more compatible with esm --- packages/services/Makefile | 8 ++++---- packages/services/package.json | 6 ++++-- packages/services/protobuf/ts/ecs-relay/ecs-relay.ts | 2 +- .../services/protobuf/ts/ecs-snapshot/ecs-snapshot.ts | 2 +- packages/services/protobuf/ts/ecs-stream/ecs-stream.ts | 2 +- packages/services/protobuf/ts/faucet/faucet.ts | 2 +- packages/services/tsconfig.json | 6 +++--- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/services/Makefile b/packages/services/Makefile index ce62678f32..a6deded812 100644 --- a/packages/services/Makefile +++ b/packages/services/Makefile @@ -26,22 +26,22 @@ protoc-ts: protoc \ --plugin protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \ --ts_proto_out ./protobuf/ts/ecs-stream \ - --ts_proto_opt=env=browser,outputServices=nice-grpc,outputServices=generic-definitions,outputJsonMethods=false,useExactTypes=false,eslint_disable,esModuleInterop=true \ + --ts_proto_opt=env=browser,outputServices=nice-grpc,outputServices=generic-definitions,outputJsonMethods=false,useExactTypes=false,eslint_disable,esModuleInterop=true,importSuffix=.js \ --proto_path proto proto/ecs-stream.proto protoc \ --plugin protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \ --ts_proto_out ./protobuf/ts/ecs-snapshot \ - --ts_proto_opt=env=browser,outputServices=nice-grpc,outputServices=generic-definitions,outputJsonMethods=false,useExactTypes=false,eslint_disable,esModuleInterop=true \ + --ts_proto_opt=env=browser,outputServices=nice-grpc,outputServices=generic-definitions,outputJsonMethods=false,useExactTypes=false,eslint_disable,esModuleInterop=true,importSuffix=.js \ --proto_path proto proto/ecs-snapshot.proto protoc \ --plugin protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \ --ts_proto_out ./protobuf/ts/ecs-relay \ - --ts_proto_opt=env=browser,outputServices=nice-grpc,outputServices=generic-definitions,outputJsonMethods=false,useExactTypes=false,eslint_disable,esModuleInterop=true \ + --ts_proto_opt=env=browser,outputServices=nice-grpc,outputServices=generic-definitions,outputJsonMethods=false,useExactTypes=false,eslint_disable,esModuleInterop=true,importSuffix=.js \ --proto_path proto proto/ecs-relay.proto protoc \ --plugin protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \ --ts_proto_out ./protobuf/ts/faucet \ - --ts_proto_opt=env=browser,outputServices=nice-grpc,outputServices=generic-definitions,outputJsonMethods=false,useExactTypes=false,eslint_disable,esModuleInterop=true \ + --ts_proto_opt=env=browser,outputServices=nice-grpc,outputServices=generic-definitions,outputJsonMethods=false,useExactTypes=false,eslint_disable,esModuleInterop=true,importSuffix=.js \ --proto_path proto proto/faucet.proto .PHONY: protoc-ts diff --git a/packages/services/package.json b/packages/services/package.json index 17c9c6ce24..857c57e6a3 100644 --- a/packages/services/package.json +++ b/packages/services/package.json @@ -3,7 +3,8 @@ "license": "MIT", "version": "1.37.1", "description": "MUD services for enhanced interactions with on-chain ECS state", - "main": "protobuf/ts", + "main": "protobuf/ts/index.ts", + "type": "module", "repository": { "type": "git", "url": "https://github.com/latticexyz/mud.git", @@ -12,7 +13,8 @@ "scripts": { "prepare": "make build", "docs": "rimraf API && mkdir -p _docs/pkg && find pkg -type f -name '*.go' -exec bash -c 'gomarkdoc {} > \"$(dirname _docs/{})\".md' \\; && mv _docs/pkg API && rimraf _docs", - "test": "echo 'todo: add tests'", + "test": "tsc --noEmit && echo 'todo: add tests'", + "protoc-ts": "make protoc-ts", "link": "yarn link", "release": "npm publish || echo 'version already published'" }, diff --git a/packages/services/protobuf/ts/ecs-relay/ecs-relay.ts b/packages/services/protobuf/ts/ecs-relay/ecs-relay.ts index 1ab4eaab06..3370f3d5f5 100644 --- a/packages/services/protobuf/ts/ecs-relay/ecs-relay.ts +++ b/packages/services/protobuf/ts/ecs-relay/ecs-relay.ts @@ -1,7 +1,7 @@ /* eslint-disable */ import Long from "long"; import { CallContext, CallOptions } from "nice-grpc-common"; -import _m0 from "protobufjs/minimal"; +import _m0 from "protobufjs/minimal.js"; export const protobufPackage = "ecsrelay"; diff --git a/packages/services/protobuf/ts/ecs-snapshot/ecs-snapshot.ts b/packages/services/protobuf/ts/ecs-snapshot/ecs-snapshot.ts index 7e83f4b500..3c0ade6aa0 100644 --- a/packages/services/protobuf/ts/ecs-snapshot/ecs-snapshot.ts +++ b/packages/services/protobuf/ts/ecs-snapshot/ecs-snapshot.ts @@ -1,7 +1,7 @@ /* eslint-disable */ import Long from "long"; import { CallContext, CallOptions } from "nice-grpc-common"; -import _m0 from "protobufjs/minimal"; +import _m0 from "protobufjs/minimal.js"; export const protobufPackage = "ecssnapshot"; diff --git a/packages/services/protobuf/ts/ecs-stream/ecs-stream.ts b/packages/services/protobuf/ts/ecs-stream/ecs-stream.ts index 25e2bb67c8..d4a0960cdd 100644 --- a/packages/services/protobuf/ts/ecs-stream/ecs-stream.ts +++ b/packages/services/protobuf/ts/ecs-stream/ecs-stream.ts @@ -1,7 +1,7 @@ /* eslint-disable */ import Long from "long"; import { CallContext, CallOptions } from "nice-grpc-common"; -import _m0 from "protobufjs/minimal"; +import _m0 from "protobufjs/minimal.js"; export const protobufPackage = "ecsstream"; diff --git a/packages/services/protobuf/ts/faucet/faucet.ts b/packages/services/protobuf/ts/faucet/faucet.ts index 252252e8fb..5c916f6899 100644 --- a/packages/services/protobuf/ts/faucet/faucet.ts +++ b/packages/services/protobuf/ts/faucet/faucet.ts @@ -1,7 +1,7 @@ /* eslint-disable */ import Long from "long"; import { CallContext, CallOptions } from "nice-grpc-common"; -import _m0 from "protobufjs/minimal"; +import _m0 from "protobufjs/minimal.js"; export const protobufPackage = "faucet"; diff --git a/packages/services/tsconfig.json b/packages/services/tsconfig.json index 42f492082e..e565d6d2dd 100644 --- a/packages/services/tsconfig.json +++ b/packages/services/tsconfig.json @@ -24,7 +24,7 @@ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ /* Modules */ - // "module": "esnext" /* Specify what module code is generated. */, + "module": "esnext" /* Specify what module code is generated. */, // "rootDir": "." /* Specify the root folder within your source files. */, "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ @@ -67,8 +67,8 @@ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ /* Interop Constraints */ - // "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, + "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ // "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */ From f81c3377076bc02dd2af5edf1d0f1be76adadae9 Mon Sep 17 00:00:00 2001 From: dk1a Date: Sun, 19 Feb 2023 04:42:25 +0300 Subject: [PATCH 03/13] build(cli): add clarifications to tsup config --- packages/cli/tsup.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cli/tsup.config.js b/packages/cli/tsup.config.js index 096451a795..223662b509 100644 --- a/packages/cli/tsup.config.js +++ b/packages/cli/tsup.config.js @@ -9,5 +9,8 @@ export default defineConfig({ clean: true, bundle: true, dts: "src/index.ts", + // both inlined imports are hacks to make esm+esbuild work + // @latticexyz/services: esm imports directly from ts files don't work properly from transpiled js + // @latticexyz/solecs: esm requires import assertions for json, but esbuild doesn't support those (they just get stripped) noExternal: ["@latticexyz/services", "@latticexyz/solecs"], }); \ No newline at end of file From 2d20709fe35f67289bbf722ccc9a9279974571cf Mon Sep 17 00:00:00 2001 From: dk1a Date: Sun, 19 Feb 2023 13:53:21 +0300 Subject: [PATCH 04/13] fix(cli): fix mud.ts shebang --- packages/cli/src/mud.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/cli/src/mud.ts b/packages/cli/src/mud.ts index c4c563cd09..b2bcb4f596 100755 --- a/packages/cli/src/mud.ts +++ b/packages/cli/src/mud.ts @@ -1,9 +1,4 @@ -#!/usr/bin/env ts-node-esm - -// #!/usr/bin/env -S NODE_NO_WARNINGS=1 ts-node-esm - -// `-S NODE_NO_WARNINGS=1` suppresses an experimental warning about esm json imports. -// TODO It could be removed if `-S` causes issues for users, although it should be well-supported now. +#!/usr/bin/env node import yargs from "yargs"; import { hideBin } from "yargs/helpers"; From 46042b31a07515b86ce59f33e6888290c189afc2 Mon Sep 17 00:00:00 2001 From: dk1a Date: Sun, 19 Feb 2023 14:05:53 +0300 Subject: [PATCH 05/13] refactor(cli): fix minor eslint problems --- packages/cli/src/commands/call-system.ts | 6 +++--- packages/cli/src/utils/hsr.ts | 2 +- packages/cli/src/utils/typegen.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/call-system.ts b/packages/cli/src/commands/call-system.ts index 9b78a5af19..89b00bb8f7 100644 --- a/packages/cli/src/commands/call-system.ts +++ b/packages/cli/src/commands/call-system.ts @@ -10,8 +10,8 @@ type Options = { world: string; systemId?: string; systemAddress?: string; - argTypes?: any[]; - args?: any[]; + argTypes?: string[]; + args?: (string | number)[]; calldata?: string; broadcast?: boolean; callerPrivateKey?: string; @@ -30,7 +30,7 @@ const commandModule: CommandModule = { world: { type: "string", required: true, description: "world contract address" }, systemId: { type: "string", description: "system id preimage (eg mud.system.Move)" }, systemAddress: { type: "string", description: "system address (alternative to system id)" }, - argTypes: { type: "array", description: "system argument types for abi encoding" }, + argTypes: { type: "array", string: true, description: "system argument types for abi encoding" }, args: { type: "array", description: "system arguments" }, calldata: { type: "string", description: "abi encoded system arguments (instead of args/argTypes)" }, broadcast: { type: "boolean", description: "send txs to the chain" }, diff --git a/packages/cli/src/utils/hsr.ts b/packages/cli/src/utils/hsr.ts index d9d23747c0..03c2d52041 100644 --- a/packages/cli/src/utils/hsr.ts +++ b/packages/cli/src/utils/hsr.ts @@ -39,7 +39,7 @@ export function hsr(root: string, replaceSystems: (systems: string[]) => Promise console.log("Watching system file changes..."); - chokidar.watch(root).on("all", async (event, path, stats) => { + chokidar.watch(root).on("all", async (event, path) => { console.log(`[${event}]: ${path}`); // Find changed file diff --git a/packages/cli/src/utils/typegen.ts b/packages/cli/src/utils/typegen.ts index 6c3e7143d3..80370d4b6e 100644 --- a/packages/cli/src/utils/typegen.ts +++ b/packages/cli/src/utils/typegen.ts @@ -19,13 +19,13 @@ export async function generateAbiTypes( const cwd = options?.cwd ?? process.cwd(); - const allFiles = typechainGlob(cwd, [`${inputDir!}/**/+([a-zA-Z0-9_]).json`]); + const allFiles = typechainGlob(cwd, [`${inputDir}/**/+([a-zA-Z0-9_]).json`]); const result = await runTypeChain({ cwd, filesToProcess: allFiles, allFiles, - outDir: outputDir!, + outDir: outputDir, target: "ethers-v5", }); From 4ccbcf6e3eee3cdc49997b92d6ac1afa90efa8fc Mon Sep 17 00:00:00 2001 From: dk1a Date: Sun, 19 Feb 2023 14:10:00 +0300 Subject: [PATCH 06/13] chore(cli): add lint to scripts --- packages/cli/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/package.json b/packages/cli/package.json index 3aea738825..62e79003d9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -16,6 +16,7 @@ }, "scripts": { "prepare": "yarn build && chmod u+x git-install.sh", + "lint": "eslint . --ext .ts", "build": "tsup", "link": "yarn link", "test": "tsc --noEmit && echo 'todo: add tests'", From 8d53d07f0ce0e8bb278289c63017e60337d5179c Mon Sep 17 00:00:00 2001 From: dk1a Date: Sun, 19 Feb 2023 14:12:01 +0300 Subject: [PATCH 07/13] feat(cli): use moduleResolution nodenext when testing tsc --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 62e79003d9..c635755154 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -19,7 +19,7 @@ "lint": "eslint . --ext .ts", "build": "tsup", "link": "yarn link", - "test": "tsc --noEmit && echo 'todo: add tests'", + "test": "tsc --noEmit --moduleResolution nodenext && echo 'todo: add tests'", "git:install": "bash git-install.sh", "release": "npm publish || echo 'version already published'" }, From 109f891d83b60e7555d5a6cffa710071e046f55e Mon Sep 17 00:00:00 2001 From: dk1a Date: Sun, 19 Feb 2023 14:49:13 +0300 Subject: [PATCH 08/13] fix(create-mud): add missing peerDependency --- .../create-mud/templates/minimal/packages/client/package.json | 1 + packages/create-mud/templates/react/packages/client/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/create-mud/templates/minimal/packages/client/package.json b/packages/create-mud/templates/minimal/packages/client/package.json index b15a358eda..d97b5d1b95 100644 --- a/packages/create-mud/templates/minimal/packages/client/package.json +++ b/packages/create-mud/templates/minimal/packages/client/package.json @@ -22,6 +22,7 @@ "@latticexyz/recs": "1.36.1", "@latticexyz/services": "1.36.1", "@latticexyz/std-client": "1.36.1", + "@latticexyz/utils": "1.36.1", "async-mutex": "^0.4.0", "ethers": "^5.7.2", "mobx": "^6.7.0", diff --git a/packages/create-mud/templates/react/packages/client/package.json b/packages/create-mud/templates/react/packages/client/package.json index b4989e4964..f3b2fd3962 100644 --- a/packages/create-mud/templates/react/packages/client/package.json +++ b/packages/create-mud/templates/react/packages/client/package.json @@ -28,6 +28,7 @@ "@latticexyz/recs": "1.36.1", "@latticexyz/services": "1.36.1", "@latticexyz/std-client": "1.36.1", + "@latticexyz/utils": "1.36.1", "async-mutex": "^0.4.0", "ethers": "^5.7.2", "mobx": "^6.7.0", From 82b1df96ac16e911eb1299f0fea8204fa88236fa Mon Sep 17 00:00:00 2001 From: dk1a Date: Mon, 20 Feb 2023 18:37:14 +0300 Subject: [PATCH 09/13] feat(cli): remove mud create, deprecated in #424 --- packages/cli/src/commands/create.ts | 35 ----------------------------- packages/cli/src/commands/index.ts | 2 -- 2 files changed, 37 deletions(-) delete mode 100644 packages/cli/src/commands/create.ts diff --git a/packages/cli/src/commands/create.ts b/packages/cli/src/commands/create.ts deleted file mode 100644 index 035a41bd1c..0000000000 --- a/packages/cli/src/commands/create.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { CommandModule } from "yargs"; -import { execLog } from "../utils/index.js"; -import chalk from "chalk"; - -type Options = { - name: string; - template: string; -}; - -const commandModule: CommandModule = { - command: "create ", - - deprecated: true, - - describe: "Sets up a mud project into . Requires yarn.", - - builder(yargs) { - return yargs - .options({ - template: { type: "string", desc: "Template to be used (available: [minimal, react])", default: "minimal" }, - }) - .positional("name", { type: "string", default: "mud-app" }); - }, - - async handler({ name, template }) { - console.log(chalk.red.bold("⚠️ This command will be removed soon. Use `yarn create mud` instead.")); - - const child = await execLog("yarn", ["create", "mud", name, "--template", template]); - if (child.exitCode != 0) process.exit(child.exitCode); - - process.exit(0); - }, -}; - -export default commandModule; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 4ca2a72512..4c32d7b790 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -3,7 +3,6 @@ import { CommandModule } from "yargs"; import bulkupload from "./bulkupload.js"; import callSystem from "./call-system.js"; import codegenLibdeploy from "./codegen-libdeploy.js"; -import create from "./create.js"; import deployContracts from "./deploy-contracts.js"; import devnode from "./devnode.js"; import faucet from "./faucet.js"; @@ -19,7 +18,6 @@ export const commands: CommandModule[] = [ bulkupload, callSystem, codegenLibdeploy, - create, deployContracts, devnode, faucet, From 902d4909cc74d2712d2ee01bacab286e26fae5d1 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 22 Feb 2023 14:23:55 +0300 Subject: [PATCH 10/13] fix: resolve indirect conflicts --- packages/cli/src/commands/call-system.ts | 2 +- packages/cli/src/commands/deploy-contracts.ts | 2 +- packages/cli/src/commands/system-types.ts | 2 +- packages/cli/src/commands/test.ts | 2 +- packages/cli/src/commands/trace.ts | 4 ++-- packages/cli/src/utils/build.ts | 2 +- packages/cli/src/utils/typegen.ts | 4 ++-- packages/schema-type/package.json | 2 +- packages/store/package.json | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/commands/call-system.ts b/packages/cli/src/commands/call-system.ts index 89b00bb8f7..3fecdbb5ec 100644 --- a/packages/cli/src/commands/call-system.ts +++ b/packages/cli/src/commands/call-system.ts @@ -2,7 +2,7 @@ import { defaultAbiCoder as abi } from "ethers/lib/utils.js"; import path from "path"; import type { CommandModule } from "yargs"; import { execLog } from "../utils/index.js"; -import { getTestDirectory } from "../utils/forgeConfig"; +import { getTestDirectory } from "../utils/forgeConfig.js"; type Options = { rpc?: string; diff --git a/packages/cli/src/commands/deploy-contracts.ts b/packages/cli/src/commands/deploy-contracts.ts index ad9dbbd047..744f56c462 100644 --- a/packages/cli/src/commands/deploy-contracts.ts +++ b/packages/cli/src/commands/deploy-contracts.ts @@ -2,7 +2,7 @@ import type { CommandModule } from "yargs"; import { DeployOptions, generateAndDeploy, hsr } from "../utils/index.js"; import openurl from "openurl"; import chalk from "chalk"; -import { getSrcDirectory } from "../utils/forgeConfig"; +import { getSrcDirectory } from "../utils/forgeConfig.js"; type Options = DeployOptions & { watch?: boolean; diff --git a/packages/cli/src/commands/system-types.ts b/packages/cli/src/commands/system-types.ts index 2aab2bf946..96e9e055f5 100644 --- a/packages/cli/src/commands/system-types.ts +++ b/packages/cli/src/commands/system-types.ts @@ -1,6 +1,6 @@ import type { CommandModule } from "yargs"; import { generateSystemTypes } from "../utils/index.js"; -import { systemsDir } from "../utils/constants"; +import { systemsDir } from "../utils/constants.js"; type Options = { outputDir: string; diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index 7fa77362c1..6a1a387da9 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -1,6 +1,6 @@ import type { CommandModule } from "yargs"; import { execLog, generateLibDeploy, resetLibDeploy } from "../utils/index.js"; -import { getTestDirectory } from "../utils/forgeConfig"; +import { getTestDirectory } from "../utils/forgeConfig.js"; type Options = { forgeOpts?: string; diff --git a/packages/cli/src/commands/trace.ts b/packages/cli/src/commands/trace.ts index b82abf9295..1cea44509b 100644 --- a/packages/cli/src/commands/trace.ts +++ b/packages/cli/src/commands/trace.ts @@ -4,9 +4,9 @@ import { readFileSync } from "fs"; import { Contract } from "ethers"; import { JsonRpcProvider } from "@ethersproject/providers"; import WorldAbi from "@latticexyz/solecs/abi/World.json" assert { type: "json" }; -import { getSrcDirectory } from "../utils/forgeConfig"; +import { getSrcDirectory } from "../utils/forgeConfig.js"; import path from "path"; -import { componentsDir, systemsDir } from "../utils/constants"; +import { componentsDir, systemsDir } from "../utils/constants.js"; type Options = { config?: string; diff --git a/packages/cli/src/utils/build.ts b/packages/cli/src/utils/build.ts index f6712b47dd..d3a214f8d8 100644 --- a/packages/cli/src/utils/build.ts +++ b/packages/cli/src/utils/build.ts @@ -1,7 +1,7 @@ import { execa } from "execa"; import { copyFileSync, mkdirSync, readdirSync, rmSync } from "fs"; import path from "path"; -import { getOutDirectory } from "./forgeConfig"; +import { getOutDirectory } from "./forgeConfig.js"; export async function forgeBuild(options?: { clear?: boolean }) { if (options?.clear) { diff --git a/packages/cli/src/utils/typegen.ts b/packages/cli/src/utils/typegen.ts index 80370d4b6e..1b2077f305 100644 --- a/packages/cli/src/utils/typegen.ts +++ b/packages/cli/src/utils/typegen.ts @@ -4,8 +4,8 @@ import { extractIdFromFile } from "./ids.js"; import { rmSync, writeFileSync } from "fs"; import path from "path"; import { filterAbi, forgeBuild } from "./build.js"; -import { getOutDirectory, getSrcDirectory } from "./forgeConfig"; -import { systemsDir } from "./constants"; +import { getOutDirectory, getSrcDirectory } from "./forgeConfig.js"; +import { systemsDir } from "./constants.js"; export async function generateAbiTypes( inputDir: string, diff --git a/packages/schema-type/package.json b/packages/schema-type/package.json index a8cec27140..d9795526db 100644 --- a/packages/schema-type/package.json +++ b/packages/schema-type/package.json @@ -25,7 +25,7 @@ }, "devDependencies": { "ds-test": "https://github.com/dapphub/ds-test.git#c9ce3f25bde29fc5eb9901842bf02850dfd2d084", - "forge-std": "https://github.com/foundry-rs/forge-std.git#f36dab24d63d1c1945a05ed375ce341d3c1a49ed", + "forge-std": "https://github.com/foundry-rs/forge-std.git#b4f121555729b3afb3c5ffccb62ff4b6e2818fd3", "rimraf": "^3.0.2" }, "gitHead": "218f56893d268b0c5157a3e4c603b859e287a343" diff --git a/packages/store/package.json b/packages/store/package.json index eebb2ac035..edd26520d9 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -31,7 +31,7 @@ "@types/mocha": "^9.1.1", "ds-test": "https://github.com/dapphub/ds-test.git#c9ce3f25bde29fc5eb9901842bf02850dfd2d084", "ejs": "^3.1.8", - "forge-std": "https://github.com/foundry-rs/forge-std.git#f36dab24d63d1c1945a05ed375ce341d3c1a49ed", + "forge-std": "https://github.com/foundry-rs/forge-std.git#b4f121555729b3afb3c5ffccb62ff4b6e2818fd3", "hardhat": "^2.10.1", "prettier": "^2.6.2", "prettier-plugin-solidity": "^1.0.0-beta.19", @@ -45,7 +45,7 @@ }, "peerDependencies": { "ds-test": "https://github.com/dapphub/ds-test.git#c9ce3f25bde29fc5eb9901842bf02850dfd2d084", - "forge-std": "https://github.com/foundry-rs/forge-std.git#f36dab24d63d1c1945a05ed375ce341d3c1a49ed" + "forge-std": "https://github.com/foundry-rs/forge-std.git#b4f121555729b3afb3c5ffccb62ff4b6e2818fd3" }, "gitHead": "218f56893d268b0c5157a3e4c603b859e287a343" } From 55157f41f995d246151dc40f5bdc26f998b35b45 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 22 Feb 2023 14:34:16 +0300 Subject: [PATCH 11/13] refactor(cli): add separate test:next to avoid test failures in CI --- packages/cli/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index c635755154..ba6c2de145 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -19,7 +19,8 @@ "lint": "eslint . --ext .ts", "build": "tsup", "link": "yarn link", - "test": "tsc --noEmit --moduleResolution nodenext && echo 'todo: add tests'", + "test": "tsc --noEmit && echo 'todo: add tests'", + "test:next": "tsc --noEmit --moduleResolution nodenext", "git:install": "bash git-install.sh", "release": "npm publish || echo 'version already published'" }, From ff9ef0964794b8143e2204716b51397f43f3cb7b Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 22 Feb 2023 18:52:25 +0300 Subject: [PATCH 12/13] fix(store): use released cli for gas-report --- packages/store/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/store/package.json b/packages/store/package.json index edd26520d9..28a3e6d8ef 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -23,9 +23,10 @@ "link": "yarn link", "docs": "rimraf API && hardhat docgen && echo 'label: API\norder: 50' > API/index.yml", "release": "npm publish || echo 'version already published'", - "gasreport": " ../cli/dist/index.js gas-report --path test/** --save gas-report.txt" + "gasreport": "mud gas-report --path test/** --save gas-report.txt" }, "devDependencies": { + "@latticexyz/cli": "^1.37.1", "@typechain/ethers-v5": "^9.0.0", "@types/ejs": "^3.1.1", "@types/mocha": "^9.1.1", @@ -40,8 +41,7 @@ "solidity-docgen": "^0.6.0-beta.22", "ts-node": "10.7", "typechain": "^8.1.1", - "typedoc": "^0.23.10", - "@latticexyz/cli": "^1.34.0" + "typedoc": "^0.23.10" }, "peerDependencies": { "ds-test": "https://github.com/dapphub/ds-test.git#c9ce3f25bde29fc5eb9901842bf02850dfd2d084", From f814214d2ed5f1fa49b9279c4117b62e4ade1f27 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 22 Feb 2023 16:01:51 +0000 Subject: [PATCH 13/13] test: update gas report --- packages/store/gas-report.txt | 122 +++++++++++++++++----------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/packages/store/gas-report.txt b/packages/store/gas-report.txt index d928f3ab8f..aec8528678 100644 --- a/packages/store/gas-report.txt +++ b/packages/store/gas-report.txt @@ -7,36 +7,36 @@ (test/Bytes.t.sol) | slice bytes32 with offset 10 [bytes32 output = Bytes.slice32(input, 10)]: 74 (test/Bytes.t.sol) | create bytes32 from bytes memory with offset 0 [bytes32 output = Bytes.toBytes32(input, 0)]: 22 (test/Bytes.t.sol) | create bytes32 from bytes memory with offset 16 [bytes32 output = Bytes.toBytes32(input, 16)]: 22 -(test/Gas.t.sol) | abi encode [bytes memory abiEncoded = abi.encode(mixed)]: 963 -(test/Gas.t.sol) | abi decode [Mixed memory abiDecoded = abi.decode(abiEncoded, (Mixed))]: 1746 -(test/Gas.t.sol) | custom encode [bytes memory customEncoded = customEncode(mixed)]: 1818 -(test/Gas.t.sol) | custom decode [Mixed memory customDecoded = customDecode(customEncoded)]: 2440 -(test/Gas.t.sol) | pass abi encoded bytes to external contract [someContract.doSomethingWithBytes(abiEncoded)]: 6551 -(test/Gas.t.sol) | pass custom encoded bytes to external contract [someContract.doSomethingWithBytes(customEncoded)]: 1378 -(test/MixedTable.t.sol) | store Mixed struct in storage (native solidity) [testMixed = mixed]: 92050 -(test/MixedTable.t.sol) | register MixedTable schema [MixedTable.registerSchema()]: 35085 -(test/MixedTable.t.sol) | set record in MixedTable [MixedTable.set({ key: key, u32: 1, u128: 2, a32: a32, s: s })]: 109198 -(test/MixedTable.t.sol) | get record from MixedTable [Mixed memory mixed = MixedTable.get(key)]: 13309 -(test/PackedCounter.t.sol) | get value at index of PackedCounter [packedCounter.atIndex(3)]: 261 -(test/PackedCounter.t.sol) | set value at index of PackedCounter [packedCounter = packedCounter.setAtIndex(2, 5)]: 799 -(test/PackedCounter.t.sol) | pack uint16 array into PackedCounter [PackedCounter packedCounter = PackedCounterLib.pack(counters)]: 2152 +(test/Gas.t.sol) | abi encode [bytes memory abiEncoded = abi.encode(mixed)]: 930 +(test/Gas.t.sol) | abi decode [Mixed memory abiDecoded = abi.decode(abiEncoded, (Mixed))]: 1713 +(test/Gas.t.sol) | custom encode [bytes memory customEncoded = customEncode(mixed)]: 1766 +(test/Gas.t.sol) | custom decode [Mixed memory customDecoded = customDecode(customEncoded)]: 2552 +(test/Gas.t.sol) | pass abi encoded bytes to external contract [someContract.doSomethingWithBytes(abiEncoded)]: 6537 +(test/Gas.t.sol) | pass custom encoded bytes to external contract [someContract.doSomethingWithBytes(customEncoded)]: 1341 +(test/MixedTable.t.sol) | store Mixed struct in storage (native solidity) [testMixed = mixed]: 92016 +(test/MixedTable.t.sol) | register MixedTable schema [MixedTable.registerSchema()]: 35123 +(test/MixedTable.t.sol) | set record in MixedTable [MixedTable.set({ key: key, u32: 1, u128: 2, a32: a32, s: s })]: 109190 +(test/MixedTable.t.sol) | get record from MixedTable [Mixed memory mixed = MixedTable.get(key)]: 13406 +(test/PackedCounter.t.sol) | get value at index of PackedCounter [packedCounter.atIndex(3)]: 272 +(test/PackedCounter.t.sol) | set value at index of PackedCounter [packedCounter = packedCounter.setAtIndex(2, 5)]: 830 +(test/PackedCounter.t.sol) | pack uint16 array into PackedCounter [PackedCounter packedCounter = PackedCounterLib.pack(counters)]: 2148 (test/PackedCounter.t.sol) | get total of PackedCounter [packedCounter.total()]: 33 -(test/RouteTable.t.sol) | register RouteTable schema [RouteTable.registerSchema()]: 33822 -(test/RouteTable.t.sol) | set RouteTable record [RouteTable.set(key, addr, selector, executionMode)]: 35769 -(test/RouteTable.t.sol) | get RouteTable record [Route memory systemEntry = RouteTable.get(key)]: 3723 -(test/Schema.t.sol) | encode schema with 6 entries [SchemaLib.encode]: 6042 -(test/Schema.t.sol) | get schema type at index [SchemaType schemaType1 = schema.atIndex(0)]: 191 +(test/RouteTable.t.sol) | register RouteTable schema [RouteTable.registerSchema()]: 33891 +(test/RouteTable.t.sol) | set RouteTable record [RouteTable.set(key, addr, selector, executionMode)]: 35807 +(test/RouteTable.t.sol) | get RouteTable record [Route memory systemEntry = RouteTable.get(key)]: 3745 +(test/Schema.t.sol) | encode schema with 6 entries [SchemaLib.encode]: 6044 +(test/Schema.t.sol) | get schema type at index [SchemaType schemaType1 = schema.atIndex(0)]: 200 (test/Schema.t.sol) | get number of dynamic fields from schema [uint256 num = schema.numDynamicFields()]: 80 (test/Schema.t.sol) | get number of static fields from schema [uint256 num = schema.numStaticFields()]: 91 (test/Schema.t.sol) | get static data length from schema [uint256 length = schema.staticDataLength()]: 39 (test/Schema.t.sol) | check if schema is empty (non-empty schema) [bool empty = encodedSchema.isEmpty()]: 13 (test/Schema.t.sol) | check if schema is empty (empty schema) [bool empty = encodedSchema.isEmpty()]: 13 -(test/Schema.t.sol) | validate schema [encodedSchema.validate()]: 18926 +(test/Schema.t.sol) | validate schema [encodedSchema.validate()]: 19398 (test/Slice.t.sol) | make Slice from bytes [Slice slice = SliceLib.fromBytes(data)]: 40 (test/Slice.t.sol) | get Slice length [slice.length()]: 7 (test/Slice.t.sol) | get Slice pointer [slice.pointer()]: 33 -(test/Slice.t.sol) | subslice bytes (no copy) [1:4] [Slice slice1 = SliceLib.getSubslice(data, 1, 1 + 3)]: 317 -(test/Slice.t.sol) | subslice bytes (no copy) [4:37] [Slice slice2 = SliceLib.getSubslice(data, 4, 4 + 33)]: 317 +(test/Slice.t.sol) | subslice bytes (no copy) [1:4] [Slice slice1 = SliceLib.getSubslice(data, 1, 1 + 3)]: 330 +(test/Slice.t.sol) | subslice bytes (no copy) [4:37] [Slice slice2 = SliceLib.getSubslice(data, 4, 4 + 33)]: 330 (test/Slice.t.sol) | Slice (0 bytes) to bytes memory [bytes memory sliceData0 = slice.toBytes()]: 607 (test/Slice.t.sol) | Slice (2 bytes) to bytes memory [bytes memory sliceData2 = slice.toBytes()]: 642 (test/Slice.t.sol) | Slice (32 bytes) to bytes memory [bytes memory sliceData32 = slice.toBytes()]: 510 @@ -44,49 +44,49 @@ (test/Slice.t.sol) | Slice (1024 bytes) to bytes memory [bytes memory sliceData1024 = slice.toBytes()]: 4847 (test/Slice.t.sol) | Slice (1024x1024 bytes) to bytes memory [bytes memory sliceData1024x1024 = slice.toBytes()]: 6605248 (test/Slice.t.sol) | Slice to bytes32 [bytes32 output = slice.toBytes32()]: 87 -(test/Storage.t.sol) | store 1 storage slot [Storage.store({ storagePointer: storagePointer, data: originalDataFirstSlot })]: 22506 -(test/Storage.t.sol) | store 34 bytes over 3 storage slots (with offset and safeTrail)) [Storage.store({ storagePointer: storagePointer, offset: 31, data: data1 })]: 23155 -(test/Storage.t.sol) | load 34 bytes over 3 storage slots (with offset and safeTrail)) [bytes memory data = Storage.load({ storagePointer: storagePointer, length: 34, offset: 31 })]: 1095 -(test/StoreCore.t.sol) | access non-existing record [bytes memory data1 = StoreCore.getRecord(table, key)]: 7079 -(test/StoreCore.t.sol) | access static field of non-existing record [bytes memory data2 = StoreCore.getField(table, key, 0)]: 2741 -(test/StoreCore.t.sol) | access dynamic field of non-existing record [bytes memory data3 = StoreCore.getField(table, key, 1)]: 3352 -(test/StoreCore.t.sol) | delete record (complex data, 3 slots) [StoreCore.deleteRecord(table, key)]: 8385 -(test/StoreCore.t.sol) | Check for existence of table (existent) [StoreCore.hasTable(table)]: 938 +(test/Storage.t.sol) | store 1 storage slot [Storage.store({ storagePointer: storagePointer, data: originalDataFirstSlot })]: 22511 +(test/Storage.t.sol) | store 34 bytes over 3 storage slots (with offset and safeTrail)) [Storage.store({ storagePointer: storagePointer, offset: 31, data: data1 })]: 23165 +(test/Storage.t.sol) | load 34 bytes over 3 storage slots (with offset and safeTrail)) [bytes memory data = Storage.load({ storagePointer: storagePointer, length: 34, offset: 31 })]: 1105 +(test/StoreCore.t.sol) | access non-existing record [bytes memory data1 = StoreCore.getRecord(table, key)]: 7076 +(test/StoreCore.t.sol) | access static field of non-existing record [bytes memory data2 = StoreCore.getField(table, key, 0)]: 2751 +(test/StoreCore.t.sol) | access dynamic field of non-existing record [bytes memory data3 = StoreCore.getField(table, key, 1)]: 3354 +(test/StoreCore.t.sol) | delete record (complex data, 3 slots) [StoreCore.deleteRecord(table, key)]: 8419 +(test/StoreCore.t.sol) | Check for existence of table (existent) [StoreCore.hasTable(table)]: 935 (test/StoreCore.t.sol) | check for existence of table (non-existent) [StoreCore.hasTable(table2)]: 2960 -(test/StoreCore.t.sol) | register subscriber [StoreCore.registerHook(table, subscriber)]: 67079 -(test/StoreCore.t.sol) | set record on table with subscriber [StoreCore.setRecord(table, key, data)]: 71100 -(test/StoreCore.t.sol) | set static field on table with subscriber [StoreCore.setField(table, key, 0, data)]: 26812 -(test/StoreCore.t.sol) | delete record on table with subscriber [StoreCore.deleteRecord(table, key)]: 21495 -(test/StoreCore.t.sol) | register subscriber [StoreCore.registerHook(table, subscriber)]: 67079 -(test/StoreCore.t.sol) | set (dynamic) record on table with subscriber [StoreCore.setRecord(table, key, data)]: 164483 -(test/StoreCore.t.sol) | set (dynamic) field on table with subscriber [StoreCore.setField(table, key, 1, arrayDataBytes)]: 29624 -(test/StoreCore.t.sol) | delete (dynamic) record on table with subscriber [StoreCore.deleteRecord(table, key)]: 23142 -(test/StoreCore.t.sol) | StoreCore: register schema [StoreCore.registerSchema(table, schema)]: 29943 +(test/StoreCore.t.sol) | register subscriber [StoreCore.registerHook(table, subscriber)]: 67126 +(test/StoreCore.t.sol) | set record on table with subscriber [StoreCore.setRecord(table, key, data)]: 71131 +(test/StoreCore.t.sol) | set static field on table with subscriber [StoreCore.setField(table, key, 0, data)]: 26800 +(test/StoreCore.t.sol) | delete record on table with subscriber [StoreCore.deleteRecord(table, key)]: 21563 +(test/StoreCore.t.sol) | register subscriber [StoreCore.registerHook(table, subscriber)]: 67126 +(test/StoreCore.t.sol) | set (dynamic) record on table with subscriber [StoreCore.setRecord(table, key, data)]: 164558 +(test/StoreCore.t.sol) | set (dynamic) field on table with subscriber [StoreCore.setField(table, key, 1, arrayDataBytes)]: 29636 +(test/StoreCore.t.sol) | delete (dynamic) record on table with subscriber [StoreCore.deleteRecord(table, key)]: 23210 +(test/StoreCore.t.sol) | StoreCore: register schema [StoreCore.registerSchema(table, schema)]: 29995 (test/StoreCore.t.sol) | StoreCore: get schema (warm) [Schema loadedSchema = StoreCore.getSchema(table)]: 906 -(test/StoreCore.t.sol) | set complex record with dynamic data (4 slots) [StoreCore.setRecord(table, key, data)]: 104986 -(test/StoreCore.t.sol) | get complex record with dynamic data (4 slots) [bytes memory loadedData = StoreCore.getRecord(table, key)]: 6191 -(test/StoreCore.t.sol) | compare: Set complex record with dynamic data using native solidity [testStruct = _testStruct]: 116839 -(test/StoreCore.t.sol) | compare: Set complex record with dynamic data using abi.encode [testMapping[1234] = abi.encode(_testStruct)]: 267369 -(test/StoreCore.t.sol) | set dynamic length of dynamic index 0 [StoreCoreInternal._setDynamicDataLengthAtIndex(table, key, 0, 10)]: 23582 -(test/StoreCore.t.sol) | set dynamic length of dynamic index 1 [StoreCoreInternal._setDynamicDataLengthAtIndex(table, key, 1, 99)]: 1699 -(test/StoreCore.t.sol) | reduce dynamic length of dynamic index 0 [StoreCoreInternal._setDynamicDataLengthAtIndex(table, key, 0, 5)]: 1688 -(test/StoreCore.t.sol) | set static field (1 slot) [StoreCore.setField(table, key, 0, abi.encodePacked(firstDataBytes))]: 35416 -(test/StoreCore.t.sol) | get static field (1 slot) [bytes memory loadedData = StoreCore.getField(table, key, 0)]: 2746 -(test/StoreCore.t.sol) | set static field (overlap 2 slot) [StoreCore.setField(table, key, 1, abi.encodePacked(secondDataBytes))]: 30419 -(test/StoreCore.t.sol) | get static field (overlap 2 slot) [loadedData = StoreCore.getField(table, key, 1)]: 3626 -(test/StoreCore.t.sol) | set dynamic field (1 slot, first dynamic field) [StoreCore.setField(table, key, 2, thirdDataBytes)]: 52691 -(test/StoreCore.t.sol) | get dynamic field (1 slot, first dynamic field) [loadedData = StoreCore.getField(table, key, 2)]: 3572 -(test/StoreCore.t.sol) | set dynamic field (1 slot, second dynamic field) [StoreCore.setField(table, key, 3, fourthDataBytes)]: 32818 -(test/StoreCore.t.sol) | get dynamic field (1 slot, second dynamic field) [loadedData = StoreCore.getField(table, key, 3)]: 3588 -(test/StoreCore.t.sol) | set static record (1 slot) [StoreCore.setRecord(table, key, data)]: 34699 +(test/StoreCore.t.sol) | set complex record with dynamic data (4 slots) [StoreCore.setRecord(table, key, data)]: 105039 +(test/StoreCore.t.sol) | get complex record with dynamic data (4 slots) [bytes memory loadedData = StoreCore.getRecord(table, key)]: 6235 +(test/StoreCore.t.sol) | compare: Set complex record with dynamic data using native solidity [testStruct = _testStruct]: 116815 +(test/StoreCore.t.sol) | compare: Set complex record with dynamic data using abi.encode [testMapping[1234] = abi.encode(_testStruct)]: 267532 +(test/StoreCore.t.sol) | set dynamic length of dynamic index 0 [StoreCoreInternal._setDynamicDataLengthAtIndex(table, key, 0, 10)]: 23606 +(test/StoreCore.t.sol) | set dynamic length of dynamic index 1 [StoreCoreInternal._setDynamicDataLengthAtIndex(table, key, 1, 99)]: 1726 +(test/StoreCore.t.sol) | reduce dynamic length of dynamic index 0 [StoreCoreInternal._setDynamicDataLengthAtIndex(table, key, 0, 5)]: 1718 +(test/StoreCore.t.sol) | set static field (1 slot) [StoreCore.setField(table, key, 0, abi.encodePacked(firstDataBytes))]: 35435 +(test/StoreCore.t.sol) | get static field (1 slot) [bytes memory loadedData = StoreCore.getField(table, key, 0)]: 2756 +(test/StoreCore.t.sol) | set static field (overlap 2 slot) [StoreCore.setField(table, key, 1, abi.encodePacked(secondDataBytes))]: 30462 +(test/StoreCore.t.sol) | get static field (overlap 2 slot) [loadedData = StoreCore.getField(table, key, 1)]: 3656 +(test/StoreCore.t.sol) | set dynamic field (1 slot, first dynamic field) [StoreCore.setField(table, key, 2, thirdDataBytes)]: 52722 +(test/StoreCore.t.sol) | get dynamic field (1 slot, first dynamic field) [loadedData = StoreCore.getField(table, key, 2)]: 3574 +(test/StoreCore.t.sol) | set dynamic field (1 slot, second dynamic field) [StoreCore.setField(table, key, 3, fourthDataBytes)]: 32849 +(test/StoreCore.t.sol) | get dynamic field (1 slot, second dynamic field) [loadedData = StoreCore.getField(table, key, 3)]: 3590 +(test/StoreCore.t.sol) | set static record (1 slot) [StoreCore.setRecord(table, key, data)]: 34718 (test/StoreCore.t.sol) | get static record (1 slot) [bytes memory loadedData = StoreCore.getRecord(table, key, schema)]: 1318 -(test/StoreCore.t.sol) | set static record (2 slots) [StoreCore.setRecord(table, key, data)]: 57261 -(test/StoreCore.t.sol) | get static record (2 slots) [bytes memory loadedData = StoreCore.getRecord(table, key, schema)]: 1560 +(test/StoreCore.t.sol) | set static record (2 slots) [StoreCore.setRecord(table, key, data)]: 57285 +(test/StoreCore.t.sol) | get static record (2 slots) [bytes memory loadedData = StoreCore.getRecord(table, key, schema)]: 1565 (test/StoreSwitch.t.sol) | check if delegatecall [isDelegate = StoreSwitch.isDelegateCall()]: 716 (test/StoreSwitch.t.sol) | check if delegatecall [isDelegate = StoreSwitch.isDelegateCall()]: 627 (test/System.t.sol) | extract msg.sender from calldata [address sender = _msgSender()]: 24 -(test/Vector2Table.t.sol) | register Vector2Table schema [Vector2Table.registerSchema()]: 31864 -(test/Vector2Table.t.sol) | set Vector2Table record [Vector2Table.set({ key: key, x: 1, y: 2 })]: 35735 -(test/Vector2Table.t.sol) | get Vector2Table record [Vector2 memory vector = Vector2Table.get(key)]: 3616 -(test/World.t.sol) | call autonomous system via World contract [WorldWithWorldTestSystem(address(world)).WorldTestSystem_move(entity, 1, 2)]: 43414 -(test/World.t.sol) | call delegate system via World contract [WorldWithWorldTestSystem(address(world)).WorldTestSystem_move(entity, 1, 2)]: 41241 \ No newline at end of file +(test/Vector2Table.t.sol) | register Vector2Table schema [Vector2Table.registerSchema()]: 31910 +(test/Vector2Table.t.sol) | set Vector2Table record [Vector2Table.set({ key: key, x: 1, y: 2 })]: 35773 +(test/Vector2Table.t.sol) | get Vector2Table record [Vector2 memory vector = Vector2Table.get(key)]: 3638 +(test/World.t.sol) | call autonomous system via World contract [WorldWithWorldTestSystem(address(world)).WorldTestSystem_move(entity, 1, 2)]: 43386 +(test/World.t.sol) | call delegate system via World contract [WorldWithWorldTestSystem(address(world)).WorldTestSystem_move(entity, 1, 2)]: 41226 \ No newline at end of file