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.cjs
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//#region rolldown:runtime
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
let node_fs = require("node:fs");
|
|
26
|
+
node_fs = __toESM(node_fs);
|
|
27
|
+
let node_path = require("node:path");
|
|
28
|
+
node_path = __toESM(node_path);
|
|
29
|
+
let node_url = require("node:url");
|
|
30
|
+
node_url = __toESM(node_url);
|
|
31
|
+
let picocolors = require("picocolors");
|
|
32
|
+
picocolors = __toESM(picocolors);
|
|
33
|
+
let enquirer = require("enquirer");
|
|
34
|
+
enquirer = __toESM(enquirer);
|
|
35
|
+
let fs_extra = require("fs-extra");
|
|
36
|
+
fs_extra = __toESM(fs_extra);
|
|
37
|
+
|
|
38
|
+
//#region src/create-indexer.ts
|
|
39
|
+
const { prompt } = enquirer.default;
|
|
40
|
+
const { ensureDirSync, copySync, writeFileSync, readFileSync } = fs_extra.default;
|
|
41
|
+
const __filename$2 = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
|
|
42
|
+
const __dirname$1 = (0, node_path.dirname)(__filename$2);
|
|
43
|
+
const availableModules = [
|
|
44
|
+
{
|
|
45
|
+
name: "Auth",
|
|
46
|
+
value: "auth",
|
|
47
|
+
hint: "Account and authentication data"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "Bank",
|
|
51
|
+
value: "bank",
|
|
52
|
+
hint: "Token transfers and balances"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "Staking",
|
|
56
|
+
value: "staking",
|
|
57
|
+
hint: "Validator and delegation data"
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
const minimalAvailableModules = [{
|
|
61
|
+
name: "Auth",
|
|
62
|
+
value: "auth",
|
|
63
|
+
hint: "Account and authentication data"
|
|
64
|
+
}, {
|
|
65
|
+
name: "Bank",
|
|
66
|
+
value: "bank",
|
|
67
|
+
hint: "Token transfers and balances"
|
|
68
|
+
}];
|
|
69
|
+
async function createIndexer(initialProjectName) {
|
|
70
|
+
const config = await gatherProjectInfo(initialProjectName);
|
|
71
|
+
const targetDir = (0, node_path.resolve)(process.cwd(), config.projectName);
|
|
72
|
+
console.log(picocolors.default.blue("📁 Creating project directory..."));
|
|
73
|
+
ensureDirSync(targetDir);
|
|
74
|
+
console.log(picocolors.default.blue("📋 Copying files..."));
|
|
75
|
+
await copyTemplateFiles(config, targetDir);
|
|
76
|
+
console.log(picocolors.default.blue("🔧 Processing template variables..."));
|
|
77
|
+
await processTemplates(config, targetDir);
|
|
78
|
+
console.log(picocolors.default.blue("📦 Installing dependencies..."));
|
|
79
|
+
await installDependencies(config, targetDir);
|
|
80
|
+
console.log(picocolors.default.blue("📦 Building..."));
|
|
81
|
+
await buildProject(config, targetDir);
|
|
82
|
+
console.log();
|
|
83
|
+
console.log(picocolors.default.green("🎉 Your indexer is ready!"));
|
|
84
|
+
console.log();
|
|
85
|
+
console.log("Next steps:");
|
|
86
|
+
console.log(picocolors.default.cyan(` cd ${config.projectName}`));
|
|
87
|
+
console.log(picocolors.default.cyan(` ${config.packageManager} local-dev:start # To run a self-contained local development environment with Postgres`));
|
|
88
|
+
console.log();
|
|
89
|
+
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.");
|
|
90
|
+
console.log();
|
|
91
|
+
console.log(picocolors.default.cyan(` ${config.packageManager} start # To run the indexer`));
|
|
92
|
+
console.log();
|
|
93
|
+
console.log("Happy indexing!");
|
|
94
|
+
console.log();
|
|
95
|
+
}
|
|
96
|
+
async function gatherProjectInfo(initialProjectName) {
|
|
97
|
+
const answers1 = await prompt([
|
|
98
|
+
{
|
|
99
|
+
type: "input",
|
|
100
|
+
name: "projectName",
|
|
101
|
+
message: "Project name:",
|
|
102
|
+
initial: initialProjectName || "my-indexer",
|
|
103
|
+
skip: !!initialProjectName
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
type: "input",
|
|
107
|
+
name: "chainName",
|
|
108
|
+
message: "Chain name:",
|
|
109
|
+
initial: "cosmos-hub"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: "input",
|
|
113
|
+
name: "chainPrefix",
|
|
114
|
+
message: "Chain address prefix:",
|
|
115
|
+
initial: "cosmos"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: "input",
|
|
119
|
+
name: "description",
|
|
120
|
+
message: "Description:",
|
|
121
|
+
initial: "A custom Cosmos SDK chain indexer"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: "input",
|
|
125
|
+
name: "rpcEndpoint",
|
|
126
|
+
message: "RPC endpoint:",
|
|
127
|
+
initial: "https://rpc.cosmos.network",
|
|
128
|
+
validate: (value) => {
|
|
129
|
+
try {
|
|
130
|
+
const url = new URL(value);
|
|
131
|
+
if (url.protocol === "http:" || url.protocol === "https:" || url.protocol === "ws:" || url.protocol === "wss:") return true;
|
|
132
|
+
return "Please enter a valid HTTP/HTTPS/WS/WSS URL.";
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return "Please enter a valid URL: " + err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: "toggle",
|
|
140
|
+
name: "minimal",
|
|
141
|
+
message: "Minimal block indexing? (Only stores heights)",
|
|
142
|
+
enabled: "Yes",
|
|
143
|
+
disabled: "No",
|
|
144
|
+
initial: false
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: "number",
|
|
148
|
+
name: "queueSize",
|
|
149
|
+
message: "Number of blocks to keep prefetched (queue size):",
|
|
150
|
+
initial: 200
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: "number",
|
|
154
|
+
name: "startHeight",
|
|
155
|
+
message: "Height to start indexing from (>1 not compatible with standard modules):",
|
|
156
|
+
initial: 1
|
|
157
|
+
}
|
|
158
|
+
]);
|
|
159
|
+
if (answers1.startHeight == 1) answers1.processGenesis = (await prompt([{
|
|
160
|
+
type: "toggle",
|
|
161
|
+
name: "processGenesis",
|
|
162
|
+
message: "Process genesis file?",
|
|
163
|
+
enabled: "Yes",
|
|
164
|
+
disabled: "No"
|
|
165
|
+
}])).processGenesis;
|
|
166
|
+
else answers1.processGenesis = "No";
|
|
167
|
+
if (answers1.processGenesis === "Yes") {
|
|
168
|
+
const genesisPathQuestion = await prompt([{
|
|
169
|
+
type: "input",
|
|
170
|
+
name: "genesisPath",
|
|
171
|
+
message: "Path to genesis file:",
|
|
172
|
+
initial: "./genesis.json",
|
|
173
|
+
validate: (value) => {
|
|
174
|
+
try {
|
|
175
|
+
fs_extra.default.accessSync(value, fs_extra.default.constants.R_OK);
|
|
176
|
+
return true;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
return "File not found or not readable. Please enter a valid path: " + err;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}]);
|
|
182
|
+
answers1.genesisPath = genesisPathQuestion.genesisPath ? node_path.default.resolve(genesisPathQuestion.genesisPath) : null;
|
|
183
|
+
} else answers1.genesisPath = null;
|
|
184
|
+
const questions2 = [
|
|
185
|
+
{
|
|
186
|
+
type: "multiselect",
|
|
187
|
+
name: "modules",
|
|
188
|
+
message: "Select modules to include:",
|
|
189
|
+
choices: answers1.startHeight == 1 && !answers1.minimal && answers1.processGenesis ? availableModules : minimalAvailableModules,
|
|
190
|
+
initial: answers1.startHeight == 1 && !answers1.minimal && answers1.processGenesis ? [
|
|
191
|
+
0,
|
|
192
|
+
1,
|
|
193
|
+
2
|
|
194
|
+
] : [0, 1]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
type: "select",
|
|
198
|
+
name: "packageManager",
|
|
199
|
+
message: "Package manager:",
|
|
200
|
+
choices: [
|
|
201
|
+
{
|
|
202
|
+
name: "pnpm",
|
|
203
|
+
hint: "recommended"
|
|
204
|
+
},
|
|
205
|
+
{ name: "npm" },
|
|
206
|
+
{ name: "yarn" }
|
|
207
|
+
],
|
|
208
|
+
initial: 0
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
type: "select",
|
|
212
|
+
name: "logLevel",
|
|
213
|
+
message: "Log level:",
|
|
214
|
+
choices: [
|
|
215
|
+
"error",
|
|
216
|
+
"warn",
|
|
217
|
+
"info",
|
|
218
|
+
"verbose",
|
|
219
|
+
"debug",
|
|
220
|
+
"silly"
|
|
221
|
+
],
|
|
222
|
+
initial: 4
|
|
223
|
+
}
|
|
224
|
+
];
|
|
225
|
+
const answers2 = await prompt(questions2);
|
|
226
|
+
return {
|
|
227
|
+
...answers1,
|
|
228
|
+
...answers2
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async function copyTemplateFiles(config, targetDir) {
|
|
232
|
+
const templatesDir = (0, node_path.resolve)(__dirname$1, "..", "templates", "basic");
|
|
233
|
+
if (config.genesisPath) {
|
|
234
|
+
console.log(picocolors.default.blue("📋 Copying genesis file..."));
|
|
235
|
+
copySync(config.genesisPath, targetDir + "/genesis.json");
|
|
236
|
+
}
|
|
237
|
+
console.log(picocolors.default.blue("📋 Copying template files..."));
|
|
238
|
+
copySync(templatesDir, targetDir, { filter: (src) => {
|
|
239
|
+
if (src.includes(".template")) return false;
|
|
240
|
+
if (config.packageManager !== "pnpm" && src.includes("pnpm-workspace")) return false;
|
|
241
|
+
if (src.includes("Dockerfile")) return false;
|
|
242
|
+
return true;
|
|
243
|
+
} });
|
|
244
|
+
copySync((0, node_path.resolve)(templatesDir, "Dockerfile." + config.packageManager), targetDir + "/Dockerfile");
|
|
245
|
+
[
|
|
246
|
+
"package.json.template",
|
|
247
|
+
"src/index.ts.template",
|
|
248
|
+
"tsconfig.json.template",
|
|
249
|
+
"docker-compose.yml.template",
|
|
250
|
+
"README.md.template",
|
|
251
|
+
".env.template"
|
|
252
|
+
].forEach((templateFile) => {
|
|
253
|
+
const srcPath = (0, node_path.resolve)(templatesDir, templateFile);
|
|
254
|
+
const destPath = (0, node_path.resolve)(targetDir, templateFile.replace(".template", ""));
|
|
255
|
+
try {
|
|
256
|
+
copySync(srcPath, destPath);
|
|
257
|
+
} catch (_error) {}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
async function processTemplates(config, targetDir) {
|
|
261
|
+
let polling = false;
|
|
262
|
+
const url = new URL(config.rpcEndpoint);
|
|
263
|
+
if (url.protocol === "http:" || url.protocol === "https:") polling = true;
|
|
264
|
+
const templateVars = {
|
|
265
|
+
PROJECT_NAME: config.projectName,
|
|
266
|
+
CHAIN_NAME: config.chainName,
|
|
267
|
+
DESCRIPTION: config.description,
|
|
268
|
+
RPC_ENDPOINT: config.rpcEndpoint,
|
|
269
|
+
PG_CONNECTION_STRING: "postgres://postgres:password@postgres:5432/indexer",
|
|
270
|
+
LOG_LEVEL: config.logLevel,
|
|
271
|
+
QUEUE_SIZE: config.queueSize.toString(),
|
|
272
|
+
USE_POLLING: polling ? "true" : "false",
|
|
273
|
+
PROCESS_GENESIS: config.processGenesis + "",
|
|
274
|
+
GENESIS_PATH: node_path.default.resolve(targetDir, "genesis.json"),
|
|
275
|
+
MINIMAL: config.minimal ? "true" : "false",
|
|
276
|
+
START_HEIGHT: config.startHeight.toString(),
|
|
277
|
+
CHAIN_PREFIX: config.chainName.split("-")[0] || "cosmos",
|
|
278
|
+
MODULES_IMPORT: generateModulesImport(config),
|
|
279
|
+
PACKAGE_MANAGER: config.packageManager,
|
|
280
|
+
MODULES_INSTANTIATION: generateModulesInstantiation(config),
|
|
281
|
+
MODULES_ARRAY: generateModulesArray(config)
|
|
282
|
+
};
|
|
283
|
+
[
|
|
284
|
+
"package.json",
|
|
285
|
+
"src/index.ts",
|
|
286
|
+
"tsconfig.json",
|
|
287
|
+
"docker-compose.yml",
|
|
288
|
+
"README.md",
|
|
289
|
+
"pnpm-workspace.yaml",
|
|
290
|
+
".env"
|
|
291
|
+
].forEach((file) => {
|
|
292
|
+
const filePath = (0, node_path.resolve)(targetDir, file);
|
|
293
|
+
try {
|
|
294
|
+
let content = readFileSync(filePath, "utf-8");
|
|
295
|
+
Object.entries(templateVars).forEach(([key, value]) => {
|
|
296
|
+
const regex = new RegExp(`{{${key}}}`, "g");
|
|
297
|
+
content = content.replace(regex, value);
|
|
298
|
+
});
|
|
299
|
+
writeFileSync(filePath, content);
|
|
300
|
+
} catch (_error) {}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
function generateModulesImport(config) {
|
|
304
|
+
const imports = [];
|
|
305
|
+
imports.push(" Blocks");
|
|
306
|
+
if (config.modules.includes("Auth")) imports.push(" AuthModule");
|
|
307
|
+
if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) imports.push(" BankModule");
|
|
308
|
+
if (config.modules.includes("Staking") && !config.minimal) imports.push(" StakingModule");
|
|
309
|
+
return `import {\n${imports.join(",")}\n} from "@eclesia/core-modules-pg";\n`;
|
|
310
|
+
}
|
|
311
|
+
function generateModulesInstantiation(config) {
|
|
312
|
+
const instantiations = [];
|
|
313
|
+
if (config.minimal) instantiations.push("const blocksModule = new Blocks.MinimalBlocksModule(registry);");
|
|
314
|
+
else instantiations.push("const blocksModule = new Blocks.FullBlocksModule(registry);");
|
|
315
|
+
if (config.modules.includes("Auth")) instantiations.push("const authModule = new AuthModule(registry);");
|
|
316
|
+
if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) instantiations.push("const bankModule = new BankModule(registry);");
|
|
317
|
+
if (config.modules.includes("Staking") && !config.minimal) instantiations.push("const stakingModule = new StakingModule(registry);");
|
|
318
|
+
return instantiations.join("\n");
|
|
319
|
+
}
|
|
320
|
+
function generateModulesArray(config) {
|
|
321
|
+
const moduleNames = [];
|
|
322
|
+
moduleNames.push("blocksModule");
|
|
323
|
+
if (config.modules.includes("Auth")) moduleNames.push("authModule");
|
|
324
|
+
if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) moduleNames.push("bankModule");
|
|
325
|
+
if (config.modules.includes("Staking") && !config.minimal) moduleNames.push("stakingModule");
|
|
326
|
+
return `[${moduleNames.filter((m) => !m.includes("//")).join(", ")}]`;
|
|
327
|
+
}
|
|
328
|
+
async function installDependencies(config, targetDir) {
|
|
329
|
+
const { spawn } = await import("node:child_process");
|
|
330
|
+
return new Promise((resolve$2, reject) => {
|
|
331
|
+
spawn(config.packageManager, ["install"], {
|
|
332
|
+
cwd: targetDir,
|
|
333
|
+
stdio: "inherit"
|
|
334
|
+
}).on("close", (code) => {
|
|
335
|
+
if (code !== 0) reject(/* @__PURE__ */ new Error(`Package installation failed with code ${code}`));
|
|
336
|
+
else resolve$2();
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
async function buildProject(config, targetDir) {
|
|
341
|
+
const { spawn } = await import("node:child_process");
|
|
342
|
+
return new Promise((resolve$2, reject) => {
|
|
343
|
+
spawn(config.packageManager, ["run", "build"], {
|
|
344
|
+
cwd: targetDir,
|
|
345
|
+
stdio: "inherit"
|
|
346
|
+
}).on("close", (code) => {
|
|
347
|
+
if (code !== 0) reject(/* @__PURE__ */ new Error(`Package installation failed with code ${code}`));
|
|
348
|
+
else resolve$2();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
//#endregion
|
|
354
|
+
//#region src/index.ts
|
|
355
|
+
const __filename$1 = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
|
|
356
|
+
(0, node_path.dirname)(__filename$1);
|
|
357
|
+
async function main() {
|
|
358
|
+
console.log();
|
|
359
|
+
console.log(picocolors.default.cyan("🚀 Welcome to create-eclesia-indexer!"));
|
|
360
|
+
console.log(picocolors.default.gray("Scaffolding a new Cosmos SDK chain indexer..."));
|
|
361
|
+
console.log();
|
|
362
|
+
try {
|
|
363
|
+
const projectName = process.argv[2];
|
|
364
|
+
if (projectName && (0, node_fs.existsSync)((0, node_path.resolve)(process.cwd(), projectName))) {
|
|
365
|
+
console.log(picocolors.default.red(`❌ Directory '${projectName}' already exists.`));
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
await createIndexer(projectName);
|
|
369
|
+
console.log();
|
|
370
|
+
console.log(picocolors.default.green("✅ Indexer created successfully!"));
|
|
371
|
+
console.log();
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error(picocolors.default.red("❌ Error creating indexer:"), error);
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
main().catch((error) => {
|
|
378
|
+
console.error(error);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
//#endregion
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|