@constellation-network/node-pilot 0.0.17 → 0.0.19

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.
@@ -65,11 +65,7 @@ export const projectHelper = {
65
65
  await this.installEmbedded('hypergraph');
66
66
  const { projectDir } = configStore.getProjectInfo();
67
67
  const { platform } = configStore.getSystemInfo();
68
- // Create gl0 folder for the fast-forward feature before Docker does
69
- const gl0DataDir = path.join(projectDir, 'gl0', 'data');
70
- fs.mkdirSync(path.join(gl0DataDir, 'incremental_snapshot'), { recursive: true });
71
- fs.mkdirSync(path.join(gl0DataDir, 'snapshot_info'));
72
- fs.mkdirSync(path.join(gl0DataDir, 'tmp'));
68
+ this.prepareDataFolder();
73
69
  if (platform === 'linux') {
74
70
  const layerDir = path.join(projectDir, 'gl0');
75
71
  // set permission for group "docker" on the layer folder and any subfolders created later
@@ -91,6 +87,16 @@ export const projectHelper = {
91
87
  clm.debug(`Installing project from ${projectFolder} to ${projectDir}`);
92
88
  fs.cpSync(projectFolder, projectDir, { recursive: true });
93
89
  },
90
+ prepareDataFolder() {
91
+ const { projectDir } = configStore.getProjectInfo();
92
+ // Create gl0 folder for the fast-forward feature before Docker does
93
+ const gl0DataDir = path.join(projectDir, 'gl0', 'data');
94
+ if (!fs.existsSync(gl0DataDir)) {
95
+ fs.mkdirSync(path.join(gl0DataDir, 'incremental_snapshot'), { recursive: true });
96
+ fs.mkdirSync(path.join(gl0DataDir, 'snapshot_info'));
97
+ fs.mkdirSync(path.join(gl0DataDir, 'tmp'));
98
+ }
99
+ },
94
100
  async selectProject() {
95
101
  // prompt user to install hypergraph or metagraph
96
102
  const networkType = await select({
@@ -139,5 +145,21 @@ export const projectHelper = {
139
145
  }
140
146
  }
141
147
  }
148
+ },
149
+ async upgradeEmbedded(name) {
150
+ const projectFolder = path.resolve(path.dirname(fileURLToPath(import.meta.url)), `../../projects/${name}`);
151
+ if (!fs.existsSync(projectFolder)) {
152
+ clm.error(`Project folder not found: ${projectFolder}`);
153
+ }
154
+ await this.upgradeProject(name, projectFolder);
155
+ },
156
+ async upgradeHypergraph() {
157
+ await this.upgradeEmbedded('hypergraph');
158
+ this.importEnvFiles();
159
+ },
160
+ async upgradeProject(name, projectFolder) {
161
+ const { projectDir } = configStore.getProjectInfo();
162
+ clm.debug(`Upgrading project from ${projectFolder} to ${projectDir}`);
163
+ fs.cpSync(projectFolder, projectDir, { recursive: true });
142
164
  }
143
165
  };
@@ -1,5 +1,6 @@
1
1
  export declare const promptHelper: {
2
2
  configureJavaMemoryArguments(): Promise<void>;
3
+ confirmPrompt(msg: string): Promise<boolean>;
3
4
  doYouWishToContinue(defaultAnswer?: "n" | "y"): Promise<void>;
4
5
  selectLayers(): Promise<void>;
5
6
  selectNetwork(): Promise<void>;
@@ -38,6 +38,19 @@ export const promptHelper = {
38
38
  }
39
39
  }
40
40
  },
