create-eclesia-indexer 1.0.2

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.
package/dist/index.js ADDED
@@ -0,0 +1,355 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from "node:fs";
3
+ import path, { dirname, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import colors from "picocolors";
6
+ import enquirer from "enquirer";
7
+ import fse from "fs-extra";
8
+
9
+ //#region src/create-indexer.ts
10
+ const { prompt } = enquirer;
11
+ const { ensureDirSync, copySync, writeFileSync, readFileSync } = fse;
12
+ const __filename$1 = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename$1);
14
+ const availableModules = [
15
+ {
16
+ name: "Auth",
17
+ value: "auth",
18
+ hint: "Account and authentication data"
19
+ },
20
+ {
21
+ name: "Bank",
22
+ value: "bank",
23
+ hint: "Token transfers and balances"
24
+ },
25
+ {
26
+ name: "Staking",
27
+ value: "staking",
28
+ hint: "Validator and delegation data"
29
+ }
30
+ ];
31
+ const minimalAvailableModules = [{
32
+ name: "Auth",
33
+ value: "auth",
34
+ hint: "Account and authentication data"
35
+ }, {
36
+ name: "Bank",
37
+ value: "bank",
38
+ hint: "Token transfers and balances"
39
+ }];
40
+ async function createIndexer(initialProjectName) {
41
+ const config = await gatherProjectInfo(initialProjectName);
42
+ const targetDir = resolve(process.cwd(), config.projectName);
43
+ console.log(colors.blue("📁 Creating project directory..."));
44
+ ensureDirSync(targetDir);
45
+ console.log(colors.blue("📋 Copying files..."));
46
+ await copyTemplateFiles(config, targetDir);
47
+ console.log(colors.blue("🔧 Processing template variables..."));
48
+ await processTemplates(config, targetDir);
49
+ console.log(colors.blue("📦 Installing dependencies..."));
50
+ await installDependencies(config, targetDir);
51
+ console.log(colors.blue("📦 Building..."));
52
+ await buildProject(config, targetDir);
53
+ console.log();
54
+ console.log(colors.green("🎉 Your indexer is ready!"));
55
+ console.log();
56
+ console.log("Next steps:");
57
+ console.log(colors.cyan(` cd ${config.projectName}`));
58
+ console.log(colors.cyan(` ${config.packageManager} local-dev:start # To run a self-contained local development environment with Postgres`));
59
+ console.log();
60
+ console.log("If you want to run the indexer against an external Postgres database instead of the local development environment, set the database connection string in the .env file.");
61
+ console.log();
62
+ console.log(colors.cyan(` ${config.packageManager} start # To run the indexer`));
63
+ console.log();
64
+ console.log("Happy indexing!");
65
+ console.log();
66
+ }
67
+ async function gatherProjectInfo(initialProjectName) {
68
+ const answers1 = await prompt([
69
+ {
70
+ type: "input",
71
+ name: "projectName",
72
+ message: "Project name:",
73
+ initial: initialProjectName || "my-indexer",
74
+ skip: !!initialProjectName
75
+ },
76
+ {
77
+ type: "input",
78
+ name: "chainName",
79
+ message: "Chain name:",
80
+ initial: "cosmos-hub"
81
+ },
82
+ {
83
+ type: "input",
84
+ name: "chainPrefix",
85
+ message: "Chain address prefix:",
86
+ initial: "cosmos"
87
+ },
88
+ {
89
+ type: "input",
90
+ name: "description",
91
+ message: "Description:",
92
+ initial: "A custom Cosmos SDK chain indexer"
93
+ },
94
+ {
95
+ type: "input",
96
+ name: "rpcEndpoint",
97
+ message: "RPC endpoint:",
98
+ initial: "https://rpc.cosmos.network",
99
+ validate: (value) => {
100
+ try {
101
+ const url = new URL(value);
102
+ if (url.protocol === "http:" || url.protocol === "https:" || url.protocol === "ws:" || url.protocol === "wss:") return true;
103
+ return "Please enter a valid HTTP/HTTPS/WS/WSS URL.";
104
+ } catch (err) {
105
+ return "Please enter a valid URL: " + err;
106
+ }
107
+ }
108
+ },
109
+ {
110
+ type: "toggle",
111
+ name: "minimal",
112
+ message: "Minimal block indexing? (Only stores heights)",
113
+ enabled: "Yes",
114
+ disabled: "No",
115
+ initial: false
116
+ },
117
+ {
118
+ type: "number",
119
+ name: "queueSize",
120
+ message: "Number of blocks to keep prefetched (queue size):",
121
+ initial: 200
122
+ },
123
+ {
124
+ type: "number",
125
+ name: "startHeight",
126
+ message: "Height to start indexing from (>1 not compatible with standard modules):",
127
+ initial: 1
128
+ }
129
+ ]);
130
+ if (answers1.startHeight == 1) answers1.processGenesis = (await prompt([{
131
+ type: "toggle",
132
+ name: "processGenesis",
133
+ message: "Process genesis file?",
134
+ enabled: "Yes",
135
+ disabled: "No"
136
+ }])).processGenesis;
137
+ else answers1.processGenesis = "No";
138
+ if (answers1.processGenesis === "Yes") {
139
+ const genesisPathQuestion = await prompt([{
140
+ type: "input",
141
+ name: "genesisPath",
142
+ message: "Path to genesis file:",
143
+ initial: "./genesis.json",
144
+ validate: (value) => {
145
+ try {
146
+ fse.accessSync(value, fse.constants.R_OK);
147
+ return true;
148
+ } catch (err) {
149
+ return "File not found or not readable. Please enter a valid path: " + err;
150
+ }
151
+ }
152
+ }]);
153
+ answers1.genesisPath = genesisPathQuestion.genesisPath ? path.resolve(genesisPathQuestion.genesisPath) : null;
154
+ } else answers1.genesisPath = null;
155
+ const questions2 = [
156
+ {
157
+ type: "multiselect",
158
+ name: "modules",
159
+ message: "Select modules to include:",
160
+ choices: answers1.startHeight == 1 && !answers1.minimal && answers1.processGenesis ? availableModules : minimalAvailableModules,
161
+ initial: answers1.startHeight == 1 && !answers1.minimal && answers1.processGenesis ? [
162
+ 0,
163
+ 1,
164
+ 2
165
+ ] : [0, 1]
166
+ },
167
+ {
168
+ type: "select",
169
+ name: "packageManager",
170
+ message: "Package manager:",
171
+ choices: [
172
+ {
173
+ name: "pnpm",
174
+ hint: "recommended"
175
+ },
176
+ { name: "npm" },
177
+ { name: "yarn" }
178
+ ],
179
+ initial: 0
180
+ },
181
+ {
182
+ type: "select",
183
+ name: "logLevel",
184
+ message: "Log level:",
185
+ choices: [
186
+ "error",
187
+ "warn",
188
+ "info",
189
+ "verbose",
190
+ "debug",
191
+ "silly"
192
+ ],
193
+ initial: 4
194
+ }
195
+ ];
196
+ const answers2 = await prompt(questions2);
197
+ return {
198
+ ...answers1,
199
+ ...answers2
200
+ };
201
+ }
202
+ async function copyTemplateFiles(config, targetDir) {
203
+ const templatesDir = resolve(__dirname, "..", "templates", "basic");
204
+ if (config.genesisPath) {
205
+ console.log(colors.blue("📋 Copying genesis file..."));
206
+ copySync(config.genesisPath, targetDir + "/genesis.json");
207
+ }
208
+ console.log(colors.blue("📋 Copying template files..."));
209
+ copySync(templatesDir, targetDir, { filter: (src) => {
210
+ if (src.includes(".template")) return false;
211
+ if (config.packageManager !== "pnpm" && src.includes("pnpm-workspace")) return false;
212
+ if (src.includes("Dockerfile")) return false;
213
+ return true;
214
+ } });
215
+ copySync(resolve(templatesDir, "Dockerfile." + config.packageManager), targetDir + "/Dockerfile");
216
+ [
217
+ "package.json.template",
218
+ "src/index.ts.template",
219
+ "tsconfig.json.template",
220
+ "docker-compose.yml.template",
221
+ "README.md.template",
222
+ ".env.template"
223
+ ].forEach((templateFile) => {
224
+ const srcPath = resolve(templatesDir, templateFile);
225
+ const destPath = resolve(targetDir, templateFile.replace(".template", ""));
226
+ try {
227
+ copySync(srcPath, destPath);
228
+ } catch (_error) {}
229
+ });
230
+ }
231
+ async function processTemplates(config, targetDir) {
232
+ let polling = false;
233
+ const url = new URL(config.rpcEndpoint);
234
+ if (url.protocol === "http:" || url.protocol === "https:") polling = true;
235
+ const templateVars = {
236
+ PROJECT_NAME: config.projectName,
237
+ CHAIN_NAME: config.chainName,
238
+ DESCRIPTION: config.description,
239
+ RPC_ENDPOINT: config.rpcEndpoint,
240
+ PG_CONNECTION_STRING: "postgres://postgres:password@postgres:5432/indexer",
241
+ LOG_LEVEL: config.logLevel,
242
+ QUEUE_SIZE: config.queueSize.toString(),
243
+ USE_POLLING: polling ? "true" : "false",
244
+ PROCESS_GENESIS: config.processGenesis + "",
245
+ GENESIS_PATH: path.resolve(targetDir, "genesis.json"),
246
+ MINIMAL: config.minimal ? "true" : "false",
247
+ START_HEIGHT: config.startHeight.toString(),
248
+ CHAIN_PREFIX: config.chainName.split("-")[0] || "cosmos",
249
+ MODULES_IMPORT: generateModulesImport(config),
250
+ PACKAGE_MANAGER: config.packageManager,
251
+ MODULES_INSTANTIATION: generateModulesInstantiation(config),
252
+ MODULES_ARRAY: generateModulesArray(config)
253
+ };
254
+ [
255
+ "package.json",
256
+ "src/index.ts",
257
+ "tsconfig.json",
258
+ "docker-compose.yml",
259
+ "README.md",
260
+ "pnpm-workspace.yaml",
261
+ ".env"
262
+ ].forEach((file) => {
263
+ const filePath = resolve(targetDir, file);
264
+ try {
265
+ let content = readFileSync(filePath, "utf-8");
266
+ Object.entries(templateVars).forEach(([key, value]) => {
267
+ const regex = new RegExp(`{{${key}}}`, "g");
268
+ content = content.replace(regex, value);
269
+ });
270
+ writeFileSync(filePath, content);
271
+ } catch (_error) {}
272
+ });
273
+ }
274
+ function generateModulesImport(config) {
275
+ const imports = [];
276
+ imports.push(" Blocks");
277
+ if (config.modules.includes("Auth")) imports.push(" AuthModule");
278
+ if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) imports.push(" BankModule");
279
+ if (config.modules.includes("Staking") && !config.minimal) imports.push(" StakingModule");
280
+ return `import {\n${imports.join(",")}\n} from "@eclesia/core-modules-pg";\n`;
281
+ }
282
+ function generateModulesInstantiation(config) {
283
+ const instantiations = [];
284
+ if (config.minimal) instantiations.push("const blocksModule = new Blocks.MinimalBlocksModule(registry);");
285
+ else instantiations.push("const blocksModule = new Blocks.FullBlocksModule(registry);");
286
+ if (config.modules.includes("Auth")) instantiations.push("const authModule = new AuthModule(registry);");
287
+ if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) instantiations.push("const bankModule = new BankModule(registry);");
288
+ if (config.modules.includes("Staking") && !config.minimal) instantiations.push("const stakingModule = new StakingModule(registry);");
289
+ return instantiations.join("\n");
290
+ }
291
+ function generateModulesArray(config) {
292
+ const moduleNames = [];
293
+ moduleNames.push("blocksModule");
294
+ if (config.modules.includes("Auth")) moduleNames.push("authModule");
295
+ if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) moduleNames.push("bankModule");
296
+ if (config.modules.includes("Staking") && !config.minimal) moduleNames.push("stakingModule");
297
+ return `[${moduleNames.filter((m) => !m.includes("//")).join(", ")}]`;
298
+ }
299
+ async function installDependencies(config, targetDir) {
300
+ const { spawn } = await import("node:child_process");
301
+ return new Promise((resolve$1, reject) => {
302
+ spawn(config.packageManager, ["install"], {
303
+ cwd: targetDir,
304
+ stdio: "inherit"
305
+ }).on("close", (code) => {
306
+ if (code !== 0) reject(/* @__PURE__ */ new Error(`Package installation failed with code ${code}`));
307
+ else resolve$1();
308
+ });
309
+ });
310
+ }
311
+ async function buildProject(config, targetDir) {
312
+ const { spawn } = await import("node:child_process");
313
+ return new Promise((resolve$1, reject) => {
314
+ spawn(config.packageManager, ["run", "build"], {
315
+ cwd: targetDir,
316
+ stdio: "inherit"
317
+ }).on("close", (code) => {
318
+ if (code !== 0) reject(/* @__PURE__ */ new Error(`Package installation failed with code ${code}`));
319
+ else resolve$1();
320
+ });
321
+ });
322
+ }
323
+
324
+ //#endregion
325
+ //#region src/index.ts
326
+ const __filename = fileURLToPath(import.meta.url);
327
+ dirname(__filename);
328
+ async function main() {
329
+ console.log();
330
+ console.log(colors.cyan("🚀 Welcome to create-eclesia-indexer!"));
331
+ console.log(colors.gray("Scaffolding a new Cosmos SDK chain indexer..."));
332
+ console.log();
333
+ try {
334
+ const projectName = process.argv[2];
335
+ if (projectName && existsSync(resolve(process.cwd(), projectName))) {
336
+ console.log(colors.red(`❌ Directory '${projectName}' already exists.`));
337
+ process.exit(1);
338
+ }
339
+ await createIndexer(projectName);
340
+ console.log();
341
+ console.log(colors.green("✅ Indexer created successfully!"));
342
+ console.log();
343
+ } catch (error) {
344
+ console.error(colors.red("❌ Error creating indexer:"), error);
345
+ process.exit(1);
346
+ }
347
+ }
348
+ main().catch((error) => {
349
+ console.error(error);
350
+ process.exit(1);
351
+ });
352
+
353
+ //#endregion
354
+ export { };
355
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["__filename","imports: string[]","instantiations: string[]","moduleNames: string[]"],"sources":["../src/create-indexer.ts","../src/index.ts"],"sourcesContent":["import enquirer from \"enquirer\";\nconst {\n prompt,\n} = enquirer;\nimport fse from \"fs-extra\";\nconst {\n ensureDirSync, copySync, writeFileSync, readFileSync,\n} = fse;\nimport path, {\n resolve,\n} from \"node:path\";\nimport {\n dirname,\n} from \"node:path\";\nimport {\n fileURLToPath,\n} from \"node:url\";\n\nimport colors from \"picocolors\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\ninterface ProjectConfig {\n projectName: string\n chainName: string\n description: string\n rpcEndpoint: string\n chainPrefix: string\n minimal: string\n startHeight: number\n queueSize: number\n logLevel: string\n processGenesis: string\n genesisPath: string | null\n modules: string[]\n packageManager: \"npm\" | \"yarn\" | \"pnpm\"\n}\n\nconst availableModules = [\n {\n name: \"Auth\",\n value: \"auth\",\n hint: \"Account and authentication data\",\n },\n {\n name: \"Bank\",\n value: \"bank\",\n hint: \"Token transfers and balances\",\n },\n {\n name: \"Staking\",\n value: \"staking\",\n hint: \"Validator and delegation data\",\n },\n];\n\nconst minimalAvailableModules = [\n {\n name: \"Auth\",\n value: \"auth\",\n hint: \"Account and authentication data\",\n },\n {\n name: \"Bank\",\n value: \"bank\",\n hint: \"Token transfers and balances\",\n },\n];\n\nexport async function createIndexer(initialProjectName?: string): Promise<void> {\n const config = await gatherProjectInfo(initialProjectName);\n const targetDir = resolve(process.cwd(), config.projectName);\n\n console.log(colors.blue(\"📁 Creating project directory...\"));\n ensureDirSync(targetDir);\n\n console.log(colors.blue(\"📋 Copying files...\"));\n await copyTemplateFiles(config, targetDir);\n\n console.log(colors.blue(\"🔧 Processing template variables...\"));\n await processTemplates(config, targetDir);\n\n console.log(colors.blue(\"📦 Installing dependencies...\"));\n await installDependencies(config, targetDir);\n\n console.log(colors.blue(\"📦 Building...\"));\n await buildProject(config, targetDir);\n\n console.log();\n console.log(colors.green(\"🎉 Your indexer is ready!\"));\n console.log();\n console.log(\"Next steps:\");\n console.log(colors.cyan(` cd ${config.projectName}`));\n console.log(colors.cyan(` ${config.packageManager} local-dev:start # To run a self-contained local development environment with Postgres`));\n console.log();\n console.log(\"If you want to run the indexer against an external Postgres database instead of the local development environment, set the database connection string in the .env file.\");\n console.log();\n console.log(colors.cyan(` ${config.packageManager} start # To run the indexer`));\n console.log();\n console.log(\"Happy indexing!\");\n console.log();\n}\n\nasync function gatherProjectInfo(initialProjectName?: string): Promise<ProjectConfig> {\n const questions1 = [\n {\n type: \"input\",\n name: \"projectName\",\n message: \"Project name:\",\n initial: initialProjectName || \"my-indexer\",\n skip: !!initialProjectName,\n },\n {\n type: \"input\",\n name: \"chainName\",\n message: \"Chain name:\",\n initial: \"cosmos-hub\",\n },\n {\n type: \"input\",\n name: \"chainPrefix\",\n message: \"Chain address prefix:\",\n initial: \"cosmos\",\n },\n {\n type: \"input\",\n name: \"description\",\n message: \"Description:\",\n initial: \"A custom Cosmos SDK chain indexer\",\n },\n {\n type: \"input\",\n name: \"rpcEndpoint\",\n message: \"RPC endpoint:\",\n initial: \"https://rpc.cosmos.network\",\n validate: (value: string) => {\n try {\n const url = new URL(value);\n if (url.protocol === \"http:\" || url.protocol === \"https:\" || url.protocol === \"ws:\" || url.protocol === \"wss:\") {\n return true;\n }\n return \"Please enter a valid HTTP/HTTPS/WS/WSS URL.\";\n }\n catch (err) {\n return \"Please enter a valid URL: \" + err;\n }\n },\n },\n {\n type: \"toggle\",\n name: \"minimal\",\n message: \"Minimal block indexing? (Only stores heights)\",\n enabled: \"Yes\",\n disabled: \"No\",\n initial: false,\n },\n {\n type: \"number\",\n name: \"queueSize\",\n message: \"Number of blocks to keep prefetched (queue size):\",\n initial: 200,\n },\n {\n type: \"number\",\n name: \"startHeight\",\n message: \"Height to start indexing from (>1 not compatible with standard modules):\",\n initial: 1,\n },\n ];\n const answers1 = await prompt(questions1) as ProjectConfig;\n if (answers1.startHeight == 1) {\n const genesisQuestion = await prompt([\n {\n type: \"toggle\",\n name: \"processGenesis\",\n message: \"Process genesis file?\",\n enabled: \"Yes\",\n disabled: \"No\",\n },\n ]) as ProjectConfig;\n answers1.processGenesis = genesisQuestion.processGenesis;\n }\n else {\n answers1.processGenesis = \"No\";\n }\n if (answers1.processGenesis === \"Yes\") {\n const genesisPathQuestion = await prompt([\n {\n type: \"input\",\n name: \"genesisPath\",\n message: \"Path to genesis file:\",\n initial: \"./genesis.json\",\n validate: (value: string) => {\n try {\n fse.accessSync(value, fse.constants.R_OK);\n return true;\n }\n catch (err) {\n return \"File not found or not readable. Please enter a valid path: \" + err;\n }\n },\n },\n ]) as ProjectConfig;\n answers1.genesisPath = genesisPathQuestion.genesisPath ? path.resolve(genesisPathQuestion.genesisPath) : null;\n }\n else {\n answers1.genesisPath = null;\n }\n const questions2 = [\n {\n type: \"multiselect\",\n name: \"modules\",\n message: \"Select modules to include:\",\n choices: answers1.startHeight == 1 && !answers1.minimal && answers1.processGenesis ? availableModules : minimalAvailableModules,\n initial: answers1.startHeight == 1 && !answers1.minimal && answers1.processGenesis ? [0, 1, 2] : [0, 1],\n },\n {\n type: \"select\",\n name: \"packageManager\",\n message: \"Package manager:\",\n choices: [\n {\n name: \"pnpm\",\n hint: \"recommended\",\n },\n {\n name: \"npm\",\n },\n {\n name: \"yarn\",\n },\n ],\n initial: 0,\n },\n {\n type: \"select\",\n name: \"logLevel\",\n message: \"Log level:\",\n choices: [\"error\", \"warn\", \"info\", \"verbose\", \"debug\", \"silly\"],\n initial: 4,\n },\n ];\n const answers2 = await prompt(questions2) as ProjectConfig;\n return {\n ...answers1,\n ...answers2,\n };\n}\n\nasync function copyTemplateFiles(config: ProjectConfig, targetDir: string): Promise<void> {\n const templatesDir = resolve(__dirname, \"..\", \"templates\", \"basic\");\n if (config.genesisPath) {\n console.log(colors.blue(\"📋 Copying genesis file...\"));\n copySync(config.genesisPath, targetDir + \"/genesis.json\");\n }\n\n console.log(colors.blue(\"📋 Copying template files...\"));\n copySync(templatesDir, targetDir, {\n filter: (src) => {\n if (src.includes(\".template\")) {\n return false;\n }\n if (config.packageManager !== \"pnpm\" && src.includes(\"pnpm-workspace\")) {\n return false;\n }\n if (src.includes(\"Dockerfile\")) {\n return false;\n }\n return true;\n },\n });\n copySync(resolve(templatesDir, \"Dockerfile.\" + config.packageManager), targetDir + \"/Dockerfile\");\n // Copy template files\n const templateFiles = [\"package.json.template\", \"src/index.ts.template\", \"tsconfig.json.template\", \"docker-compose.yml.template\", \"README.md.template\", \".env.template\"];\n\n templateFiles.forEach((templateFile) => {\n const srcPath = resolve(templatesDir, templateFile);\n const destPath = resolve(targetDir, templateFile.replace(\".template\", \"\"));\n\n try {\n copySync(srcPath, destPath);\n }\n catch (_error) {\n // Template file might not exist, that's okay\n }\n });\n}\n\nasync function processTemplates(config: ProjectConfig, targetDir: string): Promise<void> {\n let polling = false;\n const url = new URL(config.rpcEndpoint);\n if (url.protocol === \"http:\" || url.protocol === \"https:\") {\n polling = true;\n }\n const templateVars = {\n PROJECT_NAME: config.projectName,\n CHAIN_NAME: config.chainName,\n DESCRIPTION: config.description,\n RPC_ENDPOINT: config.rpcEndpoint,\n PG_CONNECTION_STRING: \"postgres://postgres:password@postgres:5432/indexer\",\n LOG_LEVEL: config.logLevel,\n QUEUE_SIZE: config.queueSize.toString(),\n USE_POLLING: polling ? \"true\" : \"false\",\n PROCESS_GENESIS: config.processGenesis + \"\",\n GENESIS_PATH: path.resolve(targetDir, \"genesis.json\"),\n MINIMAL: config.minimal ? \"true\" : \"false\",\n START_HEIGHT: config.startHeight.toString(),\n CHAIN_PREFIX: config.chainName.split(\"-\")[0] || \"cosmos\",\n MODULES_IMPORT: generateModulesImport(config),\n PACKAGE_MANAGER: config.packageManager,\n MODULES_INSTANTIATION: generateModulesInstantiation(config),\n MODULES_ARRAY: generateModulesArray(config),\n };\n\n const filesToProcess = [\"package.json\", \"src/index.ts\", \"tsconfig.json\", \"docker-compose.yml\", \"README.md\", \"pnpm-workspace.yaml\", \".env\"];\n\n filesToProcess.forEach((file) => {\n const filePath = resolve(targetDir, file);\n\n try {\n let content = readFileSync(filePath, \"utf-8\");\n\n Object.entries(templateVars).forEach(([key, value]) => {\n const regex = new RegExp(`{{${key}}}`, \"g\");\n content = content.replace(regex, value);\n });\n\n writeFileSync(filePath, content);\n }\n catch (_error) {\n // File might not exist, that's okay\n }\n });\n}\n\nfunction generateModulesImport(config: ProjectConfig): string {\n const imports: string[] = [];\n\n imports.push(\" Blocks\");\n if (config.modules.includes(\"Auth\")) {\n imports.push(\" AuthModule\");\n }\n if (config.modules.includes(\"Bank\") && config.startHeight === 1 && config.processGenesis) {\n imports.push(\" BankModule\");\n }\n if (config.modules.includes(\"Staking\") && !config.minimal) {\n imports.push(\" StakingModule\");\n }\n\n return `import {\\n${imports.join(\",\")}\\n} from \"@eclesia/core-modules-pg\";\\n`;\n}\n\nfunction generateModulesInstantiation(config: ProjectConfig): string {\n const instantiations: string[] = [];\n\n if (config.minimal) {\n instantiations.push(\"const blocksModule = new Blocks.MinimalBlocksModule(registry);\");\n }\n else {\n instantiations.push(\"const blocksModule = new Blocks.FullBlocksModule(registry);\");\n }\n if (config.modules.includes(\"Auth\")) {\n instantiations.push(\"const authModule = new AuthModule(registry);\");\n }\n if (config.modules.includes(\"Bank\") && config.startHeight === 1 && config.processGenesis) {\n instantiations.push(\"const bankModule = new BankModule(registry);\");\n }\n if (config.modules.includes(\"Staking\") && !config.minimal) {\n instantiations.push(\"const stakingModule = new StakingModule(registry);\");\n }\n\n return instantiations.join(\"\\n\");\n}\n\nfunction generateModulesArray(config: ProjectConfig): string {\n const moduleNames: string[] = [];\n\n moduleNames.push(\"blocksModule\");\n\n if (config.modules.includes(\"Auth\")) {\n moduleNames.push(\"authModule\");\n }\n if (config.modules.includes(\"Bank\") && config.startHeight === 1 && config.processGenesis) {\n moduleNames.push(\"bankModule\");\n }\n if (config.modules.includes(\"Staking\") && !config.minimal) {\n moduleNames.push(\"stakingModule\");\n }\n return `[${moduleNames.filter(m => !m.includes(\"//\")).join(\", \")}]`;\n}\n\nasync function installDependencies(config: ProjectConfig, targetDir: string): Promise<void> {\n const {\n spawn,\n } = await import(\"node:child_process\");\n\n return new Promise((resolve, reject) => {\n const child = spawn(config.packageManager, [\"install\"], {\n cwd: targetDir,\n stdio: \"inherit\",\n });\n\n child.on(\"close\", (code) => {\n if (code !== 0) {\n reject(new Error(`Package installation failed with code ${code}`));\n }\n else {\n resolve();\n }\n });\n });\n}\n\nasync function buildProject(config: ProjectConfig, targetDir: string): Promise<void> {\n const {\n spawn,\n } = await import(\"node:child_process\");\n\n return new Promise((resolve, reject) => {\n const child = spawn(config.packageManager, [\"run\", \"build\"], {\n cwd: targetDir,\n stdio: \"inherit\",\n });\n\n child.on(\"close\", (code) => {\n if (code !== 0) {\n reject(new Error(`Package installation failed with code ${code}`));\n }\n else {\n resolve();\n }\n });\n });\n}\n","import {\n existsSync,\n} from \"node:fs\";\nimport {\n dirname, resolve,\n} from \"node:path\";\nimport {\n fileURLToPath,\n} from \"node:url\";\n\nimport colors from \"picocolors\";\n\nimport {\n createIndexer,\n} from \"./create-indexer.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nasync function main() {\n console.log();\n console.log(colors.cyan(\"🚀 Welcome to create-eclesia-indexer!\"));\n console.log(colors.gray(\"Scaffolding a new Cosmos SDK chain indexer...\"));\n console.log();\n\n try {\n const projectName = process.argv[2];\n\n if (projectName && existsSync(resolve(process.cwd(), projectName))) {\n console.log(colors.red(`❌ Directory '${projectName}' already exists.`));\n process.exit(1);\n }\n\n await createIndexer(projectName);\n\n console.log();\n console.log(colors.green(\"✅ Indexer created successfully!\"));\n console.log();\n }\n catch (error) {\n console.error(colors.red(\"❌ Error creating indexer:\"), error);\n process.exit(1);\n }\n}\n\nmain().catch((error) => {\n console.error(error);\n process.exit(1);\n});\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "create-eclesia-indexer",
3
+ "version": "1.0.2",
4
+ "description": "CLI tool to scaffold Cosmos SDK chain indexers using eclesia-indexer-core",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-eclesia-indexer": "./bin/create-eclesia-indexer.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc --noEmit && tsdown",
11
+ "lint": "eslint src/**/*.ts",
12
+ "lint:fix": "eslint src/**/*.ts --fix",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "files": [
16
+ "dist/",
17
+ "templates/",
18
+ "bin/"
19
+ ],
20
+ "keywords": [
21
+ "cosmos",
22
+ "indexer",
23
+ "blockchain",
24
+ "cli",
25
+ "scaffolding",
26
+ "template"
27
+ ],
28
+ "author": "",
29
+ "license": "Apache-2.0",
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "dependencies": {
34
+ "enquirer": "^2.4.1",
35
+ "fs-extra": "^11.2.0",
36
+ "picocolors": "^1.1.1"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "devDependencies": {
42
+ "@types/fs-extra": "^11.0.4",
43
+ "@types/node": "^22.14.0",
44
+ "@eslint/js": "^9.35.0",
45
+ "@stylistic/eslint-plugin": "^5.3.1",
46
+ "eslint": "^9.35.0",
47
+ "eslint-plugin-simple-import-sort": "^12.1.1",
48
+ "tsdown": "^0.15.2",
49
+ "typescript": "^5.9.2",
50
+ "typescript-eslint": "^8.44.0"
51
+ }
52
+ }
@@ -0,0 +1,17 @@
1
+ # {{PROJECT_NAME}} Configuration
2
+
3
+ # Chain RPC endpoint
4
+ RPC_ENDPOINT={{RPC_ENDPOINT}}
5
+
6
+ # Database connection
7
+ PG_CONNECTION_STRING={{PG_CONNECTION_STRING}}
8
+
9
+ # Indexer settings
10
+ LOG_LEVEL={{LOG_LEVEL}}
11
+ QUEUE_SIZE={{QUEUE_SIZE}}
12
+ PROCESS_GENESIS={{PROCESS_GENESIS}}
13
+ CHAIN_PREFIX={{CHAIN_PREFIX}}
14
+ USE_POLLING={{USE_POLLING}}
15
+
16
+ # Optional: Start from specific height
17
+ START_HEIGHT={{START_HEIGHT}}
@@ -0,0 +1,24 @@
1
+ # syntax=docker.io/docker/dockerfile:1.7-labs
2
+
3
+ FROM node:20-alpine3.21 AS builder
4
+
5
+ WORKDIR /usr/src/app
6
+
7
+ COPY package*.json ./
8
+
9
+ RUN npm ci
10
+
11
+ COPY --exclude=genesis.json . .
12
+
13
+ # Final image
14
+ FROM node:20-alpine3.21
15
+
16
+ WORKDIR /usr/src/app
17
+
18
+ COPY package*.json ./
19
+
20
+ RUN npm ci --only=production && npm cache clean --force
21
+
22
+ COPY --from=builder /usr/src/app .
23
+
24
+ ENTRYPOINT ["node", "dist/index.js" ]
@@ -0,0 +1,28 @@
1
+ # syntax=docker.io/docker/dockerfile:1.7-labs
2
+
3
+ FROM node:20-alpine3.21 AS builder
4
+
5
+ RUN corepack enable && corepack prepare pnpm@latest --activate
6
+
7
+ WORKDIR /usr/src/app
8
+
9
+ COPY pnpm-lock.yaml package.json pnpm-workspace.yaml ./
10
+
11
+ RUN pnpm install --frozen-lockfile
12
+
13
+ COPY --exclude=genesis.json . .
14
+
15
+ # Final image
16
+ FROM node:20-alpine3.21
17
+
18
+ RUN corepack enable && corepack prepare pnpm@latest --activate
19
+
20
+ WORKDIR /usr/src/app
21
+
22
+ COPY pnpm-lock.yaml package.json pnpm-workspace.yaml ./
23
+
24
+ RUN pnpm install --frozen-lockfile --prod
25
+
26
+ COPY --from=builder /usr/src/app .
27
+
28
+ ENTRYPOINT ["node", "dist/index.js" ]
@@ -0,0 +1,25 @@
1
+ # syntax=docker.io/docker/dockerfile:1.7-labs
2
+
3
+ FROM node:20-alpine3.21 AS builder
4
+
5
+ WORKDIR /usr/src/app
6
+
7
+ COPY package.json yarn.lock ./
8
+
9
+ RUN yarn install --frozen-lockfile
10
+
11
+ COPY --exclude=genesis.json . .
12
+
13
+ # Final image
14
+ FROM node:20-alpine3.21
15
+
16
+ WORKDIR /usr/src/app
17
+
18
+ COPY package.json yarn.lock ./
19
+
20
+ RUN yarn install --frozen-lockfile --production && \
21
+ yarn cache clean
22
+
23
+ COPY --from=builder /usr/src/app .
24
+
25
+ ENTRYPOINT ["node", "dist/index.js" ]
@@ -0,0 +1,109 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ {{DESCRIPTION}}
4
+
5
+ A custom indexer for **{{CHAIN_NAME}}** built with eclesia-indexer-core.
6
+
7
+ ## Features
8
+
9
+ - ✅ Block indexing
10
+ - ✅ Transaction processing
11
+ - ✅ Module-based architecture
12
+ - ✅ PostgreSQL storage
13
+ - ✅ Real-time synchronization
14
+ - ✅ Docker support
15
+
16
+ ## Prerequisites
17
+
18
+ - Node.js 18+
19
+ - PostgreSQL 12+
20
+ - Access to {{CHAIN_NAME}} RPC endpoint
21
+
22
+ ## Quick Start
23
+
24
+ 1. **Install dependencies**
25
+ ```bash
26
+ npm install
27
+ ```
28
+
29
+ 2. **Set up environment**
30
+ ```bash
31
+ cp .env.example .env
32
+ # Edit .env with your configuration
33
+ ```
34
+
35
+ 3. **Start PostgreSQL** (if using Docker)
36
+ ```bash
37
+ docker-compose up postgres -d
38
+ ```
39
+
40
+ 4. **Run the indexer**
41
+ ```bash
42
+ npm run build
43
+ npm start
44
+ ```
45
+
46
+ ## Configuration
47
+
48
+ Configure your indexer through environment variables or directly in `src/index.ts`:
49
+
50
+ - `RPC_ENDPOINT`: {{CHAIN_NAME}} RPC endpoint (default: {{RPC_ENDPOINT}})
51
+ - `PG_CONNECTION_STRING`: PostgreSQL connection string
52
+ - `LOG_LEVEL`: Logging level (info, debug, warn, error)
53
+ - `QUEUE_SIZE`: Batch size for processing blocks
54
+ - `PROCESS_GENESIS`: Whether to process the genesis block
55
+
56
+ ## Docker Deployment
57
+
58
+ ```bash
59
+ docker-compose up -d
60
+ ```
61
+
62
+ This will start both PostgreSQL and the indexer in containers.
63
+
64
+ ## Development
65
+
66
+ ```bash
67
+ npm run dev # Start in development mode with hot reload
68
+ npm run lint # Run ESLint
69
+ npm run build # Build for production
70
+ ```
71
+
72
+ ## Database Schema
73
+
74
+ The indexer automatically creates the necessary database tables on startup. You can find migration files in the `src/migrations/` directory (if applicable).
75
+
76
+ ## Adding Custom Modules
77
+
78
+ To add custom indexing logic:
79
+
80
+ 1. Create a new module in `src/modules/`
81
+ 2. Implement the required interfaces from eclesia-indexer-core
82
+ 3. Register your module in `src/index.ts`
83
+
84
+ ## Troubleshooting
85
+
86
+ ### Common Issues
87
+
88
+ **Connection refused to database**
89
+ - Ensure PostgreSQL is running
90
+ - Check your connection string
91
+ - Verify network connectivity
92
+
93
+ **RPC endpoint errors**
94
+ - Verify the RPC endpoint is accessible
95
+ - Check for rate limiting
96
+ - Ensure the endpoint supports the required methods
97
+
98
+ **High memory usage**
99
+ - Reduce `QUEUE_SIZE` if processing large blocks
100
+ - Monitor PostgreSQL memory usage
101
+ - Consider using connection pooling
102
+
103
+ ## License
104
+
105
+ ISC
106
+
107
+ ## Support
108
+
109
+ For issues with eclesia-indexer-core, please refer to the main repository documentation.