go-dev 0.1.1 → 0.2.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.
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/package.json +2 -2
- package/src/config.js +28 -20
- package/src/process-manager.js +62 -1
- package/src/services/cmd.js +43 -27
- package/src/services/docker.js +2 -10
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Giuliano Collacchioni
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# `go-dev`
|
|
2
|
+
|
|
3
|
+
A simple and robust orchestrator for streamlining local development environments in monorepos.
|
|
4
|
+
|
|
5
|
+
**`npx go-dev <preset>` – Go develop!**
|
|
6
|
+
|
|
7
|
+
## 🚀 Introduction
|
|
8
|
+
|
|
9
|
+
In complex monorepos, starting your development environment can be a chore. You might need to spin up Docker containers, run multiple Node.js (or other language) development servers, handle pre-builds, and manage inter-service dependencies. `go-dev` simplifies this by allowing you to define your entire local development stack in a single YAML configuration file.
|
|
10
|
+
|
|
11
|
+
`go-dev` acts as a central command to bring up your `api`, `frontend`, `database`, and any other microservices, ensuring they start in the correct order, with the right modes, and provide clear, prefixed logs.
|
|
12
|
+
|
|
13
|
+
## ✨ Features
|
|
14
|
+
|
|
15
|
+
* **Unified Configuration:** Define all your services, their modes (e.g., `dev`, `docker`, `serve`), and dependencies in a single `go-dev.yml` file.
|
|
16
|
+
* **Service Types:**
|
|
17
|
+
* **`cmd` services:** Run any command-line process (e.g., `npm run dev`, `rollup -w`, `python app.py`). Supports `preCommands` for setup tasks like builds.
|
|
18
|
+
* **`docker` services:** Manage Docker containers via `docker compose`. Automatically checks container status and performs health checks.
|
|
19
|
+
* **Mode-Aware Dependencies:** Services can depend on other services running in specific modes (e.g., your `api` dev mode might depend on `frontend` in `serve` mode).
|
|
20
|
+
* **Preset-Driven Startup:** Define different "presets" (e.g., `api`, `frontend`, `all`) to easily spin up specific combinations of services tailored to your current development focus.
|
|
21
|
+
* **Automatic Dependency Resolution:** `go-dev` builds an intelligent execution graph, starting services in the correct topological order.
|
|
22
|
+
* **Centralized Logging:** Prefixes logs from each service, making it easy to follow activity from multiple concurrent processes.
|
|
23
|
+
* **Robust Cleanup:** Handles graceful shutdown of all started processes and Docker services on exit (e.g., via `Ctrl+C`).
|
|
24
|
+
|
|
25
|
+
## 🤔 Why `go-dev`?
|
|
26
|
+
|
|
27
|
+
While tools like `concurrently` manage parallel processes and `docker compose` handles containers, `go-dev` fills a crucial gap by:
|
|
28
|
+
|
|
29
|
+
* **Integrating `cmd` and `docker` services seamlessly:** It bridges the world of host-based processes and containerized applications under one roof.
|
|
30
|
+
* **Providing intelligent mode-aware dependency resolution:** It understands that "frontend" might mean a different set of commands (and dependencies) when you're actively developing the frontend vs. when it's just a dependency for API development.
|
|
31
|
+
* **Offering a single, declarative interface for your entire dev stack:** No more remembering multiple `npm` scripts or `docker compose` commands.
|
|
32
|
+
|
|
33
|
+
## 📦 Installation
|
|
34
|
+
|
|
35
|
+
`go-dev` is distributed via npm and designed to be used with `npx`.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Install it as a devDependency in your monorepo's root
|
|
39
|
+
npm install --save-dev go-dev
|
|
40
|
+
# or
|
|
41
|
+
yarn add --dev go-dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 🚀 Usage
|
|
45
|
+
|
|
46
|
+
Once installed, simply run `go-dev` with the name of the preset you want to start:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx go-dev <preset_name> [config_path]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
* `<preset_name>`: The name of the preset defined in your `go-dev.yml` (e.g., `api`, `frontend`, `all`).
|
|
53
|
+
* `[config_path]`: (Optional) Path to your `go-dev.yml` file. Defaults to looking for `go-dev.yml`, `.go-dev.yml`, `go-dev.yaml`, or `.go-dev.yaml` in the current directory.
|
|
54
|
+
|
|
55
|
+
**Example:**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx go-dev api # Start the environment for API development
|
|
59
|
+
npx go-dev frontend # Start the environment for Frontend development
|
|
60
|
+
npx go-dev all # Start the full development environment
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Press `Ctrl+C` at any time to gracefully shut down all running services.
|
|
64
|
+
|
|
65
|
+
## ⚙️ Configuration (`go-dev.yml`)
|
|
66
|
+
|
|
67
|
+
Create a configuration file in your project's root.
|
|
68
|
+
|
|
69
|
+
By default, `go-dev` automatically detects its configuration file. It looks for files named `go-dev` (or `.go-dev` for a hidden file), optionally including `.config` before the `.yml` or `.yaml` extension. It will search for these files in the directory where you run `npx go-dev`.
|
|
70
|
+
|
|
71
|
+
Common examples include: `go-dev.yml`, `.go-dev.yml`, and `go-dev.config.yaml`.
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
# go-dev.yml
|
|
75
|
+
|
|
76
|
+
# Define your individual services here
|
|
77
|
+
services:
|
|
78
|
+
# Example: A Docker-based PostgreSQL database
|
|
79
|
+
postgres:
|
|
80
|
+
type: docker # This service runs inside Docker
|
|
81
|
+
service: postgres # Name of the service in your docker-compose.yml
|
|
82
|
+
composeFile: infrastructure/docker-compose.yml # Path to the docker-compose file
|
|
83
|
+
healthCheck: true # Enable health checks for this Docker service
|
|
84
|
+
|
|
85
|
+
# Example: Your API service, which can run in 'dev' (cmd) or 'docker' mode
|
|
86
|
+
api:
|
|
87
|
+
type: hybrid # This service has multiple modes
|
|
88
|
+
defaultMode: dev # Default mode if not specified by a preset or dependency
|
|
89
|
+
# Note: The name of the modes is totally arbirtary
|
|
90
|
+
modes:
|
|
91
|
+
# API in active development mode (runs directly on host)
|
|
92
|
+
dev:
|
|
93
|
+
type: cmd # This mode runs a command-line process
|
|
94
|
+
commands:
|
|
95
|
+
start:
|
|
96
|
+
command: [npx, rollup, -c, -w] # The primary command for development
|
|
97
|
+
directory: ./api # Directory to run the command from
|
|
98
|
+
dependencies: # What this mode depends on
|
|
99
|
+
- postgres # API dev needs PostgreSQL (will use postgres's default docker mode)
|
|
100
|
+
- { service: frontend, mode: serve } # API dev needs frontend running in its 'serve' mode
|
|
101
|
+
|
|
102
|
+
# API running as a Docker container (e.g., for frontend-only dev)
|
|
103
|
+
docker:
|
|
104
|
+
type: docker
|
|
105
|
+
service: api
|
|
106
|
+
composeFile: infrastructure/docker-compose.yml
|
|
107
|
+
healthCheck: true
|
|
108
|
+
dependencies:
|
|
109
|
+
- postgres # Docker API also needs PostgreSQL
|
|
110
|
+
|
|
111
|
+
# Example: Your Frontend service, which can run in 'dev' (cmd) or 'serve' (cmd) mode
|
|
112
|
+
frontend:
|
|
113
|
+
type: hybrid
|
|
114
|
+
defaultMode: dev
|
|
115
|
+
modes:
|
|
116
|
+
# Frontend in active development (watch mode)
|
|
117
|
+
dev:
|
|
118
|
+
type: cmd
|
|
119
|
+
commands:
|
|
120
|
+
start:
|
|
121
|
+
command: [npx, rollup, -c, -w]
|
|
122
|
+
directory: ./frontend
|
|
123
|
+
dependencies:
|
|
124
|
+
# Frontend dev needs API (will use api's default docker mode for this preset)
|
|
125
|
+
# Note: No direct circular dependency between dev modes.
|
|
126
|
+
# Dev modes often assume peers will eventually be ready.
|
|
127
|
+
- { service: api, mode: docker }
|
|
128
|
+
|
|
129
|
+
# Frontend serving its built assets (e.g., when API depends on it)
|
|
130
|
+
serve:
|
|
131
|
+
type: cmd
|
|
132
|
+
preCommands: # Commands to run and await completion BEFORE the main 'start' command
|
|
133
|
+
- [npm, --prefix, frontend, run, build] # Build frontend assets first
|
|
134
|
+
commands:
|
|
135
|
+
start:
|
|
136
|
+
command: [node, ./localserver.mjs] # Then start the local server
|
|
137
|
+
directory: ./frontend
|
|
138
|
+
dependencies:
|
|
139
|
+
- api # Frontend serve needs API (will use api's default dev mode for this preset)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Define different development presets (combinations of services and their modes)
|
|
143
|
+
presets:
|
|
144
|
+
# Preset: "api" development focus
|
|
145
|
+
# Starts API in dev mode, pulling in its dependencies (postgres, frontend:serve)
|
|
146
|
+
api:
|
|
147
|
+
services: [api] # Only explicitly list top-level services you want to run
|
|
148
|
+
modes:
|
|
149
|
+
# no explicit modes needed here, as defaultMode and dependency requests handle it
|
|
150
|
+
# api: dev # (already default)
|
|
151
|
+
# frontend: serve # (requested by api:dev)
|
|
152
|
+
# postgres: dev # (pulled by dependencies)
|
|
153
|
+
|
|
154
|
+
# Preset: "frontend" development focus
|
|
155
|
+
# Starts Frontend in dev mode, pulling in its dependencies (api:docker, postgres)
|
|
156
|
+
frontend:
|
|
157
|
+
services: [frontend]
|
|
158
|
+
modes:
|
|
159
|
+
# frontend: dev # (already default)
|
|
160
|
+
# api: docker # (requested by frontend:dev)
|
|
161
|
+
# postgres: dev # (pulled by dependencies)
|
|
162
|
+
|
|
163
|
+
# Preset: "all" development (both API and Frontend in dev mode concurrently)
|
|
164
|
+
all:
|
|
165
|
+
services: [api, frontend] # Explicitly list both as top-level focus
|
|
166
|
+
modes:
|
|
167
|
+
# api: dev # (already default)
|
|
168
|
+
# frontend: dev # (already default)
|
|
169
|
+
# No need to specify modes if they match the defaultMode
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 🤝 Contributing
|
|
173
|
+
|
|
174
|
+
Contributions are welcome! Feel free to open issues or submit pull requests.
|
|
175
|
+
|
|
176
|
+
## 📄 License
|
|
177
|
+
|
|
178
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
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(),
|
|
@@ -68,22 +72,26 @@ const configSchema = Joi.object({
|
|
|
68
72
|
).default({})
|
|
69
73
|
});
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
const possibleNames = [
|
|
76
|
+
'go-dev'
|
|
77
|
+
].flatMap(baseName => {
|
|
78
|
+
return [
|
|
79
|
+
baseName,
|
|
80
|
+
`.${baseName}`
|
|
81
|
+
];
|
|
82
|
+
}).flatMap(baseName => {
|
|
83
|
+
return [
|
|
84
|
+
baseName,
|
|
85
|
+
`${baseName}.config`
|
|
86
|
+
];
|
|
87
|
+
}).flatMap(baseName => {
|
|
88
|
+
return [
|
|
89
|
+
`${baseName}.yml`,
|
|
90
|
+
`${baseName}.yaml`
|
|
91
|
+
];
|
|
92
|
+
});
|
|
86
93
|
|
|
94
|
+
function findConfigFile() {
|
|
87
95
|
for (const name of possibleNames) {
|
|
88
96
|
if (fs.existsSync(name)) {
|
|
89
97
|
return name;
|
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,7 +121,9 @@ 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);
|
|
126
|
+
appendFileSync('./banana.log', prefixedData);
|
|
124
127
|
});
|
|
125
128
|
proc.stderr.on('data', (data) => {
|
|
126
129
|
let prefixedData = data.toString().split('\n').map(line => `${prefix} ${line}`).join('\n');
|
|
@@ -130,7 +133,9 @@ class ProcessManager {
|
|
|
130
133
|
if (!prefixedData.endsWith('\n')) {
|
|
131
134
|
prefixedData = prefixedData + '\n';
|
|
132
135
|
}
|
|
136
|
+
prefixedData = prefixedData.replace(/\x1b\[(?:2J|H)/gi, '');
|
|
133
137
|
process.stderr.write(prefixedData);
|
|
138
|
+
appendFileSync('./banana.log', prefixedData);
|
|
134
139
|
});
|
|
135
140
|
|
|
136
141
|
this.managedProcesses.add(proc);
|
|
@@ -143,6 +148,10 @@ class ProcessManager {
|
|
|
143
148
|
});
|
|
144
149
|
|
|
145
150
|
proc.on('exit', (code, signal) => {
|
|
151
|
+
if (!this.managedProcesses.has(proc)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
146
155
|
this.managedProcesses.delete(proc);
|
|
147
156
|
if (this.cleanupInProgress) {
|
|
148
157
|
console.log(
|
|
@@ -171,6 +180,47 @@ class ProcessManager {
|
|
|
171
180
|
return proc;
|
|
172
181
|
}
|
|
173
182
|
|
|
183
|
+
killProcess(process) {
|
|
184
|
+
this.managedProcesses.delete(process);
|
|
185
|
+
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
let exited = false;
|
|
188
|
+
const onExit = () => {
|
|
189
|
+
exited = true;
|
|
190
|
+
resolve();
|
|
191
|
+
};
|
|
192
|
+
process.on('exit', onExit);
|
|
193
|
+
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
if (exited) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.error(`[ProcessManager] Timeout reached for process interruption ${process.pid}`);
|
|
200
|
+
try {
|
|
201
|
+
process.kill('SIGKILL');
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.error(`[ProcessManager] Error force killing process ${process.pid}: ${e.message}`);
|
|
204
|
+
reject(e);
|
|
205
|
+
}
|
|
206
|
+
}, 500);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
process.kill('SIGTERM');
|
|
210
|
+
return;
|
|
211
|
+
} catch (e) {
|
|
212
|
+
console.error(`[ProcessManager] Error signaling process ${process.pid}: ${e.message}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
process.kill('SIGKILL');
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.error(`[ProcessManager] Error force killing process ${process.pid}: ${e.message}`);
|
|
219
|
+
reject(e);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
174
224
|
/**
|
|
175
225
|
* Kills all currently managed child processes.
|
|
176
226
|
*/
|
|
@@ -179,11 +229,22 @@ class ProcessManager {
|
|
|
179
229
|
console.log('[ProcessManager] Cleanup of managed processes already in progress, skipping.');
|
|
180
230
|
return;
|
|
181
231
|
}
|
|
232
|
+
for (let index = this.managedProcesses.length - 1; index >= 0; index--) {
|
|
233
|
+
const process = this.managedProcesses[index];
|
|
234
|
+
if (process.killed) {
|
|
235
|
+
this.managedProcesses.splice(index, 1);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
182
239
|
this.cleanupInProgress = true;
|
|
183
240
|
|
|
184
241
|
console.log('\n[ProcessManager] Initiating cleanup of managed processes...');
|
|
185
242
|
|
|
186
243
|
for (const proc of [...this.managedProcesses]) {
|
|
244
|
+
if (proc.killed) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
187
248
|
console.log(`[ProcessManager] Killing managed process PID: ${proc.pid}`);
|
|
188
249
|
try {
|
|
189
250
|
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
|
}
|