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 +1 -1
- package/src/config.js +9 -5
- package/src/process-manager.js +60 -1
- package/src/services/cmd.js +43 -27
- package/src/services/docker.js +2 -10
- /package/{README → README.md} +0 -0
package/package.json
CHANGED
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
|
-
|
|
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
|
-
|
|
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(),
|
package/src/process-manager.js
CHANGED
|
@@ -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');
|
package/src/services/cmd.js
CHANGED
|
@@ -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.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
cmdArgs
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
70
|
-
console.log(`[${this.name}:${this.mode}] Stopping
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
package/src/services/docker.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
}
|
/package/{README → README.md}
RENAMED
|
File without changes
|