create-eclesia-indexer 1.1.0 → 2.16.0

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.
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // This is a simple shim that points to the built CLI
4
- import '../dist/index.js';
4
+ // tsdown emits fixed extensions on the node platform (index.mjs / index.cjs)
5
+ import '../dist/index.mjs';
package/dist/index.cjs CHANGED
@@ -7,12 +7,16 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
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
- });
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) {
14
+ __defProp(to, key, {
15
+ get: ((k) => from[k]).bind(null, key),
16
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
+ });
18
+ }
19
+ }
16
20
  }
17
21
  return to;
18
22
  };
@@ -25,18 +29,31 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
29
  let node_fs = require("node:fs");
26
30
  let node_path = require("node:path");
27
31
  node_path = __toESM(node_path);
28
- let node_url = require("node:url");
29
32
  let picocolors = require("picocolors");
30
33
  picocolors = __toESM(picocolors);
31
34
  let enquirer = require("enquirer");
32
35
  enquirer = __toESM(enquirer);
33
36
  let fs_extra = require("fs-extra");
34
37
  fs_extra = __toESM(fs_extra);
38
+ let node_crypto = require("node:crypto");
39
+ let node_url = require("node:url");
35
40
 
36
41
  //#region src/create-indexer.ts
37
42
  const { prompt } = enquirer.default;
38
43
  const { ensureDirSync, copySync, writeFileSync, readFileSync } = fs_extra.default;
