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/bin/create-eclesia-indexer.js +4 -0
- package/dist/index.cjs +382 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +355 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
- package/templates/basic/.env.template +17 -0
- package/templates/basic/Dockerfile.npm +24 -0
- package/templates/basic/Dockerfile.pnpm +28 -0
- package/templates/basic/Dockerfile.yarn +25 -0
- package/templates/basic/README.md.template +109 -0
- package/templates/basic/docker-compose.yml.template +56 -0
- package/templates/basic/eslint.config.mjs +84 -0
- package/templates/basic/package.json.template +70 -0
- package/templates/basic/pnpm-workspace.yaml +2 -0
- package/templates/basic/src/index.ts.template +49 -0
- package/templates/basic/tsconfig.json.template +26 -0
- package/templates/basic/tsdown.config.ts +31 -0
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.
|