apibara 2.0.0-beta.9 → 2.1.0-beta.3
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/chunks/add.mjs +44 -0
- package/dist/chunks/build.mjs +3 -3
- package/dist/chunks/dev.mjs +22 -18
- package/dist/chunks/init.mjs +37 -0
- package/dist/chunks/prepare.mjs +0 -2
- package/dist/chunks/start.mjs +56 -0
- package/dist/cli/index.mjs +5 -1
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/core/index.mjs +61 -97
- package/dist/create/index.d.mts +17 -0
- package/dist/create/index.d.ts +17 -0
- package/dist/create/index.mjs +981 -0
- package/dist/rollup/index.d.mts +2 -1
- package/dist/rollup/index.d.ts +2 -1
- package/dist/rollup/index.mjs +130 -167
- package/dist/runtime/dev.d.ts +3 -0
- package/dist/runtime/dev.mjs +55 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.mjs +2 -0
- package/dist/runtime/internal/app.d.ts +2 -0
- package/dist/runtime/internal/app.mjs +56 -0
- package/dist/runtime/internal/logger.d.ts +14 -0
- package/dist/runtime/internal/logger.mjs +45 -0
- package/dist/runtime/start.d.ts +3 -0
- package/dist/runtime/start.mjs +41 -0
- package/dist/types/index.d.mts +22 -19
- package/dist/types/index.d.ts +22 -19
- package/package.json +35 -13
- package/runtime-meta.d.ts +2 -0
- package/runtime-meta.mjs +7 -0
- package/src/cli/commands/add.ts +44 -0
- package/src/cli/commands/build.ts +5 -3
- package/src/cli/commands/dev.ts +28 -18
- package/src/cli/commands/init.ts +36 -0
- package/src/cli/commands/prepare.ts +0 -2
- package/src/cli/commands/start.ts +61 -0
- package/src/cli/index.ts +3 -0
- package/src/config/index.ts +5 -4
- package/src/core/apibara.ts +4 -2
- package/src/core/build/build.ts +2 -0
- package/src/core/build/dev.ts +1 -0
- package/src/core/build/error.ts +0 -1
- package/src/core/build/prepare.ts +5 -2
- package/src/core/build/prod.ts +10 -6
- package/src/core/build/types.ts +4 -95
- package/src/core/config/defaults.ts +1 -4
- package/src/core/config/loader.ts +1 -0
- package/src/core/config/resolvers/runtime-config.resolver.ts +1 -1
- package/src/core/config/update.ts +2 -3
- package/src/core/path.ts +11 -0
- package/src/core/scan.ts +40 -0
- package/src/create/add.ts +238 -0
- package/src/create/colors.ts +15 -0
- package/src/create/constants.ts +98 -0
- package/src/create/index.ts +2 -0
- package/src/create/init.ts +175 -0
- package/src/create/templates.ts +468 -0
- package/src/create/types.ts +34 -0
- package/src/create/utils.ts +422 -0
- package/src/rollup/config.ts +67 -189
- package/src/rollup/index.ts +1 -0
- package/src/rollup/plugins/config.ts +12 -0
- package/src/rollup/plugins/esm-shim.ts +69 -0
- package/src/rollup/plugins/indexers.ts +17 -0
- package/src/runtime/dev.ts +64 -0
- package/src/runtime/index.ts +2 -0
- package/src/runtime/internal/app.ts +78 -0
- package/src/runtime/internal/logger.ts +70 -0
- package/src/runtime/start.ts +48 -0
- package/src/types/apibara.ts +8 -0
- package/src/types/config.ts +28 -27
- package/src/types/hooks.ts +1 -0
- package/src/types/virtual/config.d.ts +3 -0
- package/src/types/virtual/indexers.d.ts +10 -0
- package/dist/internal/citty/index.d.mts +0 -1
- package/dist/internal/citty/index.d.ts +0 -1
- package/dist/internal/citty/index.mjs +0 -1
- package/dist/internal/consola/index.d.mts +0 -2
- package/dist/internal/consola/index.d.ts +0 -2
- package/dist/internal/consola/index.mjs +0 -1
- package/src/internal/citty/index.ts +0 -1
- package/src/internal/consola/index.ts +0 -1
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
import path, { basename } from 'node:path';
|
|
2
|
+
import consola$1, { consola } from 'consola';
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import colors from 'picocolors';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { Project, SyntaxKind } from 'ts-morph';
|
|
7
|
+
import * as prettier from 'prettier';
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
blue,
|
|
11
|
+
blueBright,
|
|
12
|
+
cyan,
|
|
13
|
+
gray,
|
|
14
|
+
green,
|
|
15
|
+
greenBright,
|
|
16
|
+
magenta,
|
|
17
|
+
red,
|
|
18
|
+
redBright,
|
|
19
|
+
reset,
|
|
20
|
+
yellow
|
|
21
|
+
} = colors;
|
|
22
|
+
|
|
23
|
+
const chains = [
|
|
24
|
+
{
|
|
25
|
+
name: "starknet",
|
|
26
|
+
display: "Starknet",
|
|
27
|
+
color: blue,
|
|
28
|
+
networks: [
|
|
29
|
+
{ name: "mainnet", display: "Mainnet", color: blue },
|
|
30
|
+
{ name: "sepolia", display: "Sepolia", color: yellow }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "ethereum",
|
|
35
|
+
display: "Ethereum",
|
|
36
|
+
color: green,
|
|
37
|
+
networks: [
|
|
38
|
+
{ name: "mainnet", display: "Mainnet", color: blue },
|
|
39
|
+
{ name: "sepolia", display: "Sepolia", color: yellow }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "beaconchain",
|
|
44
|
+
display: "Beacon Chain",
|
|
45
|
+
color: yellow,
|
|
46
|
+
networks: [{ name: "mainnet", display: "Mainnet", color: yellow }]
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
const networks = [
|
|
50
|
+
{ name: "mainnet", display: "Mainnet", color: blue },
|
|
51
|
+
{ name: "sepolia", display: "Sepolia", color: green },
|
|
52
|
+
{ name: "other", display: "Other", color: red }
|
|
53
|
+
];
|
|
54
|
+
const storages = [
|
|
55
|
+
{ name: "postgres", display: "Postgres", color: green },
|
|
56
|
+
{ name: "none", display: "None", color: red }
|
|
57
|
+
];
|
|
58
|
+
const packageVersions = {
|
|
59
|
+
// Required Dependencies
|
|
60
|
+
apibara: "^2.1.0-beta.2",
|
|
61
|
+
"@apibara/indexer": "^2.1.0-beta.2",
|
|
62
|
+
"@apibara/protocol": "^2.1.0-beta.2",
|
|
63
|
+
// Chain Dependencies
|
|
64
|
+
"@apibara/evm": "^2.1.0-beta.2",
|
|
65
|
+
"@apibara/beaconchain": "^2.1.0-beta.2",
|
|
66
|
+
"@apibara/starknet": "^2.1.0-beta.2",
|
|
67
|
+
// Storage Dependencies
|
|
68
|
+
"@apibara/plugin-drizzle": "^2.1.0-beta.2",
|
|
69
|
+
"@apibara/plugin-mongo": "^2.1.0-beta.2",
|
|
70
|
+
"@apibara/plugin-sqlite": "^2.1.0-beta.2",
|
|
71
|
+
// Postgres Dependencies
|
|
72
|
+
"@electric-sql/pglite": "^0.2.17",
|
|
73
|
+
"drizzle-orm": "^0.37.0",
|
|
74
|
+
pg: "^8.13.1",
|
|
75
|
+
"@types/pg": "^8.11.10",
|
|
76
|
+
"drizzle-kit": "^0.29.0",
|
|
77
|
+
// Typescript Dependencies
|
|
78
|
+
typescript: "^5.6.2",
|
|
79
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
80
|
+
"@types/node": "^20.5.2"
|
|
81
|
+
};
|
|
82
|
+
const dnaUrls = {
|
|
83
|
+
ethereum: "https://ethereum.preview.apibara.org",
|
|
84
|
+
ethereumSepolia: "https://ethereum-sepolia.preview.apibara.org",
|
|
85
|
+
beaconchain: "https://beaconchain.preview.apibara.org",
|
|
86
|
+
starknet: "https://starknet.preview.apibara.org",
|
|
87
|
+
starknetSepolia: "https://starknet-sepolia.preview.apibara.org"
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function isEmpty(path2) {
|
|
91
|
+
const files = fs.readdirSync(path2);
|
|
92
|
+
return files.length === 0 || files.length === 1 && files[0] === ".git";
|
|
93
|
+
}
|
|
94
|
+
function emptyDir(dir) {
|
|
95
|
+
if (!fs.existsSync(dir)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
for (const file of fs.readdirSync(dir)) {
|
|
99
|
+
if (file === ".git") {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function validateLanguage(language, throwError = false) {
|
|
106
|
+
if (!language) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (language === "typescript" || language === "ts" || language === "javascript" || language === "js") {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (throwError) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Invalid language ${cyan("(--language | -l)")}: ${red(language)}. Options: ${blue("typescript, ts")} or ${yellow("javascript, js")} | default: ${cyan("typescript")}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
function getLanguageFromAlias(alias) {
|
|
120
|
+
if (alias === "ts" || alias === "typescript") {
|
|
121
|
+
return "typescript";
|
|
122
|
+
}
|
|
123
|
+
if (alias === "js" || alias === "javascript") {
|
|
124
|
+
return "javascript";
|
|
125
|
+
}
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Invalid language ${cyan("(--language | -l)")}: ${red(alias)}. Options: ${blue("typescript, ts")} or ${yellow("javascript, js")}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
function validateIndexerId(indexerId, throwError = false) {
|
|
131
|
+
if (!indexerId) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if (!/^[a-z0-9-]+$/.test(indexerId)) {
|
|
135
|
+
if (throwError) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Invalid indexer ID ${cyan("(--indexer-id)")}: ${red(indexerId)}. Indexer ID must contain only lowercase letters, numbers, and hyphens.`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
function validateChain(chain, throwError = false) {
|
|
145
|
+
if (!chain) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (chain) {
|
|
149
|
+
if (chain === "starknet" || chain === "ethereum" || chain === "beaconchain")
|
|
150
|
+
return true;
|
|
151
|
+
if (throwError) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Invalid chain ${cyan("(--chain)")}: ${red(chain)}. Chain must be one of ${blue("starknet, ethereum, beaconchain")}.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
function validateNetwork(chain, network, throwError = false) {
|
|
161
|
+
if (!network) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
if (network === "other") {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
if (chain) {
|
|
168
|
+
if (chain === "starknet") {
|
|
169
|
+
if (network === "mainnet" || network === "sepolia") {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
if (throwError) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Invalid network ${cyan("(--network)")}: ${red(network)}. For chain ${blue("starknet")}, network must be one of ${blue("mainnet, sepolia, other")}.`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (chain === "ethereum") {
|
|
180
|
+
if (network === "mainnet" || network === "goerli") {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
if (throwError) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Invalid network ${cyan("(--network)")}: ${red(network)}. For chain ${blue("ethereum")}, network must be one of ${blue("mainnet, goerli, other")}.`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (chain === "beaconchain") {
|
|
191
|
+
if (network === "mainnet") {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
if (throwError) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Invalid network ${cyan("(--network)")}: ${red(network)}. For chain ${blue("beaconchain")}, network must be ${blue("mainnet, other")}.`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (networks.find((n) => n.name === network)) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
if (throwError) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Invalid network ${cyan("(--network)")}: ${red(network)}. Network must be one of ${blue("mainnet, sepolia, goerli, other")}.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
function validateStorage(storage, throwError = false) {
|
|
213
|
+
if (!storage) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
if (storage === "postgres" || storage === "none") {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
if (throwError) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Invalid storage ${cyan("(--storage)")}: ${red(storage)}. Storage must be one of ${blue("postgres, none")}.`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
function validateDnaUrl(dnaUrl, throwError = false) {
|
|
227
|
+
if (!dnaUrl) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
if (!dnaUrl.startsWith("https://") && !dnaUrl.startsWith("http://")) {
|
|
231
|
+
if (throwError) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Invalid DNA URL ${cyan("(--dna-url)")}: ${red(dnaUrl)}. DNA URL must start with ${blue("https:// or http://")}.`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
function hasApibaraConfig(cwd) {
|
|
241
|
+
const configPathJS = path.join(cwd, "apibara.config.js");
|
|
242
|
+
const configPathTS = path.join(cwd, "apibara.config.ts");
|
|
243
|
+
return fs.existsSync(configPathJS) || fs.existsSync(configPathTS);
|
|
244
|
+
}
|
|
245
|
+
function getApibaraConfigLanguage(cwd) {
|
|
246
|
+
const configPathJS = path.join(cwd, "apibara.config.js");
|
|
247
|
+
const configPathTS = path.join(cwd, "apibara.config.ts");
|
|
248
|
+
if (fs.existsSync(configPathJS)) {
|
|
249
|
+
return "javascript";
|
|
250
|
+
}
|
|
251
|
+
if (fs.existsSync(configPathTS)) {
|
|
252
|
+
return "typescript";
|
|
253
|
+
}
|
|
254
|
+
throw new Error(red("\u2716") + " No apibara.config found");
|
|
255
|
+
}
|
|
256
|
+
function getDnaUrl(chain, network) {
|
|
257
|
+
if (chain === "ethereum") {
|
|
258
|
+
if (network === "mainnet") {
|
|
259
|
+
return dnaUrls.ethereum;
|
|
260
|
+
}
|
|
261
|
+
if (network === "sepolia") {
|
|
262
|
+
return dnaUrls.ethereumSepolia;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (chain === "beaconchain") {
|
|
266
|
+
if (network === "mainnet") {
|
|
267
|
+
return dnaUrls.beaconchain;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (chain === "starknet") {
|
|
271
|
+
if (network === "mainnet") {
|
|
272
|
+
return dnaUrls.starknet;
|
|
273
|
+
}
|
|
274
|
+
if (network === "sepolia") {
|
|
275
|
+
return dnaUrls.starknetSepolia;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
throw new Error(red("\u2716") + " Invalid chain or network");
|
|
279
|
+
}
|
|
280
|
+
function convertKebabToCamelCase(_str) {
|
|
281
|
+
let str = _str;
|
|
282
|
+
if (!str || typeof str !== "string") {
|
|
283
|
+
return "";
|
|
284
|
+
}
|
|
285
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(str)) {
|
|
286
|
+
return str;
|
|
287
|
+
}
|
|
288
|
+
str = str.trim().replace(/^-+|-+$/g, "");
|
|
289
|
+
if (!str) {
|
|
290
|
+
return "";
|
|
291
|
+
}
|
|
292
|
+
return str.replace(/[-_]+/g, "-").split("-").filter(Boolean).map((word, index) => {
|
|
293
|
+
const _word = word.toLowerCase();
|
|
294
|
+
if (index > 0) {
|
|
295
|
+
return _word.charAt(0).toUpperCase() + _word.slice(1);
|
|
296
|
+
}
|
|
297
|
+
return _word;
|
|
298
|
+
}).join("");
|
|
299
|
+
}
|
|
300
|
+
async function checkFileExists(path2, options) {
|
|
301
|
+
const { askPrompt = false, fileName, allowIgnore = false } = options ?? {};
|
|
302
|
+
if (!fs.existsSync(path2)) {
|
|
303
|
+
return {
|
|
304
|
+
exists: false,
|
|
305
|
+
overwrite: false
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
if (askPrompt) {
|
|
309
|
+
const { overwrite } = await prompts({
|
|
310
|
+
type: "select",
|
|
311
|
+
name: "overwrite",
|
|
312
|
+
message: `${fileName ?? basename(path2)} already exists. Please choose how to proceed:`,
|
|
313
|
+
initial: 0,
|
|
314
|
+
choices: [
|
|
315
|
+
...allowIgnore ? [
|
|
316
|
+
{
|
|
317
|
+
title: "Keep original file",
|
|
318
|
+
value: "ignore"
|
|
319
|
+
}
|
|
320
|
+
] : [],
|
|
321
|
+
{
|
|
322
|
+
title: "Cancel operation",
|
|
323
|
+
value: "no"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
title: "Overwrite file",
|
|
327
|
+
value: "yes"
|
|
328
|
+
}
|
|
329
|
+
]
|
|
330
|
+
});
|
|
331
|
+
if (overwrite === "no") {
|
|
332
|
+
cancelOperation();
|
|
333
|
+
}
|
|
334
|
+
if (overwrite === "ignore") {
|
|
335
|
+
return {
|
|
336
|
+
exists: true,
|
|
337
|
+
overwrite: false
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
exists: true,
|
|
342
|
+
overwrite: true
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
exists: true,
|
|
347
|
+
overwrite: false
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function cancelOperation(message) {
|
|
351
|
+
throw new Error(red("\u2716") + (message ?? " Operation cancelled"));
|
|
352
|
+
}
|
|
353
|
+
function getPackageManager() {
|
|
354
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
355
|
+
const pkgInfo = pkgFromUserAgent(userAgent);
|
|
356
|
+
if (pkgInfo) {
|
|
357
|
+
return pkgInfo;
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
name: "npm"
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function pkgFromUserAgent(userAgent) {
|
|
364
|
+
if (!userAgent)
|
|
365
|
+
return void 0;
|
|
366
|
+
const pkgSpec = userAgent.split(" ")[0];
|
|
367
|
+
const pkgSpecArr = pkgSpec.split("/");
|
|
368
|
+
return {
|
|
369
|
+
name: pkgSpecArr[0],
|
|
370
|
+
version: pkgSpecArr[1]
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function formatFile(path2) {
|
|
374
|
+
const file = fs.readFileSync(path2, "utf8");
|
|
375
|
+
const formatted = await prettier.format(file, {
|
|
376
|
+
filepath: path2,
|
|
377
|
+
tabWidth: 2
|
|
378
|
+
});
|
|
379
|
+
fs.writeFileSync(path2, formatted);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function generatePackageJson(isTypeScript) {
|
|
383
|
+
return {
|
|
384
|
+
name: "apibara-app",
|
|
385
|
+
version: "0.1.0",
|
|
386
|
+
private: true,
|
|
387
|
+
type: "module",
|
|
388
|
+
scripts: {
|
|
389
|
+
prepare: "apibara prepare",
|
|
390
|
+
dev: "apibara dev",
|
|
391
|
+
start: "apibara start",
|
|
392
|
+
build: "apibara build",
|
|
393
|
+
...isTypeScript && { typecheck: "tsc --noEmit" }
|
|
394
|
+
},
|
|
395
|
+
dependencies: {
|
|
396
|
+
"@apibara/indexer": packageVersions["@apibara/indexer"],
|
|
397
|
+
"@apibara/protocol": packageVersions["@apibara/protocol"],
|
|
398
|
+
apibara: packageVersions.apibara
|
|
399
|
+
},
|
|
400
|
+
devDependencies: {
|
|
401
|
+
...isTypeScript && {
|
|
402
|
+
"@rollup/plugin-typescript": packageVersions["@rollup/plugin-typescript"],
|
|
403
|
+
"@types/node": packageVersions["@types/node"],
|
|
404
|
+
typescript: packageVersions.typescript
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function generateTsConfig() {
|
|
410
|
+
return {
|
|
411
|
+
$schema: "https://json.schemastore.org/tsconfig",
|
|
412
|
+
display: "Default",
|
|
413
|
+
compilerOptions: {
|
|
414
|
+
forceConsistentCasingInFileNames: true,
|
|
415
|
+
target: "ES2022",
|
|
416
|
+
lib: ["ESNext"],
|
|
417
|
+
module: "ESNext",
|
|
418
|
+
moduleResolution: "bundler",
|
|
419
|
+
skipLibCheck: true,
|
|
420
|
+
types: ["node"],
|
|
421
|
+
noEmit: true,
|
|
422
|
+
strict: true,
|
|
423
|
+
baseUrl: "."
|
|
424
|
+
},
|
|
425
|
+
include: [".", "./.apibara/types"],
|
|
426
|
+
exclude: ["node_modules"]
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function generateApibaraConfig(isTypeScript) {
|
|
430
|
+
return `${isTypeScript ? 'import typescript from "@rollup/plugin-typescript";\nimport type { Plugin } from "apibara/rollup";\n' : ""}import { defineConfig } from "apibara/config";
|
|
431
|
+
|
|
432
|
+
export default defineConfig({
|
|
433
|
+
runtimeConfig: {},${isTypeScript ? `
|
|
434
|
+
rollupConfig: {
|
|
435
|
+
plugins: [typescript()${isTypeScript ? " as Plugin" : ""}],
|
|
436
|
+
},` : ""}
|
|
437
|
+
});
|
|
438
|
+
`;
|
|
439
|
+
}
|
|
440
|
+
function generateIndexer({
|
|
441
|
+
indexerId,
|
|
442
|
+
storage,
|
|
443
|
+
chain,
|
|
444
|
+
language
|
|
445
|
+
}) {
|
|
446
|
+
return `import { defineIndexer } from "@apibara/indexer";
|
|
447
|
+
import { useLogger } from "@apibara/indexer/plugins";
|
|
448
|
+
${storage === "postgres" ? `import { drizzleStorage } from "@apibara/plugin-drizzle";` : ""}
|
|
449
|
+
${chain === "ethereum" ? `import { EvmStream } from "@apibara/evm";` : chain === "beaconchain" ? `import { BeaconChainStream } from "@apibara/beaconchain";` : chain === "starknet" ? `import { StarknetStream } from "@apibara/starknet";` : ""}
|
|
450
|
+
${language === "typescript" ? `import type { ApibaraRuntimeConfig } from "apibara/types";` : ""}
|
|
451
|
+
${storage === "postgres" ? `import { getDrizzlePgDatabase } from "../lib/db";` : ""}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
export default function (runtimeConfig${language === "typescript" ? ": ApibaraRuntimeConfig" : ""}) {
|
|
455
|
+
const indexerId = "${indexerId}";
|
|
456
|
+
const { startingBlock, streamUrl${storage === "postgres" ? ", postgresConnectionString" : ""} } = runtimeConfig[indexerId];
|
|
457
|
+
${storage === "postgres" ? "const { db } = getDrizzlePgDatabase(postgresConnectionString);" : ""}
|
|
458
|
+
|
|
459
|
+
return defineIndexer(${chain === "ethereum" ? "EvmStream" : chain === "beaconchain" ? "BeaconChainStream" : chain === "starknet" ? "StarknetStream" : ""})({
|
|
460
|
+
streamUrl,
|
|
461
|
+
finality: "accepted",
|
|
462
|
+
startingBlock: BigInt(startingBlock),
|
|
463
|
+
filter: {
|
|
464
|
+
header: "always",
|
|
465
|
+
},
|
|
466
|
+
plugins: [${storage === "postgres" ? "drizzleStorage({ db, persistState: true })" : ""}],
|
|
467
|
+
async transform({ endCursor, finality }) {
|
|
468
|
+
const logger = useLogger();
|
|
469
|
+
|
|
470
|
+
logger.info(
|
|
471
|
+
"Transforming block | orderKey: ",
|
|
472
|
+
endCursor?.orderKey,
|
|
473
|
+
" | finality: ",
|
|
474
|
+
finality
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
${storage === "postgres" ? `// Example snippet to insert data into db using drizzle with postgres
|
|
478
|
+
// const { db } = useDrizzleStorage();
|
|
479
|
+
// const { logs } = block;
|
|
480
|
+
// for (const log of logs) {
|
|
481
|
+
// await db.insert(exampleTable).values({
|
|
482
|
+
// number: Number(endCursor?.orderKey),
|
|
483
|
+
// hash: log.transactionHash,
|
|
484
|
+
// });
|
|
485
|
+
// }` : ""}
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
`;
|
|
490
|
+
}
|
|
491
|
+
async function createIndexerFile(options) {
|
|
492
|
+
const indexerFilePath = path.join(
|
|
493
|
+
options.cwd,
|
|
494
|
+
"indexers",
|
|
495
|
+
`${options.indexerFileId}.indexer.${options.language === "typescript" ? "ts" : "js"}`
|
|
496
|
+
);
|
|
497
|
+
const { exists, overwrite } = await checkFileExists(indexerFilePath, {
|
|
498
|
+
askPrompt: true
|
|
499
|
+
});
|
|
500
|
+
if (exists && !overwrite)
|
|
501
|
+
return;
|
|
502
|
+
const indexerContent = generateIndexer(options);
|
|
503
|
+
fs.mkdirSync(path.dirname(indexerFilePath), { recursive: true });
|
|
504
|
+
fs.writeFileSync(indexerFilePath, indexerContent);
|
|
505
|
+
await formatFile(indexerFilePath);
|
|
506
|
+
}
|
|
507
|
+
async function updatePackageJson({
|
|
508
|
+
cwd,
|
|
509
|
+
chain,
|
|
510
|
+
storage,
|
|
511
|
+
language
|
|
512
|
+
}) {
|
|
513
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
514
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
515
|
+
if (chain === "ethereum") {
|
|
516
|
+
packageJson.dependencies["@apibara/evm"] = packageVersions["@apibara/evm"];
|
|
517
|
+
} else if (chain === "beaconchain") {
|
|
518
|
+
packageJson.dependencies["@apibara/beaconchain"] = packageVersions["@apibara/beaconchain"];
|
|
519
|
+
} else if (chain === "starknet") {
|
|
520
|
+
packageJson.dependencies["@apibara/starknet"] = packageVersions["@apibara/starknet"];
|
|
521
|
+
}
|
|
522
|
+
if (storage === "postgres") {
|
|
523
|
+
packageJson.scripts["drizzle:generate"] = "drizzle-kit generate";
|
|
524
|
+
packageJson.scripts["drizzle:migrate"] = "drizzle-kit migrate";
|
|
525
|
+
packageJson.dependencies["@apibara/plugin-drizzle"] = packageVersions["@apibara/plugin-drizzle"];
|
|
526
|
+
packageJson.dependencies["drizzle-orm"] = packageVersions["drizzle-orm"];
|
|
527
|
+
packageJson.dependencies["@electric-sql/pglite"] = packageVersions["@electric-sql/pglite"];
|
|
528
|
+
packageJson.dependencies["drizzle-kit"] = packageVersions["drizzle-kit"];
|
|
529
|
+
packageJson.dependencies["pg"] = packageVersions["pg"];
|
|
530
|
+
if (language === "typescript") {
|
|
531
|
+
packageJson.devDependencies["@types/pg"] = packageVersions["@types/pg"];
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
535
|
+
await formatFile(packageJsonPath);
|
|
536
|
+
}
|
|
537
|
+
async function updateApibaraConfigFile({
|
|
538
|
+
indexerId,
|
|
539
|
+
cwd,
|
|
540
|
+
chain,
|
|
541
|
+
storage,
|
|
542
|
+
language,
|
|
543
|
+
network,
|
|
544
|
+
dnaUrl
|
|
545
|
+
}) {
|
|
546
|
+
const pathToConfig = path.join(
|
|
547
|
+
cwd,
|
|
548
|
+
`apibara.config.${language === "typescript" ? "ts" : "js"}`
|
|
549
|
+
);
|
|
550
|
+
const runtimeConfigString = `{
|
|
551
|
+
startingBlock: 0,
|
|
552
|
+
streamUrl: "${dnaUrl ?? getDnaUrl(chain, network)}"${storage === "postgres" ? `,
|
|
553
|
+
postgresConnectionString: process.env["POSTGRES_CONNECTION_STRING"] ?? "memory://${indexerId}"` : ""}}`;
|
|
554
|
+
const project = new Project();
|
|
555
|
+
const sourceFile = project.addSourceFileAtPath(pathToConfig);
|
|
556
|
+
const defineConfigCall = sourceFile.getFirstDescendantByKind(
|
|
557
|
+
SyntaxKind.CallExpression
|
|
558
|
+
);
|
|
559
|
+
if (!defineConfigCall)
|
|
560
|
+
return;
|
|
561
|
+
const configObjectExpression = defineConfigCall.getArguments()[0];
|
|
562
|
+
const runtimeConfigObject = configObjectExpression.getProperty("runtimeConfig");
|
|
563
|
+
if (!runtimeConfigObject) {
|
|
564
|
+
configObjectExpression.addPropertyAssignment({
|
|
565
|
+
name: "runtimeConfig",
|
|
566
|
+
initializer: `{
|
|
567
|
+
"${indexerId}": ${runtimeConfigString}
|
|
568
|
+
}`
|
|
569
|
+
});
|
|
570
|
+
} else {
|
|
571
|
+
const runtimeConfigProp = runtimeConfigObject.asKindOrThrow(
|
|
572
|
+
SyntaxKind.PropertyAssignment
|
|
573
|
+
);
|
|
574
|
+
const runtimeConfigObj = runtimeConfigProp.getInitializerOrThrow().asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
575
|
+
runtimeConfigObj.addPropertyAssignment({
|
|
576
|
+
name: `"${indexerId}"`,
|
|
577
|
+
initializer: runtimeConfigString
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
sourceFile.saveSync();
|
|
581
|
+
await formatFile(pathToConfig);
|
|
582
|
+
}
|
|
583
|
+
async function createDrizzleStorageFiles(options) {
|
|
584
|
+
const { cwd, language, storage } = options;
|
|
585
|
+
if (storage !== "postgres")
|
|
586
|
+
return;
|
|
587
|
+
const fileExtension = language === "typescript" ? "ts" : "js";
|
|
588
|
+
const drizzleConfigFileName = `drizzle.config.${fileExtension}`;
|
|
589
|
+
const drizzleConfigPath = path.join(cwd, drizzleConfigFileName);
|
|
590
|
+
const { exists, overwrite } = await checkFileExists(drizzleConfigPath, {
|
|
591
|
+
askPrompt: true,
|
|
592
|
+
allowIgnore: true
|
|
593
|
+
});
|
|
594
|
+
if (!exists || overwrite) {
|
|
595
|
+
const drizzleConfigContent = `${language === "typescript" ? 'import type { Config } from "drizzle-kit";' : ""}
|
|
596
|
+
|
|
597
|
+
export default {
|
|
598
|
+
schema: "./lib/schema.ts",
|
|
599
|
+
out: "./drizzle",
|
|
600
|
+
dialect: "postgresql",
|
|
601
|
+
dbCredentials: {
|
|
602
|
+
url: process.env["POSTGRES_CONNECTION_STRING"] ?? "",
|
|
603
|
+
},
|
|
604
|
+
}${language === "typescript" ? " satisfies Config" : ""};`;
|
|
605
|
+
fs.writeFileSync(drizzleConfigPath, drizzleConfigContent);
|
|
606
|
+
await formatFile(drizzleConfigPath);
|
|
607
|
+
consola.success(`Created ${cyan(drizzleConfigFileName)}`);
|
|
608
|
+
}
|
|
609
|
+
const schemaFileName = `schema.${fileExtension}`;
|
|
610
|
+
const schemaPath = path.join(cwd, "lib", schemaFileName);
|
|
611
|
+
const { exists: schemaExists, overwrite: schemaOverwrite } = await checkFileExists(schemaPath, {
|
|
612
|
+
askPrompt: true,
|
|
613
|
+
allowIgnore: true,
|
|
614
|
+
fileName: `lib/${schemaFileName}`
|
|
615
|
+
});
|
|
616
|
+
if (!schemaExists || schemaOverwrite) {
|
|
617
|
+
const schemaContent = `// --- Add your pg table schemas here ----
|
|
618
|
+
|
|
619
|
+
// import { bigint, pgTable, text, uuid } from "drizzle-orm/pg-core";
|
|
620
|
+
|
|
621
|
+
// export const exampleTable = pgTable("example_table", {
|
|
622
|
+
// id: uuid("id").primaryKey().defaultRandom(),
|
|
623
|
+
// number: bigint("number", { mode: "number" }),
|
|
624
|
+
// hash: text("hash"),
|
|
625
|
+
// });
|
|
626
|
+
|
|
627
|
+
export {};
|
|
628
|
+
`;
|
|
629
|
+
fs.mkdirSync(path.dirname(schemaPath), { recursive: true });
|
|
630
|
+
fs.writeFileSync(schemaPath, schemaContent);
|
|
631
|
+
await formatFile(schemaPath);
|
|
632
|
+
consola.success(`Created ${cyan("lib/schema.ts")}`);
|
|
633
|
+
}
|
|
634
|
+
const dbFileName = `db.${fileExtension}`;
|
|
635
|
+
const dbPath = path.join(cwd, "lib", dbFileName);
|
|
636
|
+
const { exists: dbExists, overwrite: dbOverwrite } = await checkFileExists(
|
|
637
|
+
dbPath,
|
|
638
|
+
{
|
|
639
|
+
askPrompt: true,
|
|
640
|
+
fileName: `lib/${dbFileName}`,
|
|
641
|
+
allowIgnore: true
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
if (!dbExists || dbOverwrite) {
|
|
645
|
+
const dbContent = `import * as schema from "./schema";
|
|
646
|
+
import { drizzle as nodePgDrizzle } from "drizzle-orm/node-postgres";
|
|
647
|
+
import { drizzle as pgLiteDrizzle } from "drizzle-orm/pglite";
|
|
648
|
+
import pg from "pg";
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
export function getDrizzlePgDatabase(connectionString${language === "typescript" ? ": string" : ""}) {
|
|
652
|
+
// Create pglite instance
|
|
653
|
+
if (connectionString.includes("memory")) {
|
|
654
|
+
return {
|
|
655
|
+
db: pgLiteDrizzle({
|
|
656
|
+
schema,
|
|
657
|
+
connection: {
|
|
658
|
+
dataDir: connectionString,
|
|
659
|
+
},
|
|
660
|
+
}),
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Create node-postgres instance
|
|
665
|
+
const pool = new pg.Pool({
|
|
666
|
+
connectionString,
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
return { db: nodePgDrizzle(pool, { schema }) };
|
|
670
|
+
}`;
|
|
671
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
672
|
+
fs.writeFileSync(dbPath, dbContent);
|
|
673
|
+
await formatFile(dbPath);
|
|
674
|
+
consola.success(`Created ${cyan(`lib/${dbFileName}`)}`);
|
|
675
|
+
}
|
|
676
|
+
console.log("\n");
|
|
677
|
+
if (!schemaExists || schemaOverwrite) {
|
|
678
|
+
consola.info(
|
|
679
|
+
`Make sure to export your pgTables in ${cyan(`lib/${schemaFileName}`)}`
|
|
680
|
+
);
|
|
681
|
+
console.log();
|
|
682
|
+
consola.info(`${magenta("Example:")}
|
|
683
|
+
|
|
684
|
+
${yellow(`
|
|
685
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
686
|
+
\u2502 lib/schema.ts \u2502
|
|
687
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
688
|
+
|
|
689
|
+
import { bigint, pgTable, text, uuid } from "drizzle-orm/pg-core";
|
|
690
|
+
|
|
691
|
+
export const exampleTable = pgTable("example_table", {
|
|
692
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
693
|
+
number: bigint("number", { mode: "number" }),
|
|
694
|
+
hash: text("hash"),
|
|
695
|
+
});`)}`);
|
|
696
|
+
console.log("\n");
|
|
697
|
+
}
|
|
698
|
+
consola.info(
|
|
699
|
+
`Run ${green(`${options.packageManager} run drizzle:generate`)} & ${green(`${options.packageManager} run drizzle:migrate`)} to generate and apply migrations.`
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
async function createStorageRelatedFiles(options) {
|
|
703
|
+
const { storage } = options;
|
|
704
|
+
if (storage === "postgres") {
|
|
705
|
+
await createDrizzleStorageFiles(options);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async function initializeProject({
|
|
710
|
+
argTargetDir,
|
|
711
|
+
argLanguage,
|
|
712
|
+
argNoCreateIndexer
|
|
713
|
+
}) {
|
|
714
|
+
const cwd = process.cwd();
|
|
715
|
+
validateLanguage(argLanguage, true);
|
|
716
|
+
console.log();
|
|
717
|
+
const result = await prompts(
|
|
718
|
+
[
|
|
719
|
+
{
|
|
720
|
+
type: () => argTargetDir && (!fs.existsSync(argTargetDir) || isEmpty(argTargetDir)) ? null : "select",
|
|
721
|
+
name: "overwrite",
|
|
722
|
+
message: () => (argTargetDir === "." ? "Current directory" : `Target directory "${argTargetDir}"`) + " is not empty. Please choose how to proceed:",
|
|
723
|
+
initial: 0,
|
|
724
|
+
choices: [
|
|
725
|
+
{
|
|
726
|
+
title: "Cancel operation",
|
|
727
|
+
value: "no"
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
title: "Remove existing files and continue",
|
|
731
|
+
value: "yes"
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
title: "Ignore files and continue",
|
|
735
|
+
value: "ignore"
|
|
736
|
+
}
|
|
737
|
+
],
|
|
738
|
+
hint: "\nCurrent Working Directory: " + cwd
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
type: (_, { overwrite: overwrite2 }) => {
|
|
742
|
+
if (overwrite2 === "no") {
|
|
743
|
+
cancelOperation();
|
|
744
|
+
}
|
|
745
|
+
return null;
|
|
746
|
+
},
|
|
747
|
+
name: "overwriteChecker"
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
type: argLanguage ? null : "select",
|
|
751
|
+
name: "prompt_language",
|
|
752
|
+
message: "Select a language:",
|
|
753
|
+
choices: [
|
|
754
|
+
{
|
|
755
|
+
title: "Typescript",
|
|
756
|
+
value: "typescript"
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
title: "Javascript",
|
|
760
|
+
value: "javascript"
|
|
761
|
+
}
|
|
762
|
+
]
|
|
763
|
+
}
|
|
764
|
+
],
|
|
765
|
+
{
|
|
766
|
+
onCancel: () => {
|
|
767
|
+
cancelOperation();
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
);
|
|
771
|
+
const { overwrite, prompt_language } = result;
|
|
772
|
+
const root = path.join(cwd, argTargetDir);
|
|
773
|
+
if (overwrite === "yes") {
|
|
774
|
+
emptyDir(root);
|
|
775
|
+
} else if (!fs.existsSync(root)) {
|
|
776
|
+
fs.mkdirSync(root, { recursive: true });
|
|
777
|
+
}
|
|
778
|
+
const lang = argLanguage ? getLanguageFromAlias(argLanguage) : prompt_language;
|
|
779
|
+
const isTs = lang === "typescript";
|
|
780
|
+
const configExt = isTs ? "ts" : "js";
|
|
781
|
+
console.log("\n");
|
|
782
|
+
consola$1.info(`Initializing project in ${argTargetDir}
|
|
783
|
+
|
|
784
|
+
`);
|
|
785
|
+
const packageJsonPath = path.join(root, "package.json");
|
|
786
|
+
const packageJson = generatePackageJson(isTs);
|
|
787
|
+
fs.writeFileSync(
|
|
788
|
+
packageJsonPath,
|
|
789
|
+
JSON.stringify(packageJson, null, 2) + "\n"
|
|
790
|
+
);
|
|
791
|
+
await formatFile(packageJsonPath);
|
|
792
|
+
consola$1.success("Created ", cyan("package.json"));
|
|
793
|
+
if (isTs) {
|
|
794
|
+
const tsConfigPath = path.join(root, "tsconfig.json");
|
|
795
|
+
const tsConfig = generateTsConfig();
|
|
796
|
+
fs.writeFileSync(tsConfigPath, JSON.stringify(tsConfig, null, 2) + "\n");
|
|
797
|
+
await formatFile(tsConfigPath);
|
|
798
|
+
consola$1.success("Created ", cyan("tsconfig.json"));
|
|
799
|
+
}
|
|
800
|
+
const apibaraConfigPath = path.join(root, `apibara.config.${configExt}`);
|
|
801
|
+
const apibaraConfig = generateApibaraConfig(isTs);
|
|
802
|
+
fs.writeFileSync(apibaraConfigPath, apibaraConfig);
|
|
803
|
+
await formatFile(apibaraConfigPath);
|
|
804
|
+
consola$1.success("Created ", cyan(`apibara.config.${configExt}`));
|
|
805
|
+
const indexersDir = path.join(root, "indexers");
|
|
806
|
+
if (!fs.existsSync(indexersDir)) {
|
|
807
|
+
fs.mkdirSync(indexersDir, { recursive: true });
|
|
808
|
+
consola$1.success(`Created ${cyan("indexers")} directory`);
|
|
809
|
+
}
|
|
810
|
+
console.log("\n");
|
|
811
|
+
consola$1.ready(green("Project initialized successfully"));
|
|
812
|
+
console.log();
|
|
813
|
+
if (!argNoCreateIndexer) {
|
|
814
|
+
consola$1.info("Let's create an indexer\n");
|
|
815
|
+
await addIndexer({});
|
|
816
|
+
} else {
|
|
817
|
+
const pkgManager = getPackageManager();
|
|
818
|
+
consola$1.info(
|
|
819
|
+
"Run ",
|
|
820
|
+
green(`${pkgManager.name} run install`),
|
|
821
|
+
" to install all dependencies"
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async function addIndexer({
|
|
827
|
+
argIndexerId,
|
|
828
|
+
argChain,
|
|
829
|
+
argNetwork,
|
|
830
|
+
argStorage,
|
|
831
|
+
argDnaUrl
|
|
832
|
+
}) {
|
|
833
|
+
const configExists = hasApibaraConfig(process.cwd());
|
|
834
|
+
if (!configExists) {
|
|
835
|
+
consola$1.error("No apibara.config found in the current directory.");
|
|
836
|
+
const prompt_initialize = await prompts({
|
|
837
|
+
type: "confirm",
|
|
838
|
+
name: "prompt_initialize",
|
|
839
|
+
message: reset(
|
|
840
|
+
"Do you want to initialize a apibara project here before adding an indexer?"
|
|
841
|
+
)
|
|
842
|
+
});
|
|
843
|
+
if (prompt_initialize.prompt_initialize) {
|
|
844
|
+
await initializeProject({
|
|
845
|
+
argTargetDir: process.cwd(),
|
|
846
|
+
argNoCreateIndexer: true
|
|
847
|
+
});
|
|
848
|
+
} else {
|
|
849
|
+
consola$1.info(
|
|
850
|
+
`Initialize a project with ${cyan("apibara init")} before adding an indexer`
|
|
851
|
+
);
|
|
852
|
+
throw new Error(
|
|
853
|
+
red("\u2716") + " Operation cancelled: No apibara.config found"
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
const language = getApibaraConfigLanguage(process.cwd());
|
|
858
|
+
validateIndexerId(argIndexerId, true);
|
|
859
|
+
validateChain(argChain, true);
|
|
860
|
+
validateNetwork(argChain, argNetwork, true);
|
|
861
|
+
validateStorage(argStorage, true);
|
|
862
|
+
validateDnaUrl(argDnaUrl, true);
|
|
863
|
+
const result = await prompts(
|
|
864
|
+
[
|
|
865
|
+
{
|
|
866
|
+
type: argIndexerId ? null : "text",
|
|
867
|
+
name: "prompt_indexerId",
|
|
868
|
+
message: reset("Indexer ID:"),
|
|
869
|
+
initial: argIndexerId ?? "my-indexer",
|
|
870
|
+
validate: (id) => validateIndexerId(id) ? checkFileExists(
|
|
871
|
+
path.join(
|
|
872
|
+
process.cwd(),
|
|
873
|
+
"indexers",
|
|
874
|
+
`${id}.indexer.${language === "typescript" ? "ts" : "js"}`
|
|
875
|
+
)
|
|
876
|
+
).then(
|
|
877
|
+
({ exists }) => exists ? `Indexer ${cyan(`${id}.indexer.${language === "typescript" ? "ts" : "js"}`)} already exists` : true
|
|
878
|
+
) : "Invalid indexer ID, it cannot be empty and must be in kebab-case format"
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
type: argChain ? null : "select",
|
|
882
|
+
name: "prompt_chain",
|
|
883
|
+
message: reset("Select a chain:"),
|
|
884
|
+
choices: chains.map((chain) => ({
|
|
885
|
+
title: chain.color(chain.display),
|
|
886
|
+
value: chain
|
|
887
|
+
}))
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
type: argNetwork ? null : "select",
|
|
891
|
+
name: "prompt_network",
|
|
892
|
+
message: reset("Select a network:"),
|
|
893
|
+
choices: (chain) => [
|
|
894
|
+
...(chain?.networks ?? chains.find((c) => c.name === argChain)?.networks ?? []).map((network) => ({
|
|
895
|
+
title: network.color(network.display),
|
|
896
|
+
value: network
|
|
897
|
+
})),
|
|
898
|
+
{
|
|
899
|
+
title: cyan("Other"),
|
|
900
|
+
value: {
|
|
901
|
+
color: cyan,
|
|
902
|
+
display: "Other",
|
|
903
|
+
name: "other"
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
]
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
type: (network) => {
|
|
910
|
+
if (network || argNetwork) {
|
|
911
|
+
return network?.name === "other" || argNetwork === "other" ? "text" : null;
|
|
912
|
+
}
|
|
913
|
+
return null;
|
|
914
|
+
},
|
|
915
|
+
name: "prompt_dnaUrl",
|
|
916
|
+
message: reset("Enter a DNA URL:"),
|
|
917
|
+
validate: (url) => validateDnaUrl(url) || "Provide a valid DNA Url"
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
type: argStorage ? null : "select",
|
|
921
|
+
name: "prompt_storage",
|
|
922
|
+
message: reset("Select a storage:"),
|
|
923
|
+
choices: storages.map((storage) => ({
|
|
924
|
+
title: storage.color(storage.display),
|
|
925
|
+
value: storage
|
|
926
|
+
}))
|
|
927
|
+
}
|
|
928
|
+
],
|
|
929
|
+
{
|
|
930
|
+
onCancel: () => {
|
|
931
|
+
cancelOperation();
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
);
|
|
935
|
+
const {
|
|
936
|
+
prompt_indexerId,
|
|
937
|
+
prompt_chain,
|
|
938
|
+
prompt_network,
|
|
939
|
+
prompt_storage,
|
|
940
|
+
prompt_dnaUrl
|
|
941
|
+
} = result;
|
|
942
|
+
if (!argIndexerId && !prompt_indexerId) {
|
|
943
|
+
throw new Error(red("\u2716") + " Indexer ID is required");
|
|
944
|
+
}
|
|
945
|
+
if (!argChain && !prompt_chain) {
|
|
946
|
+
throw new Error(red("\u2716") + " Chain is required");
|
|
947
|
+
}
|
|
948
|
+
if (!argNetwork && !prompt_network) {
|
|
949
|
+
throw new Error(red("\u2716") + " Network is required");
|
|
950
|
+
}
|
|
951
|
+
const indexerFileId = argIndexerId ?? prompt_indexerId;
|
|
952
|
+
const pkgManager = getPackageManager();
|
|
953
|
+
const options = {
|
|
954
|
+
cwd: process.cwd(),
|
|
955
|
+
indexerFileId,
|
|
956
|
+
indexerId: convertKebabToCamelCase(indexerFileId),
|
|
957
|
+
chain: argChain ?? prompt_chain?.name,
|
|
958
|
+
network: argNetwork ?? prompt_network?.name,
|
|
959
|
+
storage: argStorage ?? prompt_storage?.name,
|
|
960
|
+
dnaUrl: argDnaUrl ?? prompt_dnaUrl,
|
|
961
|
+
language,
|
|
962
|
+
packageManager: pkgManager.name
|
|
963
|
+
};
|
|
964
|
+
await updateApibaraConfigFile(options);
|
|
965
|
+
consola$1.success(
|
|
966
|
+
`Updated ${cyan("apibara.config." + (language === "typescript" ? "ts" : "js"))}`
|
|
967
|
+
);
|
|
968
|
+
await updatePackageJson(options);
|
|
969
|
+
consola$1.success(`Updated ${cyan("package.json")}`);
|
|
970
|
+
await createIndexerFile(options);
|
|
971
|
+
consola$1.success(
|
|
972
|
+
`Created ${cyan(`${indexerFileId}.indexer.${language === "typescript" ? "ts" : "js"}`)}`
|
|
973
|
+
);
|
|
974
|
+
await createStorageRelatedFiles(options);
|
|
975
|
+
console.log();
|
|
976
|
+
consola$1.info(
|
|
977
|
+
`Before running the indexer, run ${cyan(`${options.packageManager} run install`)}${language === "typescript" ? " & " + cyan(`${options.packageManager} run prepare`) : ""}`
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
export { addIndexer, initializeProject };
|