go-dev 0.1.2 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "go-dev",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "main": "src/index.js",
5
5
  "bin": {
6
6
  "go-dev": "bin/go-dev"
package/src/config.js CHANGED
@@ -12,12 +12,13 @@ const dependencyEntrySchema = Joi.alternatives().try(
12
12
  );
13
13
 
14
14
  const commandSchema = Joi.array().items(Joi.string().min(1)).min(1);
15
+ const commandObjectSchema = Joi.object({
16
+ command: commandSchema,
17
+ directory: Joi.string().min(1).optional(),
18
+ })
15
19
  const commandConfigSchema = Joi.alternatives().try(
16
20
  commandSchema,
17
- Joi.object({
18
- command: commandSchema,
19
- directory: Joi.string().min(1),
20
- })
21
+ commandObjectSchema
21
22
  );
22
23
 
23
24
  const cmdServiceConfigSchema = Joi.object({
@@ -25,7 +26,10 @@ const cmdServiceConfigSchema = Joi.object({
25
26
  preCommands: Joi.array().items(commandConfigSchema).default([]),
26
27
  commands: Joi.object().pattern(
27
28
  Joi.string(),
28
- commandConfigSchema
29
+ Joi.alternatives().try(
30
+ commandConfigSchema,
31
+ Joi.array().items(commandObjectSchema).min(1)
32
+ )
29
33
  ).min(1).required(),
30
34
  defaultCommand: Joi.string().default('start'),
31
35
  directory: Joi.string(),
@@ -1,5 +1,6 @@
1
- // src/process-manager.js
2
1
  const { spawn, spawnSync } = require('child_process');
2
+ const { appendFileSync } = require('fs');
3
+
3
4
 
4
5
  class ProcessManager {
5
6
  constructor() {
@@ -120,6 +121,7 @@ class ProcessManager {
120
121
  if (!prefixedData.endsWith('\n')) {
121
122
  prefixedData = prefixedData + '\n';
122
123
  }
124
+ prefixedData = prefixedData.replace(/\x1b\[(?:2J|3J|H)/gi, '');
123
125
  process.stdout.write(prefixedData);
124
126
  });
125
127
  proc.stderr.on('data', (data) => {
@@ -130,6 +132,7 @@ class ProcessManager {
130
132
  if (!prefixedData.endsWith('\n')) {
131
133
  prefixedData = prefixedData + '\n';
132
134
  }
135
+ prefixedData = prefixedData.replace(/\x1b\[(?:2J|H)/gi, '');
133
136
  process.stderr.write(prefixedData);
134
137
  });
135
138
 
@@ -143,6 +146,10 @@ class ProcessManager {
143
146
  });
144
147
 
145
148
  proc.on('exit', (code, signal) => {
149
+ if (!this.managedProcesses.has(proc)) {
150
+ return;
151
+ }
152
+
146
153
  this.managedProcesses.delete(proc);
147
154
  if (this.cleanupInProgress) {
148
155
  console.log(
@@ -171,6 +178,47 @@ class ProcessManager {
171
178
  return proc;
172
179
  }
173
180
 
181
+ killProcess(process) {
182
+ this.managedProcesses.delete(process);
183
+
184
+ return new Promise((resolve, reject) => {
185
+ let exited = false;
186
+ const onExit = () => {
187
+ exited = true;
188
+ resolve();
189
+ };
190
+ process.on('exit', onExit);
191
+
192
+ setTimeout(() => {
193
+ if (exited) {
194
+ return;
195
+ }
196
+
197
+ console.error(`[ProcessManager] Timeout reached for process interruption ${process.pid}`);
198
+ try {
199
+ process.kill('SIGKILL');
200
+ } catch (e) {
201
+ console.error(`[ProcessManager] Error force killing process ${process.pid}: ${e.message}`);
202
+ reject(e);
203
+ }
204
+ }, 500);
205
+
206
+ try {
207
+ process.kill('SIGTERM');
208
+ return;
209
+ } catch (e) {
210
+ console.error(`[ProcessManager] Error signaling process ${process.pid}: ${e.message}`);
211
+ }
212
+
213
+ try {
214
+ process.kill('SIGKILL');
215
+ } catch (e) {
216
+ console.error(`[ProcessManager] Error force killing process ${process.pid}: ${e.message}`);
217
+ reject(e);
218
+ }
219
+ });
220
+ }
221
+
174
222
  /**
175
223
  * Kills all currently managed child processes.
176
224
  */
@@ -179,11 +227,22 @@ class ProcessManager {
179
227
  console.log('[ProcessManager] Cleanup of managed processes already in progress, skipping.');
180
228
  return;
181
229
  }
230
+ for (let index = this.managedProcesses.length - 1; index >= 0; index--) {
231
+ const process = this.managedProcesses[index];
232
+ if (process.killed) {
233
+ this.managedProcesses.splice(index, 1);
234
+ }
235
+ }
236
+
182
237
  this.cleanupInProgress = true;
183
238
 
184
239
  console.log('\n[ProcessManager] Initiating cleanup of managed processes...');
185
240
 
186
241
  for (const proc of [...this.managedProcesses]) {
242
+ if (proc.killed) {
243
+ continue;
244
+ }
245
+
187
246
  console.log(`[ProcessManager] Killing managed process PID: ${proc.pid}`);
188
247
  try {
189
248
  proc.kill('SIGTERM');
@@ -4,7 +4,7 @@ class CmdService extends BaseService {
4
4
  constructor(name, mode, config) {
5
5
  super(name, mode, config);
6
6
  this.prefix = `${name}:${mode}:`;
7
- this.process = null;
7
+ this.processes = [];
8
8
  }
9
9
 
10
10
  async start() {
@@ -17,7 +17,7 @@ class CmdService extends BaseService {
17
17
  for (const command of preCommands) {
18
18
  const { cmdArgs, directory } = (Array.isArray(command) ?
19
19
  { cmdArgs: command } :
20
- { cmdArgs: command.command, cmdArgs: command.directory }
20
+ { cmdArgs: command.command, directory: command.directory }
21
21
  );
22
22
  try {
23
23
  CmdService._processManager.runSync(cmdArgs[0], cmdArgs.slice(1), {
@@ -25,6 +25,7 @@ class CmdService extends BaseService {
25
25
  stdio: 'inherit',
26
26
  });
27
27
  } catch (error) {
28
+ console.log({ cmdArgs });
28
29
  throw new Error(
29
30
  `[${this.name}:${this.mode}] Pre-command failed: ${cmdArgs.join(
30
31
  ' ',
@@ -42,39 +43,54 @@ class CmdService extends BaseService {
42
43
  );
43
44
  }
44
45
 
45
- const { cmdArgs, directory } = (Array.isArray(mainCommand) ?
46
- { cmdArgs: mainCommand } :
47
- { cmdArgs: mainCommand.command, directory: mainCommand.directory }
46
+ const { cmdArgs, directory } = (Array.isArray(mainCommand) && typeof mainCommand[0] === 'string' ?
47
+ { cmdArgs: [mainCommand], directory: [undefined] } :
48
+ (Array.isArray(mainCommand) ?
49
+ {
50
+ cmdArgs: mainCommand.map(({ command }) => command),
51
+ directory: mainCommand.map(({ directory }) => directory),
52
+ } :
53
+ { cmdArgs: [mainCommand.command], directory: [mainCommand.directory] }
54
+ )
48
55
  );
49
56
 
50
- this.process = CmdService._processManager.startManagedProcess(
51
- cmdArgs[0],
52
- cmdArgs.slice(1),
53
- { cwd: directory },
54
- this.prefix,
55
- true,
56
- );
57
+ const useProcessIndex = cmdArgs.length > 1;
58
+ for (let index = 0; index < cmdArgs.length; index++) {
59
+ const command = cmdArgs[index];
60
+ const cwd = directory[index];
57
61
 
58
- if (!this.process) {
59
- throw new Error(
60
- `[${this.name}:${this.mode}] Failed to spawn main process: ${cmdArgs.join(' ')}`,
62
+ const process = CmdService._processManager.startManagedProcess(
63
+ command[0],
64
+ command.slice(1),
65
+ { cwd },
66
+ (useProcessIndex ?
67
+ `${this.prefix}${index}:` :
68
+ this.prefix
69
+ ),
70
+ true,
71
+ );
72
+
73
+ if (!process) {
74
+ throw new Error(
75
+ `[${this.name}:${this.mode}] Failed to spawn process: ${command.join(' ')}`,
76
+ );
77
+ }
78
+
79
+ this.processes.push(process);
80
+ console.log(
81
+ `[${this.name}:${this.mode}] Process started (PID: ${process.pid}).`,
61
82
  );
62
83
  }
63
- console.log(
64
- `[${this.name}:${this.mode}] Main process started (PID: ${this.process.pid}).`,
65
- );
66
84
  }
67
85
 
68
86
  async stop() {
69
- if (this.process) {
70
- console.log(`[${this.name}:${this.mode}] Stopping main process (PID: ${this.process.pid}).`);
71
- try {
72
- this.process.kill('SIGTERM');
73
- } catch (e) {
74
- console.error(`[${this.name}:${this.mode}] Error signaling process ${this.process.pid}: ${e.message}`);
75
- }
76
- this.process = null;
77
- }
87
+ const promises = this.processes.map(process => {
88
+ console.log(`[${this.name}:${this.mode}] Stopping process (PID: ${process.pid}).`);
89
+ return CmdService._processManager.killProcess(process);
90
+ });
91
+ this.processes.splice(0, this.processes.length);
92
+
93
+ await Promise.all(promises);
78
94
  }
79
95
  }
80
96
 
@@ -80,7 +80,6 @@ class DockerService extends BaseService {
80
80
  }
81
81
 
82
82
  async stop() {
83
- // The static cleanup method will handle the actual docker compose stop.
84
83
  console.log(`[${this.name}:${this.mode}] Relying on orchestrator's static docker compose stop for '${this.dockerServiceName}'.`);
85
84
  this.containerName = null;
86
85
  }
@@ -94,7 +93,6 @@ class DockerService extends BaseService {
94
93
  return this.containerName;
95
94
  }
96
95
  try {
97
- // Access processManager statically
98
96
  const name = DockerService._processManager.runSync(
99
97
  'docker',
100
98
  ['compose', '-f', this.dockerComposeFile, 'ps', '-a', '-q', this.dockerServiceName],
@@ -115,7 +113,6 @@ class DockerService extends BaseService {
115
113
  return null;
116
114
  }
117
115
  try {
118
- // Access processManager statically
119
116
  return DockerService._processManager.runSync(
120
117
  'docker',
121
118
  ['container', 'inspect', '-f', '{{.State.Status}}', containerName],
@@ -132,15 +129,13 @@ class DockerService extends BaseService {
132
129
  throw new Error(`[${this.name}:${this.mode}] Cannot check health: Container for '${this.dockerServiceName}' not found.`);
133
130
  }
134
131
 
135
- process.stdout.write(`[${this.name}:${this.mode}] Checking healthiness for '${containerName}'`);
132
+ console.log(`[${this.name}:${this.mode}] Checking healthiness for '${containerName}'`);
136
133
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
137
134
  if (attempt > 1) {
138
- process.stdout.write(".");
139
135
  await new Promise(resolve => setTimeout(resolve, delayMs));
140
136
  }
141
137
 
142
138
  try {
143
- // Access processManager statically
144
139
  const healthStatus = DockerService._processManager.runSync(
145
140
  'docker',
146
141
  ['container', 'inspect', '-f', '{{.State.Health.Status}}', containerName],
@@ -148,7 +143,7 @@ class DockerService extends BaseService {
148
143
  );
149
144
 
150
145
  if (healthStatus === 'healthy' || healthStatus === 'none') {
151
- process.stdout.write(" Healthy!\n");
146
+ console.log(`[${this.name}:${this.mode}] Container '${containerName}' healty!\n`);
152
147
  return true;
153
148
  }
154
149
 
@@ -156,15 +151,12 @@ class DockerService extends BaseService {
156
151
  continue;
157
152
  }
158
153
 
159
- process.stdout.write(` Status: ${healthStatus}\n`);
160
154
  throw new Error(`[${this.name}:${this.mode}] Container '${containerName}' is in unexpected health state: ${healthStatus}`);
161
155
  } catch (error) {
162
- process.stdout.write(` Error: ${error.message}\n`);
163
156
  throw new Error(`[${this.name}:${this.mode}] Failed to check health for '${containerName}': ${error.message}`);
164
157
  }
165
158
  }
166
159
 
167
- process.stdout.write("\n");
168
160
  throw new Error(`[${this.name}:${this.mode}] Service '${this.dockerServiceName}' wasn't healthy in time after ${maxAttempts} attempts.`);
169
161
  }
170
162
  }
File without changes