@latticexyz/cli 0.0.0-main-20230704121617

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,200 @@
1
+ import chalk from "chalk";
2
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
3
+ import path from "path";
4
+ import type { CommandModule } from "yargs";
5
+ import { MUDError } from "@latticexyz/common/errors";
6
+ import { logError } from "../utils/errors";
7
+ import localPackageJson from "../../package.json" assert { type: "json" };
8
+
9
+ type Options = {
10
+ backup?: boolean;
11
+ force?: boolean;
12
+ restore?: boolean;
13
+ mudVersion?: string;
14
+ link?: string;
15
+ };
16
+
17
+ const BACKUP_FILE = ".mudbackup";
18
+ const MUD_PREFIX = "@latticexyz";
19
+
20
+ const commandModule: CommandModule<Options, Options> = {
21
+ command: "set-version",
22
+
23
+ describe: "Install a custom MUD version and optionally backup the previously installed version",
24
+
25
+ builder(yargs) {
26
+ return yargs.options({
27
+ backup: { type: "boolean", description: `Back up the current MUD versions to "${BACKUP_FILE}"` },
28
+ force: {
29
+ type: "boolean",
30
+ description: `Backup fails if a "${BACKUP_FILE}" file is found, unless --force is provided`,
31
+ },
32
+ restore: { type: "boolean", description: `Restore the previous MUD versions from "${BACKUP_FILE}"` },
33
+ mudVersion: { alias: "v", type: "string", description: "The MUD version to install" },
34
+ link: { alias: "l", type: "string", description: "Relative path to the local MUD root directory to link" },
35
+ });
36
+ },
37
+
38
+ async handler(options) {
39
+ try {
40
+ if (!options.mudVersion && !options.link && !options.restore) {
41
+ throw new MUDError("`--mudVersion` or `--link` is required unless --restore is provided.");
42
+ }
43
+
44
+ // `link` and `mudVersion` are mutually exclusive
45
+ if (options.link && options.mudVersion) {
46
+ throw new MUDError("Options `--link` and `--mudVersion` are mutually exclusive");
47
+ }
48
+
49
+ // Resolve the `canary` version number if needed
50
+ options.mudVersion =
51
+ options.mudVersion === "canary" ? await getCanaryVersion(localPackageJson.name) : options.mudVersion;
52
+
53
+ // Read the current package.json
54
+ const rootPath = "./package.json";
55
+ const { workspaces } = updatePackageJson(rootPath, options);
56
+
57
+ // Load the package.json of each workspace
58
+ if (workspaces) {
59
+ for (const workspace of workspaces) {
60
+ const filePath = path.join(workspace, "/package.json");
61
+ updatePackageJson(filePath, options);
62
+ }
63
+ }
64
+ } catch (e) {
65
+ logError(e);
66
+ } finally {
67
+ process.exit(0);
68
+ }
69
+ },
70
+ };
71
+
72
+ function updatePackageJson(filePath: string, options: Options): { workspaces?: string[] } {
73
+ const { restore, force, link } = options;
74
+ let { backup, mudVersion } = options;
75
+
76
+ const backupFilePath = path.join(path.dirname(filePath), BACKUP_FILE);
77
+ const backupFileExists = existsSync(backupFilePath);
78
+
79
+ // Create a backup file for previous MUD versions by default if linking to local MUD
80
+ if (link && !backupFileExists) backup = true;
81
+
82
+ // If `backup` is true and force not set, check if a backup file already exists and throw an error if it does
83
+ if (backup && !force && backupFileExists) {
84
+ throw new MUDError(
85
+ `A backup file already exists at ${backupFilePath}.\nUse --force to overwrite it or --restore to restore it.`
86
+ );
87
+ }
88
+
89
+ const packageJson = readPackageJson(filePath);
90
+
91
+ // Load .mudbackup if `restore` is true
92
+ const backupJson = restore ? readPackageJson(backupFilePath) : undefined;
93
+
94
+ // Find all MUD dependencies
95
+ const mudDependencies: Record<string, string> = {};
96
+ for (const key in packageJson.dependencies) {
97
+ if (key.startsWith(MUD_PREFIX)) {
98
+ mudDependencies[key] = packageJson.dependencies[key];
99
+ }
100
+ }
101
+
102
+ // Find all MUD devDependencies
103
+ const mudDevDependencies: Record<string, string> = {};
104
+ for (const key in packageJson.devDependencies) {
105
+ if (key.startsWith(MUD_PREFIX)) {
106
+ mudDevDependencies[key] = packageJson.devDependencies[key];
107
+ }
108
+ }
109
+
110
+ // Back up the current dependencies if `backup` is true
111
+ if (backup) {
112
+ writeFileSync(
113
+ backupFilePath,
114
+ JSON.stringify({ dependencies: mudDependencies, devDependencies: mudDevDependencies }, null, 2)
115
+ );
116
+ console.log(chalk.green(`Backed up MUD dependencies from ${filePath} to ${backupFilePath}`));
117
+ }
118
+
119
+ // Update the dependencies
120
+ for (const key in packageJson.dependencies) {
121
+ if (key.startsWith(MUD_PREFIX)) {
122
+ packageJson.dependencies[key] = resolveMudVersion(key, "dependencies");
123
+ }
124
+ }
125
+
126
+ // Update the devDependencies
127
+ for (const key in packageJson.devDependencies) {
128
+ if (key.startsWith(MUD_PREFIX)) {
129
+ packageJson.devDependencies[key] = resolveMudVersion(key, "devDependencies");
130
+ }
131
+ }
132
+
133
+ // Write the updated package.json
134
+ writeFileSync(filePath, JSON.stringify(packageJson, null, 2) + "\n");
135
+
136
+ console.log(`Updating ${filePath}`);
137
+ logComparison(mudDependencies, packageJson.dependencies);
138
+ logComparison(mudDevDependencies, packageJson.devDependencies);
139
+
140
+ // Remove the backup file if `restore` is true and `backup` is false
141
+ // because the old backup file is no longer needed
142
+ if (restore && !backup) {
143
+ rmSync(backupFilePath);
144
+ console.log(chalk.green(`Cleaned up ${backupFilePath}`));
145
+ }
146
+
147
+ return packageJson;
148
+
149
+ function resolveMudVersion(key: string, type: "dependencies" | "devDependencies") {
150
+ if (restore && backupJson) return backupJson[type][key];
151
+ if (link) mudVersion = resolveLinkPath(filePath, link, key);
152
+ if (!mudVersion) return packageJson[type][key];
153
+ return mudVersion;
154
+ }
155
+ }
156
+
157
+ function readPackageJson(path: string): {
158
+ workspaces?: string[];
159
+ dependencies: Record<string, string>;
160
+ devDependencies: Record<string, string>;
161
+ } {
162
+ try {
163
+ const jsonString = readFileSync(path, "utf8");
164
+ return JSON.parse(jsonString);
165
+ } catch {
166
+ throw new MUDError("Could not read JSON at " + path);
167
+ }
168
+ }
169
+
170
+ async function getCanaryVersion(pkg: string) {
171
+ try {
172
+ console.log(chalk.blue("fetching MUD canary version..."));
173
+ const result = await (await fetch(`https://registry.npmjs.org/${pkg}`)).json();
174
+ const canary = result["dist-tags"].canary;
175
+ console.log(chalk.green("MUD canary version:", canary));
176
+ return canary;
177
+ } catch (e) {
178
+ throw new MUDError(`Could not fetch canary version of ${pkg}`);
179
+ }
180
+ }
181
+
182
+ function logComparison(prev: Record<string, string>, curr: Record<string, string>) {
183
+ for (const key in prev) {
184
+ if (prev[key] !== curr[key]) {
185
+ console.log(`${key}: ${chalk.red(prev[key])} -> ${chalk.green(curr[key])}`);
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Returns path of the package to link, given a path to a local MUD clone and a package
192
+ */
193
+ function resolveLinkPath(packageJsonPath: string, mudLinkPath: string, pkg: string) {
194
+ const pkgName = pkg.replace(MUD_PREFIX, "");
195
+ const packageJsonToRootPath = path.relative(path.dirname(packageJsonPath), process.cwd());
196
+ const linkPath = path.join(packageJsonToRootPath, mudLinkPath, "packages", pkgName);
197
+ return "link:" + linkPath;
198
+ }
199
+
200
+ export default commandModule;
@@ -0,0 +1,33 @@
1
+ import path from "path";
2
+ import type { CommandModule } from "yargs";
3
+ import { loadConfig } from "@latticexyz/config/node";
4
+ import { StoreConfig } from "@latticexyz/store";
5
+ import { tablegen } from "@latticexyz/store/codegen";
6
+ import { getSrcDirectory } from "@latticexyz/common/foundry";
7
+
8
+ type Options = {
9
+ configPath?: string;
10
+ };
11
+
12
+ const commandModule: CommandModule<Options, Options> = {
13
+ command: "tablegen",
14
+
15
+ describe: "Autogenerate MUD Store table libraries based on the config file",
16
+
17
+ builder(yargs) {
18
+ return yargs.options({
19
+ configPath: { type: "string", desc: "Path to the config file" },
20
+ });
21
+ },
22
+
23
+ async handler({ configPath }) {
24
+ const config = (await loadConfig(configPath)) as StoreConfig;
25
+ const srcDir = await getSrcDirectory();
26
+
27
+ await tablegen(config, path.join(srcDir, config.codegenDirectory));
28
+
29
+ process.exit(0);
30
+ },
31
+ };
32
+
33
+ export default commandModule;
@@ -0,0 +1,70 @@
1
+ import type { CommandModule } from "yargs";
2
+ import { anvil, forge, getRpcUrl } from "@latticexyz/common/foundry";
3
+ import chalk from "chalk";
4
+ import { rmSync, writeFileSync } from "fs";
5
+ import { yDeployOptions } from "./deploy";
6
+ import { deployHandler, DeployOptions } from "../utils";
7
+
8
+ type Options = DeployOptions & { port?: number; worldAddress?: string; forgeOptions?: string };
9
+
10
+ const WORLD_ADDRESS_FILE = ".mudtest";
11
+
12
+ const commandModule: CommandModule<Options, Options> = {
13
+ command: "test",
14
+
15
+ describe: "Run tests in MUD contracts",
16
+
17
+ builder(yargs) {
18
+ return yargs.options({
19
+ ...yDeployOptions,
20
+ port: { type: "number", description: "Port to run internal node for fork testing on", default: 4242 },
21
+ worldAddress: {
22
+ type: "string",
23
+ description:
24
+ "Address of an existing world contract. If provided, deployment is skipped and the RPC provided in the foundry.toml is used for fork testing.",
25
+ },
26
+ forgeOptions: { type: "string", description: "Options to pass to forge test" },
27
+ });
28
+ },
29
+
30
+ async handler(args) {
31
+ // Start an internal anvil process if no world address is provided
32
+ if (!args.worldAddress) {
33
+ const anvilArgs = ["--block-base-fee-per-gas", "0", "--port", String(args.port)];
34
+ anvil(anvilArgs);
35
+ }
36
+
37
+ const forkRpc = args.worldAddress ? await getRpcUrl(args.profile) : `http://127.0.0.1:${args.port}`;
38
+
39
+ const worldAddress =
40
+ args.worldAddress ??
41
+ (
42
+ await deployHandler({
43
+ ...args,
44
+ saveDeployment: false,
45
+ rpc: forkRpc,
46
+ })
47
+ ).worldAddress;
48
+
49
+ console.log(chalk.blue("World address", worldAddress));
50
+
51
+ // Create a temporary file to pass the world address to the tests
52
+ writeFileSync(WORLD_ADDRESS_FILE, worldAddress);
53
+
54
+ const userOptions = args.forgeOptions?.replaceAll("\\", "").split(" ") ?? [];
55
+ try {
56
+ const testResult = await forge(["test", "--fork-url", forkRpc, ...userOptions], {
57
+ profile: args.profile,
58
+ });
59
+ console.log(testResult);
60
+ } catch (e) {
61
+ console.error(e);
62
+ }
63
+
64
+ rmSync(WORLD_ADDRESS_FILE);
65
+
66
+ process.exit(0);
67
+ },
68
+ };
69
+
70
+ export default commandModule;
@@ -0,0 +1,108 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ import type { CommandModule } from "yargs";
3
+ import { ethers } from "ethers";
4
+
5
+ import { loadConfig } from "@latticexyz/config/node";
6
+ import { MUDError } from "@latticexyz/common/errors";
7
+ import { cast, getRpcUrl, getSrcDirectory } from "@latticexyz/common/foundry";
8
+ import { TableId } from "@latticexyz/common";
9
+ import { StoreConfig } from "@latticexyz/store";
10
+ import { resolveWorldConfig, WorldConfig } from "@latticexyz/world";
11
+ import { IBaseWorld } from "@latticexyz/world/types/ethers-contracts/IBaseWorld";
12
+ import IBaseWorldData from "@latticexyz/world/abi/IBaseWorld.sol/IBaseWorld.json" assert { type: "json" };
13
+ import { getChainId, getExistingContracts } from "../utils";
14
+
15
+ const systemsTableId = new TableId("", "Systems");
16
+
17
+ type Options = {
18
+ tx: string;
19
+ worldAddress?: string;
20
+ configPath?: string;
21
+ profile?: string;
22
+ srcDir?: string;
23
+ rpc?: string;
24
+ };
25
+
26
+ const commandModule: CommandModule<Options, Options> = {
27
+ command: "trace",
28
+
29
+ describe: "Display the trace of a transaction",
30
+
31
+ builder(yargs) {
32
+ return yargs.options({
33
+ tx: { type: "string", required: true, description: "Transaction hash to replay" },
34
+ worldAddress: {
35
+ type: "string",
36
+ description: "World contract address. Defaults to the value from worlds.json, based on rpc's chainId",
37
+ },
38
+ configPath: { type: "string", description: "Path to the config file" },
39
+ profile: { type: "string", description: "The foundry profile to use" },
40
+ srcDir: { type: "string", description: "Source directory. Defaults to foundry src directory." },
41
+ rpc: { type: "string", description: "json rpc endpoint. Defaults to foundry's configured eth_rpc_url" },
42
+ });
43
+ },
44
+
45
+ async handler(args) {
46
+ args.profile ??= process.env.FOUNDRY_PROFILE;
47
+ const { profile } = args;
48
+ args.srcDir ??= await getSrcDirectory(profile);
49
+ args.rpc ??= await getRpcUrl(profile);
50
+ const { tx, configPath, srcDir, rpc } = args;
51
+
52
+ const existingContracts = getExistingContracts(srcDir);
53
+
54
+ // Load the config
55
+ const mudConfig = (await loadConfig(configPath)) as StoreConfig & WorldConfig;
56
+
57
+ const resolvedConfig = resolveWorldConfig(
58
+ mudConfig,
59
+ existingContracts.map(({ basename }) => basename)
60
+ );
61
+
62
+ // Get worldAddress either from args or from worldsFile
63
+ const worldAddress = args.worldAddress ?? (await getWorldAddress(mudConfig.worldsFile, rpc));
64
+
65
+ // Create World contract instance from deployed address
66
+ const provider = new ethers.providers.StaticJsonRpcProvider(rpc);
67
+ const WorldContract = new ethers.Contract(worldAddress, IBaseWorldData.abi, provider) as IBaseWorld;
68
+
69
+ // TODO account for multiple namespaces (https://github.com/latticexyz/mud/issues/994)
70
+ const namespace = mudConfig.namespace;
71
+ const names = Object.values(resolvedConfig.systems).map(({ name }) => name);
72
+
73
+ const labels: { name: string; address: string }[] = [];
74
+ for (const name of names) {
75
+ const systemSelector = new TableId(namespace, name);
76
+ // Get the first field of `Systems` table (the table maps system name to its address and other data)
77
+ const address = await WorldContract.getField(systemsTableId.toHex(), [systemSelector.toHex()], 0);
78
+ labels.push({ name, address });
79
+ }
80
+
81
+ const result = await cast([
82
+ "run",
83
+ "--label",
84
+ `${worldAddress}:World`,
85
+ ...labels.map(({ name, address }) => ["--label", `${address}:${name}`]).flat(),
86
+ `${tx}`,
87
+ ]);
88
+ console.log(result);
89
+
90
+ process.exit(0);
91
+ },
92
+ };
93
+
94
+ export default commandModule;
95
+
96
+ async function getWorldAddress(worldsFile: string, rpc: string) {
97
+ if (existsSync(worldsFile)) {
98
+ const chainId = await getChainId(rpc);
99
+ const deploys = JSON.parse(readFileSync(worldsFile, "utf-8"));
100
+
101
+ if (!deploys[chainId]) {
102
+ throw new MUDError(`chainId ${chainId} is missing in worldsFile "${worldsFile}"`);
103
+ }
104
+ return deploys[chainId].address as string;
105
+ } else {
106
+ throw new MUDError("worldAddress is not specified and worldsFile is missing");
107
+ }
108
+ }
@@ -0,0 +1,34 @@
1
+ import type { CommandModule } from "yargs";
2
+ import { loadConfig } from "@latticexyz/config/node";
3
+ import { StoreConfig } from "@latticexyz/store";
4
+ import { tsgen } from "../render-ts/tsgen";
5
+
6
+ type Options = {
7
+ configPath: string;
8
+ out: string;
9
+ };
10
+
11
+ const commandModule: CommandModule<Options, Options> = {
12
+ command: "tsgen",
13
+
14
+ describe: "Autogenerate MUD typescript definitions based on the config file",
15
+
16
+ builder(yargs) {
17
+ return yargs.options({
18
+ configPath: { type: "string", demandOption: true, desc: "Path to the config file" },
19
+ out: { type: "string", demandOption: true, desc: "Directory to output MUD typescript definition files" },
20
+ });
21
+ },
22
+
23
+ async handler(args) {
24
+ const { configPath, out } = args;
25
+
26
+ const config = (await loadConfig(configPath)) as StoreConfig;
27
+
28
+ await tsgen(config, out);
29
+
30
+ process.exit(0);
31
+ },
32
+ };
33
+
34
+ export default commandModule;
@@ -0,0 +1,57 @@
1
+ import type { CommandModule } from "yargs";
2
+ import { loadConfig } from "@latticexyz/config/node";
3
+ import { StoreConfig } from "@latticexyz/store";
4
+ import { WorldConfig } from "@latticexyz/world";
5
+ import { worldgen } from "@latticexyz/world/node";
6
+ import { getSrcDirectory } from "@latticexyz/common/foundry";
7
+ import path from "path";
8
+ import { rmSync } from "fs";
9
+ import { getExistingContracts } from "../utils";
10
+
11
+ type Options = {
12
+ configPath?: string;
13
+ clean?: boolean;
14
+ srcDir?: string;
15
+ config?: StoreConfig & WorldConfig;
16
+ };
17
+
18
+ const commandModule: CommandModule<Options, Options> = {
19
+ command: "worldgen",
20
+
21
+ describe: "Autogenerate interfaces for Systems and World based on existing contracts and the config file",
22
+
23
+ builder(yargs) {
24
+ return yargs.options({
25
+ configPath: { type: "string", desc: "Path to the config file" },
26
+ clean: {
27
+ type: "boolean",
28
+ desc: "Clear the worldgen directory before generating new interfaces (defaults to true)",
29
+ default: true,
30
+ },
31
+ });
32
+ },
33
+
34
+ async handler(args) {
35
+ await worldgenHandler(args);
36
+ process.exit(0);
37
+ },
38
+ };
39
+
40
+ export async function worldgenHandler(args: Options) {
41
+ const srcDir = args.srcDir ?? (await getSrcDirectory());
42
+
43
+ const existingContracts = getExistingContracts(srcDir);
44
+
45
+ // Load the config
46
+ const mudConfig = args.config ?? ((await loadConfig(args.configPath)) as StoreConfig & WorldConfig);
47
+
48
+ const outputBaseDirectory = path.join(srcDir, mudConfig.codegenDirectory);
49
+
50
+ // clear the worldgen directory
51
+ if (args.clean) rmSync(path.join(outputBaseDirectory, mudConfig.worldgenDirectory), { recursive: true, force: true });
52
+
53
+ // generate new interfaces
54
+ await worldgen(mudConfig, existingContracts, outputBaseDirectory);
55
+ }
56
+
57
+ export default commandModule;
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./render-ts";
2
+ export * from "./utils/deployHandler";
@@ -0,0 +1,11 @@
1
+ // adding .js to minimal would break clients down the line because it probably won't get a synthetic default import
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ declare module "protobufjs/minimal" {
4
+ export const configure: any;
5
+ export const util: any;
6
+ export const Reader: any;
7
+ export type Reader = any;
8
+ export const Writer: any;
9
+ export type Writer = any;
10
+ }
11
+ declare module "rollup-plugin-preserve-shebang";
package/src/mud.ts ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ import yargs from "yargs";
4
+ import { hideBin } from "yargs/helpers";
5
+ import { commands } from "./commands";
6
+ import { logError } from "./utils/errors";
7
+
8
+ // Load .env file into process.env
9
+ import * as dotenv from "dotenv";
10
+ import chalk from "chalk";
11
+ dotenv.config();
12
+
13
+ yargs(hideBin(process.argv))
14
+ // Explicit name to display in help (by default it's the entry file, which may not be "mud" for e.g. ts-node)
15
+ .scriptName("mud")
16
+ // Use the commands directory to scaffold
17
+ // 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
18
+ .command(commands as any)
19
+ // Enable strict mode.
20
+ .strict()
21
+ // Custom error handler
22
+ .fail((msg, err) => {
23
+ console.error(chalk.red(msg));
24
+ if (msg.includes("Missing required argument")) {
25
+ console.log(
26
+ chalk.yellow(`Run 'pnpm mud ${process.argv[2]} --help' for a list of available and required arguments.`)
27
+ );
28
+ }
29
+ console.log("");
30
+ logError(err);
31
+ console.log("");
32
+
33
+ process.exit(1);
34
+ })
35
+ // Useful aliases.
36
+ .alias({ h: "help" }).argv;
@@ -0,0 +1,5 @@
1
+ export * from "./recsV1TableOptions.js";
2
+ export * from "./renderRecsV1Tables.js";
3
+ export * from "./schemaTypesToRecsTypeStrings.js";
4
+ export * from "./tsgen.js";
5
+ export * from "./types.js";
@@ -0,0 +1,39 @@
1
+ import { StoreConfig } from "@latticexyz/store";
2
+ import { resolveAbiOrUserType } from "@latticexyz/store/codegen";
3
+ import { schemaTypesToRecsTypeStrings } from "./schemaTypesToRecsTypeStrings";
4
+ import { RecsV1TableOptions } from "./types";
5
+
6
+ export function getRecsV1TableOptions(config: StoreConfig): RecsV1TableOptions {
7
+ const tableOptions = [];
8
+ for (const tableName of Object.keys(config.tables)) {
9
+ const tableData = config.tables[tableName];
10
+
11
+ const fields = Object.keys(tableData.schema).map((name) => {
12
+ const abiOrUserType = tableData.schema[name];
13
+ const { schemaType } = resolveAbiOrUserType(abiOrUserType, config);
14
+
15
+ const recsTypeString = schemaTypesToRecsTypeStrings[schemaType];
16
+
17
+ return {
18
+ recsTypeString,
19
+ name,
20
+ };
21
+ });
22
+
23
+ // WARNING: skip tables without a static tableId
24
+ if (tableData.tableIdArgument) continue;
25
+ const staticResourceData = {
26
+ namespace: config.namespace,
27
+ name: tableData.name,
28
+ };
29
+
30
+ tableOptions.push({
31
+ tableName,
32
+ fields,
33
+ staticResourceData,
34
+ });
35
+ }
36
+ return {
37
+ tables: tableOptions,
38
+ };
39
+ }
@@ -0,0 +1,31 @@
1
+ import { RecsV1TableOptions } from "./types";
2
+
3
+ export function renderRecsV1Tables(options: RecsV1TableOptions) {
4
+ const { tables } = options;
5
+
6
+ return `/* Autogenerated file. Do not edit manually. */
7
+
8
+ import { TableId } from "@latticexyz/common";
9
+ import { defineComponent, Type as RecsType, World } from "@latticexyz/recs";
10
+
11
+ export function defineContractComponents(world: World) {
12
+ return {
13
+ ${tables.map((table) => `${table.tableName}: ${renderDefineComponent(table)},`).join("")}
14
+ }
15
+ }
16
+ `;
17
+ }
18
+
19
+ function renderDefineComponent(table: RecsV1TableOptions["tables"][number]) {
20
+ const { namespace, name } = table.staticResourceData;
21
+ return `
22
+ (() => {
23
+ const tableId = new TableId("${namespace}", "${name}");
24
+ return defineComponent(world, {
25
+ ${table.fields.map(({ name, recsTypeString }) => `${name}: ${recsTypeString},`).join("")}
26
+ }, {
27
+ metadata: { contractId: tableId.toHex(), tableId: tableId.toString() },
28
+ });
29
+ })()
30
+ `;
31
+ }