41
+ async confirmPrompt(msg) {
42
+ const result = await input({
43
+ default: 'y',
44
+ message: `${msg} (y/n)`,
45
+ validate(value) {
46
+ if (value.toLowerCase() === 'y' || value.toLowerCase() === 'n') {
47
+ return true;
48
+ }
49
+ return 'Please enter "y" or "n"';
50
+ }
51
+ });
52
+ return result.toLowerCase() !== 'n';
53
+ },
41
54
  async doYouWishToContinue(defaultAnswer = 'y') {
42
55
  const result = await input({
43
56
  default: defaultAnswer,
@@ -1,44 +1,49 @@
1
+ import os from "node:os";
1
2
  class CellFormatter {
2
3
  formatCpu(value) {
3
- if (value === '-')
4
- return value;
5
- const num = Number.parseInt(value, 10);
4
+ if (!value || value === '-')
5
+ return '-';
6
+ const cores = os.cpus().length;
7
+ const num = Number.parseFloat(value) / cores;
8
+ value = num.toFixed(1) + '%';
6
9
  if (num === 0)
7
10
  return value;
8
- if (num < 60)
9
- return this.style(value.toString(), "green");
10
- if (num < 99)
11
- return this.style(value.toString(), "yellow", "bold");
12
- return this.style(value.toString(), "bgRed", "bold");
11
+ if (num < 40)
12
+ return this.style(value, "green");
13
+ if (num < 85)
14
+ return this.style(value, "yellow", "bold");
15
+ return this.style(value, "bgRed", "bold");
13
16
  }
14
17
  formatDistance(value) {
15
- if (value === '-')
16
- return value;
17
- const num = Number(value);
18
+ if (!value || value === '-')
19
+ return '-';
20
+ const num = Math.abs(Number(value));
18
21
  if (num === 0)
19
22
  return value;
20
23
  if (num < 9)
21
- return this.style(value.toString(), "bgYellow", "whiteBright", "bold");
22
- return this.style(value.toString(), "bgRed", "bold");
24
+ return this.style(value, "bgYellow", "whiteBright", "bold");
25
+ return this.style(value, "bgRed", "bold");
23
26
  }
24
27
  formatError(value) {
25
- if (value === '-')
26
- return value;
28
+ if (!value || value === '-')
29
+ return '-';
27
30
  return this.style(value, "bgRed", "bold");
28
31
  }
29
32
  formatMem(value) {
30
- if (value === '-')
31
- return value;
32
- const num = Number.parseInt(value.split('(')[1], 10);
33
+ if (!value || value === '-')
34
+ return '-';
35
+ const num = Number.parseInt(value, 10);
33
36
  if (num === 0)
34
37
  return value;
35
- if (num < 70)
38
+ if (num < 86)
36
39
  return this.style(value.toString(), "green");
37
- if (num < 90)
40
+ if (num < 99)
38
41
  return this.style(value.toString(), "yellow", "bold");
39
42
  return this.style(value.toString(), "red", "bold");
40
43
  }
41
44
  formatOrdinal(value) {
45
+ if (!value || value === '-')
46
+ return '-';
42
47
  const [v, changed] = value.split(':');
43
48
  if (changed) {
44
49
  return this.style(v, "bgCyan", "whiteBright", "bold");
@@ -46,6 +51,8 @@ class CellFormatter {
46
51
  return this.style(v, "cyan");
47
52
  }
48
53
  formatState(value) {
54
+ if (!value || value === '-')
55
+ return '-';
49
56
  if (value === 'Offline')
50
57
  return this.style(value, "bgRed", "bold");
51
58
  if (value === 'Ready')
@@ -57,6 +64,8 @@ class CellFormatter {
57
64
  return this.style(value, "white");
58
65
  }
59
66
  formatUpTIme(startTime) {
67
+ if (!startTime)
68
+ return '-';
60
69
  const formattedTime = formatTime(Date.now() - Number(startTime), true);
61
70
  return formattedTime ?? '-';
62
71
  }
@@ -24,7 +24,6 @@ export class StatusTable {
24
24
  return '';
25
25
  });
26
26
  await onKeyPress({});
27
- console.log("\u001B[?25h");
28
27
  process.exit();
29
28
  }
30
29
  getProjectInfo() {
@@ -55,7 +54,7 @@ export class StatusTable {
55
54
  for (const p of projects) {
56
55
  if (!values[p.layer])
57
56
  values[p.layer] = {};
58
- const n = JSON.parse(fs.readFileSync(p.path, 'utf8'));
57
+ const n = fs.existsSync(p.path) ? JSON.parse(fs.readFileSync(p.path, 'utf8')) : {};
59
58
  const projectName = p.project === 'hypergraph' ? '' : p.project;
60
59
  const network = p.network === 'integrationnet' ? 'intnet' : p.network;
61
60
  const distance = Number.isNaN(n.clusterOrdinal - n.ordinal) ? '-' : String(n.clusterOrdinal - n.ordinal);
@@ -82,7 +81,11 @@ export class StatusTable {
82
81
  }
83
82
  this.render(rows, err.msg !== '');
84
83
  if (err.msg) {
85
- process.stdout.write(chalk.green(` AUTO HEALED (${err.timeAgo}): `) + chalk.red(`${err.layer}:${err.msg} - ${err.date}\n`));
84
+ const d = new Date(err.date);
85
+ // if under 8 hours ago
86
+ if (d.getTime() + (8 * 60 * 60 * 1000) > Date.now()) {
87
+ process.stdout.write(chalk.green(` AUTO HEALED (${err.timeAgo}): `) + chalk.red(`${err.layer}:${err.msg} - ${err.date}\n`));
88
+ }
86
89
  }
87
90
  process.stdout.write(" * press any key to cancel");
88
91
  // eslint-disable-next-line no-await-in-loop
@@ -68,6 +68,9 @@ export const clusterService = {
68
68
  const { CL_L0_PEER_HTTP_HOST } = configStore.getEnvNetworkInfo(type);
69
69
  clm.debug(`http://${CL_L0_PEER_HTTP_HOST}:${CL_PUBLIC_HTTP_PORT}/${path}`);
70
70
  return fetch(`http://${CL_L0_PEER_HTTP_HOST}:${CL_PUBLIC_HTTP_PORT}/${path}`)
71
- .then(res => res.json());
71
+ .then(res => res.json())
72
+ .catch(() => {
73
+ throw new Error(`Unable to connect to source node at http://${CL_L0_PEER_HTTP_HOST}:${CL_PUBLIC_HTTP_PORT}/${path}`);
74
+ });
72
75
  }
73
76
  };
@@ -1,8 +1,9 @@
1
1
  import { TessellationLayer } from "../types.js";
2
2
  export declare const dockerService: {
3
3
  dockerBuild(): Promise<void>;
4
- dockerDown(): Promise<void>;
4
+ dockerDown(layers?: TessellationLayer[]): Promise<void>;
5
5
  dockerRestart(layer: TessellationLayer): Promise<void>;
6
+ dockerRestartAll(): Promise<void>;
6
7
  dockerStartLayers(layers: TessellationLayer[]): Promise<void>;
7
8
  dockerUp(): Promise<void>;
8
9
  isRunning(): Promise<boolean>;
@@ -19,16 +19,23 @@ export const dockerService = {
19
19
  if (silent) {
20
20
  spinner.stop();
21
21
  }
22
- clm.postStep('Node container built.');
22
+ clm.postStep('Node container built.');
23
23
  }
24
24
  },
25
- async dockerDown() {
26
- await run('down');
25
+ async dockerDown(layers) {
27
26
  configStore.setProjectStatusToRunning(false);
27
+ await run('down', layers);
28
28
  },
29
29
  async dockerRestart(layer) {
30
30
  await run('restart', [layer]);
31
31
  },
32
+ async dockerRestartAll() {
33
+ if (await this.isRunning()) {
34
+ await this.dockerDown();
35
+ }
36
+ configStore.setProjectStatusToRunning(true);
37
+ await run('up -d');
38
+ },
32
39
  async dockerStartLayers(layers) {
33
40
  await run('up -d', layers);
34
41
  },
@@ -37,9 +44,9 @@ export const dockerService = {
37
44
  if (await this.isRunning()) {
38
45
  await this.dockerDown();
39
46
  }
47
+ configStore.setProjectStatusToRunning(true);
40
48
  await projectHelper.generateLayerEnvFiles();
41
49
  await run('up -d');
42
- configStore.setProjectStatusToRunning(true);
43
50
  },
44
51
  async isRunning() {
45
52
  return shellService.runProjectCommand('docker compose ps -q | grep .', undefined, true).then(Boolean).catch(() => false);
@@ -6,6 +6,7 @@ import path from "node:path";
6
6
  import { clm } from "../clm.js";
7
7
  import { configStore } from "../config-store.js";
8
8
  import { promptHelper } from "../helpers/prompt-helper.js";
9
+ import { shellService } from "./shell-service.js";
9
10
  const CHUNK_SIZE = 20_000;
10
11
  export class FastforwardService {
11
12
  dataDir;
@@ -51,7 +52,7 @@ export class FastforwardService {
51
52
  clm.debug('Saving compressedSnapshotIncremental to: ' + this.tmpDir);
52
53
  fs.writeFileSync(path.join(this.tmpDir, ordinal.toString() + '.c'), compressedSnapshotIncremental);
53
54
  await this.saveSnapshotFiles(ordinal.toString(), hash);
54
- clm.postStep(`Fastforward to snapshot "${ordinal}" completed.`);
55
+ clm.postStep(`✅ Fastforward to snapshot ${chalk.bold(ordinal)} completed.`);
55
56
  }
56
57
  async fetchLatestSnapshot() {
57
58
  const url = `${this.lbUrl}/global-snapshots/latest/combined`;
@@ -92,8 +93,18 @@ export class FastforwardService {
92
93
  const incrementalSnapshotDir = path.join(this.dataDir, 'incremental_snapshot');
93
94
  const hashDir = path.join(incrementalSnapshotDir, 'hash', hashSubdir);
94
95
  const ordinalDir = path.join(incrementalSnapshotDir, 'ordinal', ordinalRounded.toString());
95
- fs.mkdirSync(hashDir, { recursive: true });
96
- fs.mkdirSync(ordinalDir, { recursive: true });
96
+ try {
97
+ fs.mkdirSync(hashDir, { recursive: true });
98
+ }
99
+ catch {
100
+ await shellService.runCommand(`sudo mkdir -p ${hashDir}`);
101
+ }
102
+ try {
103
+ fs.mkdirSync(ordinalDir, { recursive: true });
104
+ }
105
+ catch {
106
+ await shellService.runCommand(`sudo mkdir -p ${ordinalDir}`);
107
+ }
97
108
  const destOrdinalFile = path.join(ordinalDir, ordinal);
98
109
  if (fs.existsSync(destOrdinalFile)) {
99
110
  clm.warn(`Snapshot ${destOrdinalFile} already exists. Skipping...`);
@@ -29,53 +29,6 @@ check_acl() {
29
29
  fi
30
30
  }
31
31
 
32
- # Check and install curl
33
- check_curl() {
34
- # echo "Checking for curl..."
35
- if command -v curl >/dev/null 2>&1; then
36
- echo "✅ curl is already installed."
37
- return 0
38
- fi
39
-
40
- echo "⚠️ curl not found. Will attempt to install curl."
41
-
42
- case "$(uname)" in
43
- Linux)
44
- if command -v apt >/dev/null 2>&1; then
45
- echo "Installing curl using apt..."
46
- sudo apt update
47
- sudo apt install -y curl
48
- elif command -v yum >/dev/null 2>&1; then
49
- echo "Installing curl using yum..."
50
- sudo yum install -y curl
51
- else
52
- echo "⚠️ Unsupported Linux distribution. Please install curl manually."
53
- return 1
54
- fi
55
- ;;
56
- Darwin)
57
- if command -v brew >/dev/null 2>&1; then
58
- echo "Installing curl using Homebrew..."
59
- brew install curl
60
- else
61
- echo "⚠️ Homebrew not found. Please install curl manually."
62
- return 1
63
- fi
64
- ;;
65
- MINGW*|MSYS*|CYGWIN*)
66
- echo "On Windows, please install curl manually."
67
- return 1
68
- ;;
69
- *)
70
- echo "⚠️ Unsupported OS: $(uname). Please install curl manually."
71
- return 1
72
- ;;
73
- esac
74
-
75
- echo "✅ curl installation complete."
76
- return 0
77
- }
78
-
79
32
  # Check and install Docker Engine
80
33
  check_docker() {
81
34
  # echo "Checking for Docker..."
@@ -115,5 +68,4 @@ check_docker() {
115
68
  # Run all checks
116
69
  echo "🔍 Checking and installing required dependencies..."
117
70
  check_acl
118
- check_curl
119
71
  check_docker
@@ -1,5 +1,56 @@
1
1
  {
2
2
  "commands": {
3
+ "clean": {
4
+ "aliases": [],
5
+ "args": {
6
+ "layer": {
7
+ "description": "network layer to clean. e.g. gl0",
8
+ "name": "layer",
9
+ "required": true
10
+ }
11
+ },
12
+ "description": "Remove data and/or logs from a validator node",
13
+ "examples": [
14
+ "<%= config.bin %> <%= command.id %>"
15
+ ],
16
+ "flags": {
17
+ "all": {
18
+ "char": "a",
19
+ "description": "remove all data and logs",
20
+ "name": "all",
21
+ "allowNo": false,
22
+ "type": "boolean"
23
+ },
24
+ "data": {
25
+ "char": "d",
26
+ "description": "remove data",
27
+ "name": "data",
28
+ "allowNo": false,
29
+ "type": "boolean"
30
+ },
31
+ "logs": {
32
+ "char": "l",
33
+ "description": "remove logs",
34
+ "name": "logs",
35
+ "allowNo": false,
36
+ "type": "boolean"
37
+ }
38
+ },
39
+ "hasDynamicHelp": false,
40
+ "hiddenAliases": [],
41
+ "id": "clean",
42
+ "pluginAlias": "@constellation-network/node-pilot",
43
+ "pluginName": "@constellation-network/node-pilot",
44
+ "pluginType": "core",
45
+ "strict": true,
46
+ "enableJsonFlag": false,
47
+ "isESM": true,
48
+ "relativePath": [
49
+ "dist",
50
+ "commands",
51
+ "clean.js"
52
+ ]
53
+ },
3
54
  "config": {
4
55
  "aliases": [],
5
56
  "args": {},
@@ -151,7 +202,16 @@
151
202
  "examples": [
152
203
  "<%= config.bin %> <%= command.id %>"
153
204
  ],
154
- "flags": {},
205
+ "flags": {
206
+ "layer": {
207
+ "char": "l",
208
+ "description": "specify a layer to shutdown. e.g. gl0",
209
+ "name": "layer",
210
+ "hasDynamicHelp": false,
211
+ "multiple": false,
212
+ "type": "option"
213
+ }
214
+ },
155
215
  "hasDynamicHelp": false,
156
216
  "hiddenAliases": [],
157
217
  "id": "shutdown",
@@ -276,5 +336,5 @@
276
336
  ]
277
337
  }
278
338
  },
279
- "version": "0.0.17"
339
+ "version": "0.0.19"
280
340
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@constellation-network/node-pilot",
3
3
  "description": "An easy deployment and monitoring tool for Constellation nodes.",
4
- "version": "0.0.17",
4
+ "version": "0.0.19",
5
5
  "author": "Frank Fox",
6
6
  "bin": {
7
7
  "cpilot": "bin/run.js"
File without changes