39
44
  const __dirname$1 = (0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
45
+ /** npm package name rules, restricted to what also works as a directory and a bin name */
46
+ const PROJECT_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,212}[a-z0-9])?$/;
47
+ const CHAIN_PREFIX_PATTERN = /^[a-z][a-z0-9]*$/;
48
+ const CHAIN_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 ._-]*$/;
49
+ function validateProjectName(value) {
50
+ if (!PROJECT_NAME_PATTERN.test(value)) return "Use lowercase letters, digits, dots, hyphens or underscores (an npm package name without a scope); no slashes or spaces.";
51
+ return true;
52
+ }
53
+ /** Random secret safe for .env files and connection strings (URL-safe base64, no padding) */
54
+ function generateSecret(bytes) {
55
+ return (0, node_crypto.randomBytes)(bytes).toString("base64url");
56
+ }
40
57
  const availableModules = [
41
58
  {
42
59
  name: "Auth",
@@ -64,6 +81,10 @@ const minimalAvailableModules = [{
64
81
  hint: "Token transfers and balances"
65
82
  }];
66
83
  async function createIndexer(initialProjectName) {
84
+ if (initialProjectName !== void 0) {
85
+ const valid = validateProjectName(initialProjectName);
86
+ if (valid !== true) throw new Error("Invalid project name '" + initialProjectName + "': " + valid);
87
+ }
67
88
  const config = await gatherProjectInfo(initialProjectName);
68
89
  const targetDir = (0, node_path.resolve)(process.cwd(), config.projectName);
69
90
  console.log(picocolors.default.blue("📁 Creating project directory..."));
@@ -83,7 +104,8 @@ async function createIndexer(initialProjectName) {
83
104
  console.log(picocolors.default.cyan(` cd ${config.projectName}`));
84
105
  console.log(picocolors.default.cyan(` ${config.packageManager} local-dev:start # To run a self-contained local development environment with Postgres`));
85
106
  console.log();
86
- 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.");
107
+ console.log("Credentials for the local environment (Postgres password, Hasura admin secret) were generated into .env. To use an external Postgres instead, change PG_CONNECTION_STRING there.");
108
+ if (config.modules.includes("Bank") && !config.processGenesis) console.log(picocolors.default.yellow("Bank module without genesis processing: balances are tracked as changes since the start height, not absolute amounts."));
87
109
  console.log();
88
110
  console.log(picocolors.default.cyan(` ${config.packageManager} start # To run the indexer`));
89
111
  console.log();
@@ -97,25 +119,29 @@ async function gatherProjectInfo(initialProjectName) {
97
119
  name: "projectName",
98
120
  message: "Project name:",
99
121
  initial: initialProjectName || "my-indexer",
100
- skip: !!initialProjectName
122
+ skip: !!initialProjectName,
123
+ validate: validateProjectName
101
124
  },
102
125
  {
103
126
  type: "input",
104
127
  name: "chainName",
105
128
  message: "Chain name:",
106
- initial: "cosmos-hub"
129
+ initial: "cosmos-hub",
130
+ validate: (value) => CHAIN_NAME_PATTERN.test(value) || "Use letters, digits, spaces, dots, hyphens or underscores."
107
131
  },
108
132
  {
109
133
  type: "input",
110
134
  name: "chainPrefix",
111
135
  message: "Chain address prefix:",
112
- initial: "cosmos"
136
+ initial: "cosmos",
137
+ validate: (value) => CHAIN_PREFIX_PATTERN.test(value) || "A bech32 prefix is lowercase letters and digits, starting with a letter."
113
138
  },
114
139
  {
115
140
  type: "input",
116
141
  name: "description",
117
142
  message: "Description:",
118
- initial: "A custom Cosmos SDK chain indexer"
143
+ initial: "A custom Cosmos SDK chain indexer",
144
+ validate: (value) => !/[\r\n]/.test(value) || "Keep the description on one line."
119
145
  },
120
146
  {
121
147
  type: "input",
@@ -235,9 +261,11 @@ async function copyTemplateFiles(config, targetDir) {
235
261
  if (src.includes(".template")) return false;
236
262
  if (config.packageManager !== "pnpm" && src.includes("pnpm-workspace")) return false;
237
263
  if (src.includes("Dockerfile")) return false;
264
+ if (src.endsWith("_gitignore")) return false;
238
265
  return true;
239
266
  } });
240
267
  copySync((0, node_path.resolve)(templatesDir, "Dockerfile." + config.packageManager), targetDir + "/Dockerfile");
268
+ copySync((0, node_path.resolve)(templatesDir, "_gitignore"), targetDir + "/.gitignore");
241
269
  [
242
270
  "package.json.template",
243
271
  "src/index.ts.template",
@@ -255,16 +283,22 @@ async function copyTemplateFiles(config, targetDir) {
255
283
  }
256
284
  async function processTemplates(config, targetDir) {
257
285
  let polling = false;
286
+ config.enableHealthcheck = false;
287
+ config.healthCheckPort = 8888;
258
288
  config.enablePrometheus = false;
259
289
  config.prometheusPort = 9090;
260
290
  const url = new URL(config.rpcEndpoint);
261
291
  if (url.protocol === "http:" || url.protocol === "https:") polling = true;
292
+ const postgresPassword = generateSecret(18);
293
+ const hasuraAdminSecret = generateSecret(24);
262
294
  const templateVars = {
263
295
  PROJECT_NAME: config.projectName,
264
296
  CHAIN_NAME: config.chainName,
265
297
  DESCRIPTION: config.description,
266
298
  RPC_ENDPOINT: config.rpcEndpoint,
267
- PG_CONNECTION_STRING: "postgres://postgres:password@postgres:5432/indexer",
299
+ PG_CONNECTION_STRING: "postgres://postgres:" + postgresPassword + "@localhost:5432/indexer",
300
+ POSTGRES_PASSWORD: postgresPassword,
301
+ HASURA_ADMIN_SECRET: hasuraAdminSecret,
268
302
  LOG_LEVEL: config.logLevel,
269
303
  QUEUE_SIZE: config.queueSize.toString(),
270
304
  USE_POLLING: polling ? "true" : "false",
@@ -273,6 +307,8 @@ async function processTemplates(config, targetDir) {
273
307
  MINIMAL: config.minimal ? "true" : "false",
274
308
  START_HEIGHT: config.startHeight.toString(),
275
309
  CHAIN_PREFIX: config.chainPrefix,
310
+ ENABLE_HEALTHCHECK: config.enableHealthcheck ? "true" : "false",
311
+ HEALTH_CHECK_PORT: config.healthCheckPort.toString(),
276
312
  ENABLE_PROMETHEUS: config.enablePrometheus ? "true" : "false",
277
313
  PROMETHEUS_PORT: config.prometheusPort.toString(),
278
314
  MODULES_IMPORT: generateModulesImport(config),
@@ -292,9 +328,11 @@ async function processTemplates(config, targetDir) {
292
328
  const filePath = (0, node_path.resolve)(targetDir, file);
293
329
  try {
294
330
  let content = readFileSync(filePath, "utf-8");
331
+ if (file === "docker-compose.yml" && !config.processGenesis) content = content.replace(/\n {4}volumes:\n {6}- \{\{GENESIS_PATH\}\}:[^\n]*\n/, "\n");
295
332
  Object.entries(templateVars).forEach(([key, value]) => {
296
333
  const regex = new RegExp(`{{${key}}}`, "g");
297
- content = content.replace(regex, value);
334
+ const replacement = file === "package.json" ? JSON.stringify(value).slice(1, -1) : value;
335
+ content = content.replace(regex, () => replacement);
298
336
  });
299
337
  writeFileSync(filePath, content);
300
338
  } catch (_error) {}
@@ -304,7 +342,7 @@ function generateModulesImport(config) {
304
342
  const imports = [];
305
343
  imports.push(" Blocks");
306
344
  if (config.modules.includes("Auth")) imports.push(" AuthModule");
307
- if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) imports.push(" BankModule");
345
+ if (config.modules.includes("Bank")) imports.push(" BankModule");
308
346
  if (config.modules.includes("Staking") && !config.minimal) imports.push(" StakingModule");
309
347
  return `import {\n${imports.join(",")}\n} from "@eclesia/core-modules-pg";\n`;
310
348
  }
@@ -313,7 +351,7 @@ function generateModulesInstantiation(config) {
313
351
  if (config.minimal) instantiations.push("const blocksModule = new Blocks.MinimalBlocksModule(registry);");
314
352
  else instantiations.push("const blocksModule = new Blocks.FullBlocksModule(registry);");
315
353
  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);");
354
+ if (config.modules.includes("Bank")) instantiations.push("const bankModule = new BankModule(registry);");
317
355
  if (config.modules.includes("Staking") && !config.minimal) instantiations.push("const stakingModule = new StakingModule(registry);");
318
356
  return instantiations.join("\n");
319
357
  }
@@ -321,9 +359,9 @@ function generateModulesArray(config) {
321
359
  const moduleNames = [];
322
360
  moduleNames.push("blocksModule");
323
361
  if (config.modules.includes("Auth")) moduleNames.push("authModule");
324
- if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) moduleNames.push("bankModule");
362
+ if (config.modules.includes("Bank")) moduleNames.push("bankModule");
325
363
  if (config.modules.includes("Staking") && !config.minimal) moduleNames.push("stakingModule");
326
- return `[${moduleNames.filter((m) => !m.includes("//")).join(", ")}]`;
364
+ return `[${moduleNames.join(", ")}]`;
327
365
  }
328
366
  async function installDependencies(config, targetDir) {
329
367
  const { spawn } = await import("node:child_process");
@@ -344,7 +382,7 @@ async function buildProject(config, targetDir) {
344
382
  cwd: targetDir,
345
383
  stdio: "inherit"
346
384
  }).on("close", (code) => {
347
- if (code !== 0) reject(/* @__PURE__ */ new Error(`Package installation failed with code ${code}`));
385
+ if (code !== 0) reject(/* @__PURE__ */ new Error(`Build failed with code ${code}`));
348
386
  else resolve$2();
349
387
  });
350
388
  });
@@ -352,7 +390,6 @@ async function buildProject(config, targetDir) {
352
390
 
353
391
  //#endregion
354
392
  //#region src/index.ts
355
- (0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
356
393
  async function main() {
357
394
  console.log();
358
395
  console.log(picocolors.default.cyan("🚀 Welcome to create-eclesia-indexer!"));
@@ -1,15 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync } from "node:fs";
3
3
  import path, { dirname, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
4
  import colors from "picocolors";
6
5
  import enquirer from "enquirer";
7
6
  import fse from "fs-extra";
7
+ import { randomBytes } from "node:crypto";
8
+ import { fileURLToPath } from "node:url";
8
9
 
9
10
  //#region src/create-indexer.ts
10
11
  const { prompt } = enquirer;
11
12
  const { ensureDirSync, copySync, writeFileSync, readFileSync } = fse;
12
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ /** npm package name rules, restricted to what also works as a directory and a bin name */
15
+ const PROJECT_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,212}[a-z0-9])?$/;
16
+ const CHAIN_PREFIX_PATTERN = /^[a-z][a-z0-9]*$/;
17
+ const CHAIN_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 ._-]*$/;
18
+ function validateProjectName(value) {
19
+ if (!PROJECT_NAME_PATTERN.test(value)) return "Use lowercase letters, digits, dots, hyphens or underscores (an npm package name without a scope); no slashes or spaces.";
20
+ return true;
21
+ }
22
+ /** Random secret safe for .env files and connection strings (URL-safe base64, no padding) */
23
+ function generateSecret(bytes) {
24
+ return randomBytes(bytes).toString("base64url");
25
+ }
13
26
  const availableModules = [
14
27
  {
15
28
  name: "Auth",
@@ -37,6 +50,10 @@ const minimalAvailableModules = [{
37
50
  hint: "Token transfers and balances"
38
51
  }];
39
52
  async function createIndexer(initialProjectName) {
53
+ if (initialProjectName !== void 0) {
54
+ const valid = validateProjectName(initialProjectName);
55
+ if (valid !== true) throw new Error("Invalid project name '" + initialProjectName + "': " + valid);
56
+ }
40
57
  const config = await gatherProjectInfo(initialProjectName);
41
58
  const targetDir = resolve(process.cwd(), config.projectName);
42
59
  console.log(colors.blue("📁 Creating project directory..."));
@@ -56,7 +73,8 @@ async function createIndexer(initialProjectName) {
56
73
  console.log(colors.cyan(` cd ${config.projectName}`));
57
74
  console.log(colors.cyan(` ${config.packageManager} local-dev:start # To run a self-contained local development environment with Postgres`));
58
75
  console.log();
59
- 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.");
76
+ console.log("Credentials for the local environment (Postgres password, Hasura admin secret) were generated into .env. To use an external Postgres instead, change PG_CONNECTION_STRING there.");
77
+ if (config.modules.includes("Bank") && !config.processGenesis) console.log(colors.yellow("Bank module without genesis processing: balances are tracked as changes since the start height, not absolute amounts."));
60
78
  console.log();
61
79
  console.log(colors.cyan(` ${config.packageManager} start # To run the indexer`));
62
80
  console.log();
@@ -70,25 +88,29 @@ async function gatherProjectInfo(initialProjectName) {
70
88
  name: "projectName",
71
89
  message: "Project name:",
72
90
  initial: initialProjectName || "my-indexer",
73
- skip: !!initialProjectName
91
+ skip: !!initialProjectName,
92
+ validate: validateProjectName
74
93
  },
75
94
  {
76
95
  type: "input",
77
96
  name: "chainName",
78
97
  message: "Chain name:",
79
- initial: "cosmos-hub"
98
+ initial: "cosmos-hub",
99
+ validate: (value) => CHAIN_NAME_PATTERN.test(value) || "Use letters, digits, spaces, dots, hyphens or underscores."
80
100
  },
81
101
  {
82
102
  type: "input",
83
103
  name: "chainPrefix",
84
104
  message: "Chain address prefix:",
85
- initial: "cosmos"
105
+ initial: "cosmos",
106
+ validate: (value) => CHAIN_PREFIX_PATTERN.test(value) || "A bech32 prefix is lowercase letters and digits, starting with a letter."
86
107
  },
87
108
  {
88
109
  type: "input",
89
110
  name: "description",
90
111
  message: "Description:",
91
- initial: "A custom Cosmos SDK chain indexer"
112
+ initial: "A custom Cosmos SDK chain indexer",
113
+ validate: (value) => !/[\r\n]/.test(value) || "Keep the description on one line."
92
114
  },
93
115
  {
94
116
  type: "input",
@@ -208,9 +230,11 @@ async function copyTemplateFiles(config, targetDir) {
208
230
  if (src.includes(".template")) return false;
209
231
  if (config.packageManager !== "pnpm" && src.includes("pnpm-workspace")) return false;
210
232
  if (src.includes("Dockerfile")) return false;
233
+ if (src.endsWith("_gitignore")) return false;
211
234
  return true;
212
235
  } });
213
236
  copySync(resolve(templatesDir, "Dockerfile." + config.packageManager), targetDir + "/Dockerfile");
237
+ copySync(resolve(templatesDir, "_gitignore"), targetDir + "/.gitignore");
214
238
  [
215
239
  "package.json.template",
216
240
  "src/index.ts.template",
@@ -228,16 +252,22 @@ async function copyTemplateFiles(config, targetDir) {
228
252
  }
229
253
  async function processTemplates(config, targetDir) {
230
254
  let polling = false;
255
+ config.enableHealthcheck = false;
256
+ config.healthCheckPort = 8888;
231
257
  config.enablePrometheus = false;
232
258
  config.prometheusPort = 9090;
233
259
  const url = new URL(config.rpcEndpoint);
234
260
  if (url.protocol === "http:" || url.protocol === "https:") polling = true;
261
+ const postgresPassword = generateSecret(18);
262
+ const hasuraAdminSecret = generateSecret(24);
235
263
  const templateVars = {
236
264
  PROJECT_NAME: config.projectName,
237
265
  CHAIN_NAME: config.chainName,
238
266
  DESCRIPTION: config.description,
239
267
  RPC_ENDPOINT: config.rpcEndpoint,
240
- PG_CONNECTION_STRING: "postgres://postgres:password@postgres:5432/indexer",
268
+ PG_CONNECTION_STRING: "postgres://postgres:" + postgresPassword + "@localhost:5432/indexer",
269
+ POSTGRES_PASSWORD: postgresPassword,
270
+ HASURA_ADMIN_SECRET: hasuraAdminSecret,
241
271
  LOG_LEVEL: config.logLevel,
242
272
  QUEUE_SIZE: config.queueSize.toString(),
243
273
  USE_POLLING: polling ? "true" : "false",
@@ -246,6 +276,8 @@ async function processTemplates(config, targetDir) {
246
276
  MINIMAL: config.minimal ? "true" : "false",
247
277
  START_HEIGHT: config.startHeight.toString(),
248
278
  CHAIN_PREFIX: config.chainPrefix,
279
+ ENABLE_HEALTHCHECK: config.enableHealthcheck ? "true" : "false",
280
+ HEALTH_CHECK_PORT: config.healthCheckPort.toString(),
249
281
  ENABLE_PROMETHEUS: config.enablePrometheus ? "true" : "false",
250
282
  PROMETHEUS_PORT: config.prometheusPort.toString(),
251
283
  MODULES_IMPORT: generateModulesImport(config),
@@ -265,9 +297,11 @@ async function processTemplates(config, targetDir) {
265
297
  const filePath = resolve(targetDir, file);
266
298
  try {
267
299
  let content = readFileSync(filePath, "utf-8");
300
+ if (file === "docker-compose.yml" && !config.processGenesis) content = content.replace(/\n {4}volumes:\n {6}- \{\{GENESIS_PATH\}\}:[^\n]*\n/, "\n");
268
301
  Object.entries(templateVars).forEach(([key, value]) => {
269
302
  const regex = new RegExp(`{{${key}}}`, "g");
270
- content = content.replace(regex, value);
303
+ const replacement = file === "package.json" ? JSON.stringify(value).slice(1, -1) : value;
304
+ content = content.replace(regex, () => replacement);
271
305
  });
272
306
  writeFileSync(filePath, content);
273
307
  } catch (_error) {}
@@ -277,7 +311,7 @@ function generateModulesImport(config) {
277
311
  const imports = [];
278
312
  imports.push(" Blocks");
279
313
  if (config.modules.includes("Auth")) imports.push(" AuthModule");
280
- if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) imports.push(" BankModule");
314
+ if (config.modules.includes("Bank")) imports.push(" BankModule");
281
315
  if (config.modules.includes("Staking") && !config.minimal) imports.push(" StakingModule");
282
316
  return `import {\n${imports.join(",")}\n} from "@eclesia/core-modules-pg";\n`;
283
317
  }
@@ -286,7 +320,7 @@ function generateModulesInstantiation(config) {
286
320
  if (config.minimal) instantiations.push("const blocksModule = new Blocks.MinimalBlocksModule(registry);");
287
321
  else instantiations.push("const blocksModule = new Blocks.FullBlocksModule(registry);");
288
322
  if (config.modules.includes("Auth")) instantiations.push("const authModule = new AuthModule(registry);");
289
- if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) instantiations.push("const bankModule = new BankModule(registry);");
323
+ if (config.modules.includes("Bank")) instantiations.push("const bankModule = new BankModule(registry);");
290
324
  if (config.modules.includes("Staking") && !config.minimal) instantiations.push("const stakingModule = new StakingModule(registry);");
291
325
  return instantiations.join("\n");
292
326
  }
@@ -294,9 +328,9 @@ function generateModulesArray(config) {
294
328
  const moduleNames = [];
295
329
  moduleNames.push("blocksModule");
296
330
  if (config.modules.includes("Auth")) moduleNames.push("authModule");
297
- if (config.modules.includes("Bank") && config.startHeight === 1 && config.processGenesis) moduleNames.push("bankModule");
331
+ if (config.modules.includes("Bank")) moduleNames.push("bankModule");
298
332
  if (config.modules.includes("Staking") && !config.minimal) moduleNames.push("stakingModule");
299
- return `[${moduleNames.filter((m) => !m.includes("//")).join(", ")}]`;
333
+ return `[${moduleNames.join(", ")}]`;
300
334
  }
301
335
  async function installDependencies(config, targetDir) {
302
336
  const { spawn } = await import("node:child_process");
@@ -317,7 +351,7 @@ async function buildProject(config, targetDir) {
317
351
  cwd: targetDir,
318
352
  stdio: "inherit"
319
353
  }).on("close", (code) => {
320
- if (code !== 0) reject(/* @__PURE__ */ new Error(`Package installation failed with code ${code}`));
354
+ if (code !== 0) reject(/* @__PURE__ */ new Error(`Build failed with code ${code}`));
321
355
  else resolve$1();
322
356
  });
323
357
  });
@@ -325,7 +359,6 @@ async function buildProject(config, targetDir) {
325
359
 
326
360
  //#endregion
327
361
  //#region src/index.ts
328
- dirname(fileURLToPath(import.meta.url));
329
362
  async function main() {
330
363
  console.log();
331
364
  console.log(colors.cyan("🚀 Welcome to create-eclesia-indexer!"));
@@ -353,4 +386,4 @@ main().catch((error) => {
353
386
 
354
387
  //#endregion
355
388
  export { };
356
- //# sourceMappingURL=index.js.map
389
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["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 {\n randomBytes,\n} from \"node:crypto\";\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: boolean\n startHeight: number\n queueSize: number\n logLevel: string\n processGenesis: boolean\n genesisPath: string | null\n enableHealthcheck: boolean\n healthCheckPort: number\n enablePrometheus: boolean\n prometheusPort: number\n modules: string[]\n packageManager: \"npm\" | \"yarn\" | \"pnpm\"\n}\n\n/** npm package name rules, restricted to what also works as a directory and a bin name */\nconst PROJECT_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,212}[a-z0-9])?$/;\nconst CHAIN_PREFIX_PATTERN = /^[a-z][a-z0-9]*$/;\nconst CHAIN_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 ._-]*$/;\n\nexport function validateProjectName(value: string): true | string {\n if (!PROJECT_NAME_PATTERN.test(value)) {\n return \"Use lowercase letters, digits, dots, hyphens or underscores (an npm package name without a scope); no slashes or spaces.\";\n }\n return true;\n}\n\n/** Random secret safe for .env files and connection strings (URL-safe base64, no padding) */\nfunction generateSecret(bytes: number): string {\n return randomBytes(bytes).toString(\"base64url\");\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 if (initialProjectName !== undefined) {\n const valid = validateProjectName(initialProjectName);\n if (valid !== true) {\n throw new Error(\"Invalid project name '\" + initialProjectName + \"': \" + valid);\n }\n }\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(\"Credentials for the local environment (Postgres password, Hasura admin secret) were generated into .env. To use an external Postgres instead, change PG_CONNECTION_STRING there.\");\n if (config.modules.includes(\"Bank\") && !config.processGenesis) {\n console.log(colors.yellow(\"Bank module without genesis processing: balances are tracked as changes since the start height, not absolute amounts.\"));\n }\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 validate: validateProjectName,\n },\n {\n type: \"input\",\n name: \"chainName\",\n message: \"Chain name:\",\n initial: \"cosmos-hub\",\n validate: (value: string) => CHAIN_NAME_PATTERN.test(value) || \"Use letters, digits, spaces, dots, hyphens or underscores.\",\n },\n {\n type: \"input\",\n name: \"chainPrefix\",\n message: \"Chain address prefix:\",\n initial: \"cosmos\",\n validate: (value: string) => CHAIN_PREFIX_PATTERN.test(value) || \"A bech32 prefix is lowercase letters and digits, starting with a letter.\",\n },\n {\n type: \"input\",\n name: \"description\",\n message: \"Description:\",\n initial: \"A custom Cosmos SDK chain indexer\",\n validate: (value: string) => !/[\\r\\n]/.test(value) || \"Keep the description on one line.\",\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 = false;\n }\n if (answers1.processGenesis) {\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 if (src.endsWith(\"_gitignore\")) {\n return false;\n }\n return true;\n },\n });\n copySync(resolve(templatesDir, \"Dockerfile.\" + config.packageManager), targetDir + \"/Dockerfile\");\n // Stored without the leading dot so npm does not rewrite it when the CLI is published\n copySync(resolve(templatesDir, \"_gitignore\"), targetDir + \"/.gitignore\");\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 config.enableHealthcheck = false;\n config.healthCheckPort = 8888;\n config.enablePrometheus = false;\n config.prometheusPort = 9090;\n const url = new URL(config.rpcEndpoint);\n if (url.protocol === \"http:\" || url.protocol === \"https:\") {\n polling = true;\n }\n // Generated once per project and written only to .env, which is git-ignored\n const postgresPassword = generateSecret(18);\n const hasuraAdminSecret = generateSecret(24);\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:\" + postgresPassword + \"@localhost:5432/indexer\",\n POSTGRES_PASSWORD: postgresPassword,\n HASURA_ADMIN_SECRET: hasuraAdminSecret,\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.chainPrefix,\n ENABLE_HEALTHCHECK: config.enableHealthcheck ? \"true\" : \"false\",\n HEALTH_CHECK_PORT: config.healthCheckPort.toString(),\n ENABLE_PROMETHEUS: config.enablePrometheus ? \"true\" : \"false\",\n PROMETHEUS_PORT: config.prometheusPort.toString(),\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 // Without genesis processing there is no genesis.json to mount; binding a missing host\n // path would make Docker create an empty directory under that name\n if (file === \"docker-compose.yml\" && !config.processGenesis) {\n content = content.replace(/\\n {4}volumes:\\n {6}- \\{\\{GENESIS_PATH\\}\\}:[^\\n]*\\n/, \"\\n\");\n }\n\n Object.entries(templateVars).forEach(([key, value]) => {\n const regex = new RegExp(`{{${key}}}`, \"g\");\n // Placeholders in package.json sit inside JSON strings; escape for that context. A\n // replacer function keeps \"$&\"-style patterns in user input literal.\n const replacement = file === \"package.json\" ? JSON.stringify(value).slice(1, -1) : value;\n content = content.replace(regex, () => replacement);\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\")) {\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\")) {\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\")) {\n moduleNames.push(\"bankModule\");\n }\n if (config.modules.includes(\"Staking\") && !config.minimal) {\n moduleNames.push(\"stakingModule\");\n }\n return `[${moduleNames.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(`Build failed with code ${code}`));\n }\n else {\n resolve();\n }\n });\n });\n}\n","import {\n existsSync,\n} from \"node:fs\";\nimport {\n resolve,\n} from \"node:path\";\n\nimport colors from \"picocolors\";\n\nimport {\n createIndexer,\n} from \"./create-indexer.js\";\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 CHANGED
@@ -1,18 +1,11 @@
1
1
  {
2
2
  "name": "create-eclesia-indexer",
3
- "version": "1.1.0",
3
+ "version": "2.16.0",
4
4
  "description": "CLI tool to scaffold Cosmos SDK chain indexers using eclesia-indexer-core",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-eclesia-indexer": "./bin/create-eclesia-indexer.js"
8
8
  },
9
- "scripts": {
10
- "build": "tsc --noEmit && tsdown",
11
- "lint": "eslint src/**/*.ts",
12
- "lint:fix": "eslint src/**/*.ts --fix",
13
- "typecheck": "tsc --noEmit",
14
- "prepublishOnly": "npm run build"
15
- },
16
9
  "files": [
17
10
  "dist/",
18
11
  "templates/",
@@ -27,27 +20,58 @@
27
20
  "template"
28
21
  ],
29
22
  "author": "",
30
- "license": "Apache-2.0",
23
+ "license": "SEE LICENSE IN LICENSE.md",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/allinbits/eclesia-indexer-core.git",
27
+ "directory": "packages/create-eclesia-indexer"
28
+ },
29
+ "homepage": "https://github.com/allinbits/eclesia-indexer-core#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/allinbits/eclesia-indexer-core/issues"
32
+ },
31
33
  "engines": {
32
34
  "node": ">=18"
33
35
  },
34
36
  "dependencies": {
35
37
  "enquirer": "^2.4.1",
36
- "fs-extra": "^11.2.0",
38
+ "fs-extra": "^11.4.0",
37
39
  "picocolors": "^1.1.1"
38
40
  },
39
41
  "publishConfig": {
40
42
  "access": "public"
41
43
  },
42
44
  "devDependencies": {
45
+ "@eslint/js": "^9.39.5",
46
+ "@stylistic/eslint-plugin": "^5.10.0",
43
47
  "@types/fs-extra": "^11.0.4",
44
- "@types/node": "^22.14.0",
45
- "@eslint/js": "^9.35.0",
46
- "@stylistic/eslint-plugin": "^5.3.1",
47
- "eslint": "^9.35.0",
48
+ "@types/node": "^24.13.3",
49
+ "eslint": "^9.39.5",
48
50
  "eslint-plugin-simple-import-sort": "^12.1.1",
49
- "tsdown": "^0.15.2",
50
- "typescript": "^5.9.2",
51
- "typescript-eslint": "^8.44.0"
51
+ "tsdown": "^0.16.8",
52
+ "typescript": "^5.9.3",
53
+ "typescript-eslint": "^8.70.0"
54
+ },
55
+ "exports": {
56
+ ".": {
57
+ "import": {
58
+ "types": "./dist/index.d.mts",
59
+ "default": "./dist/index.mjs"
60
+ },
61
+ "require": {
62
+ "types": "./dist/index.d.cts",
63
+ "default": "./dist/index.cjs"
64
+ }
65
+ },
66
+ "./package.json": "./package.json"
67
+ },
68
+ "main": "dist/index.cjs",
69
+ "module": "dist/index.mjs",
70
+ "types": "dist/index.d.mts",
71
+ "scripts": {
72
+ "build": "tsc --noEmit && tsdown",
73
+ "lint": "eslint src/**/*.ts",
74
+ "lint:fix": "eslint src/**/*.ts --fix",
75
+ "typecheck": "tsc --noEmit"
52
76
  }
53
77
  }
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ dist
3
+ .env
4
+ *.log
5
+ .git
6
+ # mounted at runtime by docker-compose, never baked into the image
7
+ genesis.json
@@ -3,13 +3,18 @@
3
3
  # Chain RPC endpoint
4
4
  RPC_ENDPOINT={{RPC_ENDPOINT}}
5
5
 
6
- # Database connection
6
+ # Database connection (host-side; docker-compose overrides it with the container hostname)
7
7
  PG_CONNECTION_STRING={{PG_CONNECTION_STRING}}
8
+ # Generated at scaffold time. Used by docker-compose for the Postgres container and Hasura.
9
+ POSTGRES_PASSWORD={{POSTGRES_PASSWORD}}
10
+ HASURA_GRAPHQL_ADMIN_SECRET={{HASURA_ADMIN_SECRET}}
8
11
 
9
12
  # Indexer settings
10
13
  LOG_LEVEL={{LOG_LEVEL}}
11
14
  QUEUE_SIZE={{QUEUE_SIZE}}
12
15
  PROCESS_GENESIS={{PROCESS_GENESIS}}
16
+ # Path to the genesis file, used only when PROCESS_GENESIS=true
17
+ GENESIS_PATH=./genesis.json
13
18
  MINIMAL={{MINIMAL}}
14
19
  CHAIN_PREFIX={{CHAIN_PREFIX}}
15
20
  USE_POLLING={{USE_POLLING}}
@@ -1,26 +1,20 @@
1
- # syntax=docker.io/docker/dockerfile:1.7-labs
2
-
3
- FROM node:20-alpine3.21 AS builder
1
+ # syntax=docker/dockerfile:1
4
2
 
3
+ # Build stage: install every dependency, compile, then drop dev dependencies
4
+ FROM node:22-alpine AS build
5
5
  WORKDIR /usr/src/app
6
-
7
- COPY package*.json ./
8
-
6
+ COPY package.json package-lock.json ./
9
7
  RUN npm ci
8
+ COPY tsconfig.json tsdown.config.ts ./
9
+ COPY src ./src
10
+ RUN npm run build && npm prune --omit=dev
10
11
 
11
- COPY --exclude=genesis.json . .
12
-
13
- # Final image
14
- FROM node:20-alpine3.21
15
-
12
+ # Runtime stage: only the compiled output and production dependencies, running as the unprivileged node user
13
+ FROM node:22-alpine
14
+ ENV NODE_ENV=production
16
15
  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
- RUN npm run build
25
-
26
- ENTRYPOINT ["node", "dist/index.js" ]
16
+ COPY --from=build --chown=node:node /usr/src/app/package.json ./package.json
17
+ COPY --from=build --chown=node:node /usr/src/app/node_modules ./node_modules
18
+ COPY --from=build --chown=node:node /usr/src/app/dist ./dist
19
+ USER node
20
+ ENTRYPOINT ["node", "dist/index.js"]