go-dev 0.3.0 → 0.3.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/README.md +45 -8
- package/bin/go-dev +2 -3
- package/package.json +1 -1
- package/src/config.js +4 -0
- package/src/orchestrator.js +58 -1
- package/src/process-manager.js +2 -0
- package/src/services/base.js +2 -1
- package/src/services/cmd.js +24 -10
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ In complex monorepos, starting your development environment can be a chore. You
|
|
|
14
14
|
|
|
15
15
|
* **Unified Configuration:** Define all your services, their modes (e.g., `dev`, `docker`, `serve`), and dependencies in a single `go-dev.yml` file.
|
|
16
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.
|
|
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. Commands can be defined in multiple flexible ways to run single or multiple processes in parallel for a service.
|
|
18
18
|
* **`docker` services:** Manage Docker containers via `docker compose`. Automatically checks container status and performs health checks.
|
|
19
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
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.
|
|
@@ -54,15 +54,35 @@ npx go-dev <preset_name> [config_path]
|
|
|
54
54
|
* `<preset_name>`: The name of the preset defined in your `go-dev.yml` (e.g., `api`, `frontend`, `all`).
|
|
55
55
|
* `[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.
|
|
56
56
|
|
|
57
|
-
**
|
|
57
|
+
**Passing Arguments to Service Commands:**
|
|
58
|
+
|
|
59
|
+
To pass additional arguments from the command line to a specific service command, use a keyword flag followed by the target and its arguments.
|
|
60
|
+
|
|
61
|
+
By default, the keyword flag is `--args-for`. This can be customized in your `go-dev.yml` using the `serviceArgsKeyword` option (e.g., `serviceArgsKeyword: pass-to`).
|
|
62
|
+
|
|
63
|
+
The target for arguments is specified as `<service_name>[:<command_index>]`:
|
|
64
|
+
|
|
65
|
+
Specify the target for arguments as `<service_name>:<command_index>` (e.g., `api:0`, `frontend:1`). The `command_index` is 0-based and refers to the position of the command within a service's `commands` array. If the `:<command_index>` part is omitted (e.g., just `<service_name>`), arguments are passed to the **first command (index `0`)** defined for that service.
|
|
66
|
+
|
|
67
|
+
You can combine multiple keyword flag blocks for different services or specific commands.
|
|
58
68
|
|
|
59
69
|
```bash
|
|
60
|
-
npx go-dev
|
|
61
|
-
npx go-dev frontend # Start the environment for Frontend development
|
|
62
|
-
npx go-dev all # Start the full development environment
|
|
70
|
+
npx go-dev <preset_name> [--<serviceArgsKeyword> <service_name>[:<command_index>] [args...] ] [...]
|
|
63
71
|
```
|
|
64
72
|
|
|
65
|
-
|
|
73
|
+
Example:
|
|
74
|
+
|
|
75
|
+
Consider an `api` service with two parallel commands: `api:0` (main server) and `api:1` (TypeScript compiler watch).
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx go-dev all \
|
|
79
|
+
--args-for api:0 --host 0.0.0.0 --port 8081 \
|
|
80
|
+
--args-for api:1 --pretty --diagnostics \
|
|
81
|
+
--args-for frontend --log-level verbose
|
|
82
|
+
```
|
|
83
|
+
*In the example above, `--args-for frontend` is equivalent to `--args-for frontend:0`.*
|
|
84
|
+
|
|
85
|
+
Press `Ctrl+C` at any time to gracefully shut down all running services. `go-dev` will also automatically exit once all primary services (those directly listed in your chosen preset) have completed their execution cleanly.
|
|
66
86
|
|
|
67
87
|
## ⚙️ Configuration (`go-dev.yml`)
|
|
68
88
|
|
|
@@ -75,6 +95,10 @@ Common examples include: `go-dev.yml`, `.go-dev.yml`, and `go-dev.config.yaml`.
|
|
|
75
95
|
```yaml
|
|
76
96
|
# go-dev.yml
|
|
77
97
|
|
|
98
|
+
# Customize the keyword used to pass arguments to service commands from the CLI.
|
|
99
|
+
# Change this if 'args-for' conflicts with a command your services use.
|
|
100
|
+
serviceArgsKeyword: args-for # Default value
|
|
101
|
+
|
|
78
102
|
# Define your individual services here
|
|
79
103
|
services:
|
|
80
104
|
# Example: A Docker-based PostgreSQL database
|
|
@@ -93,9 +117,22 @@ services:
|
|
|
93
117
|
# API in active development mode (runs directly on host)
|
|
94
118
|
dev:
|
|
95
119
|
type: cmd # This mode runs a command-line process
|
|
120
|
+
# The 'commands' property can be specified in three ways:
|
|
121
|
+
# 1. As a simple array of strings (for a single command without extra options)
|
|
122
|
+
# commands: [npx, rollup, -c, -w]
|
|
123
|
+
# 2. As a single command object (for one command with options like 'directory' or 'restartOnError')
|
|
124
|
+
# commands:
|
|
125
|
+
# command: [npx, rollup, -c, -w]
|
|
126
|
+
# directory: ./api
|
|
127
|
+
# restartOnError: true # Default is true for 'cmd' services
|
|
128
|
+
# 3. As an array of command objects (for multiple parallel commands)
|
|
96
129
|
commands:
|
|
97
|
-
command: [npx, rollup, -c, -w] # The primary command for development
|
|
98
|
-
|
|
130
|
+
- command: [npx, rollup, -c, -w] # The primary command for development (index 0)
|
|
131
|
+
directory: ./api # Directory to run the command from
|
|
132
|
+
# restartOnError: true # (Optional, defaults to true)
|
|
133
|
+
# Example of a second parallel command for API (index 1)
|
|
134
|
+
# - command: [npx, tsc, --watch]
|
|
135
|
+
# directory: ./api
|
|
99
136
|
dependencies: # What this mode depends on
|
|
100
137
|
- postgres # API dev needs PostgreSQL (will use postgres's default docker mode)
|
|
101
138
|
- { service: frontend, mode: serve } # API dev needs frontend running in its 'serve' mode
|
package/bin/go-dev
CHANGED
|
@@ -4,13 +4,12 @@ const Orchestrator = require('../src/orchestrator');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
6
|
const presetName = process.argv[2];
|
|
7
|
-
const configPath = process.argv[3];
|
|
8
7
|
|
|
9
8
|
if (!presetName) {
|
|
10
|
-
console.error('Error: Please specify a preset to run. Usage: dev-orchestrator <preset_name>
|
|
9
|
+
console.error('Error: Please specify a preset to run. Usage: dev-orchestrator <preset_name>');
|
|
11
10
|
process.exit(1);
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
const orchestrator = new Orchestrator(
|
|
13
|
+
const orchestrator = new Orchestrator();
|
|
15
14
|
|
|
16
15
|
orchestrator.start(presetName);
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -15,6 +15,7 @@ const commandSchema = Joi.array().items(Joi.string().min(1)).min(1);
|
|
|
15
15
|
const commandObjectSchema = Joi.object({
|
|
16
16
|
command: commandSchema,
|
|
17
17
|
directory: Joi.string().min(1).optional(),
|
|
18
|
+
restartOnError: Joi.boolean().optional(),
|
|
18
19
|
})
|
|
19
20
|
const commandConfigSchema = Joi.alternatives().try(
|
|
20
21
|
commandSchema,
|
|
@@ -59,6 +60,7 @@ const serviceSchema = Joi.alternatives().try(
|
|
|
59
60
|
);
|
|
60
61
|
|
|
61
62
|
const configSchema = Joi.object({
|
|
63
|
+
serviceArgsKeyword: Joi.string().min(1).optional(),
|
|
62
64
|
services: Joi.object().pattern(Joi.string(), serviceSchema).required(),
|
|
63
65
|
presets: Joi.object().pattern(
|
|
64
66
|
Joi.string(),
|
|
@@ -109,6 +111,8 @@ function loadConfig(configPath) {
|
|
|
109
111
|
|
|
110
112
|
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
111
113
|
const config = yaml.load(configContent);
|
|
114
|
+
|
|
115
|
+
console.log(config.services.api.modes);
|
|
112
116
|
|
|
113
117
|
const { error, value } = configSchema.validate(config);
|
|
114
118
|
|
package/src/orchestrator.js
CHANGED
|
@@ -33,6 +33,63 @@ class Orchestrator {
|
|
|
33
33
|
console.log('--- Resolved Primary Services to Run ---');
|
|
34
34
|
primaryServices.forEach(s => console.log(` - ${s.name} (mode: ${s.mode})`));
|
|
35
35
|
|
|
36
|
+
const extraArgs = new Map();
|
|
37
|
+
{
|
|
38
|
+
const argsToParse = process.argv.slice(3);
|
|
39
|
+
if (argsToParse.length > 0) {
|
|
40
|
+
console.log(`--- Gathering args to pass to services ---`);
|
|
41
|
+
const serviceArgsKeyword = `--${this.config.serviceArgsKeyword ?? 'args-for'}`;
|
|
42
|
+
let isGettingService = false;
|
|
43
|
+
let currentService = null;
|
|
44
|
+
let currentIndex = 0;
|
|
45
|
+
let argsToPass = null;
|
|
46
|
+
for (const arg of argsToParse) {
|
|
47
|
+
if (arg === serviceArgsKeyword) {
|
|
48
|
+
isGettingService = true;
|
|
49
|
+
currentService = null;
|
|
50
|
+
currentIndex = 0;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (currentService == null) {
|
|
54
|
+
if (isGettingService === false) {
|
|
55
|
+
throw new Error(`Invalid arguments, use format: npx go-dev ${presetName} ${serviceArgsKeyword} <service> <args>`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const splitArg = arg.split(':');
|
|
59
|
+
if (splitArg.length > 2) {
|
|
60
|
+
throw new Error(`Invalid service name + index '${arg}': should be <service> or <service>:<command_index>`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const index = splitArg.length > 1 ? parseInt(splitArg[1]) : 0;;
|
|
64
|
+
if (splitArg.length > 1 && `${index}` !== splitArg[1]) {
|
|
65
|
+
throw new Error(`Invalid service name + index '${arg}': should be <service> or <service>:<command_index>`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
currentService = splitArg[0];
|
|
69
|
+
currentIndex = index;
|
|
70
|
+
argsToPass = extraArgs.get(currentService) ?? [];
|
|
71
|
+
extraArgs.set(currentService, argsToPass);
|
|
72
|
+
isGettingService = false;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let args = argsToPass[currentIndex];
|
|
77
|
+
if (args == null) {
|
|
78
|
+
args = [];
|
|
79
|
+
argsToPass[currentIndex] = args;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
args.push(arg);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const [service, indexedArgs] of extraArgs.entries()) {
|
|
86
|
+
indexedArgs.forEach((args, index) => {
|
|
87
|
+
console.log(`Extra args for service '${service}:${index}': [${args.join(', ')}]`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
36
93
|
console.log('\n--- Starting Dependencies ---');
|
|
37
94
|
for (const { name, mode, config } of dependencies) {
|
|
38
95
|
if (this.activeServiceInstances.has(name)) {
|
|
@@ -67,7 +124,7 @@ class Orchestrator {
|
|
|
67
124
|
}
|
|
68
125
|
|
|
69
126
|
this.cleanup();
|
|
70
|
-
});
|
|
127
|
+
}, extraArgs.get(name));
|
|
71
128
|
this.activeServiceInstances.set(name, serviceInstance);
|
|
72
129
|
activePrimaryServices.set(name, serviceInstance);
|
|
73
130
|
primaryServicePromises.push(serviceInstance.start());
|
package/src/process-manager.js
CHANGED
|
@@ -174,6 +174,8 @@ class ProcessManager {
|
|
|
174
174
|
`[ProcessManager] Restarting managed process '${command}'...`,
|
|
175
175
|
);
|
|
176
176
|
this.startManagedProcess(command, args, options, prefix, restartOnError, onExit, processReference);
|
|
177
|
+
} else {
|
|
178
|
+
onExit?.();
|
|
177
179
|
}
|
|
178
180
|
} else {
|
|
179
181
|
console.log(
|
package/src/services/base.js
CHANGED
|
@@ -28,11 +28,12 @@ class BaseService {
|
|
|
28
28
|
* @param {string} mode - The resolved mode for this service (e.g., 'dev', 'docker', 'serve').
|
|
29
29
|
* @param {object} config - The concrete configuration object for this service and mode.
|
|
30
30
|
*/
|
|
31
|
-
constructor(name, mode, config, onExit) {
|
|
31
|
+
constructor(name, mode, config, onExit, extraArgs) {
|
|
32
32
|
this.name = name;
|
|
33
33
|
this.mode = mode;
|
|
34
34
|
this.config = config;
|
|
35
35
|
this.onExit = onExit;
|
|
36
|
+
this.extraArgs = extraArgs;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
async start() {
|
package/src/services/cmd.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const { BaseService } = require('./base');
|
|
2
2
|
|
|
3
3
|
class CmdService extends BaseService {
|
|
4
|
-
constructor(name, mode, config, onExit) {
|
|
5
|
-
super(name, mode, config, onExit);
|
|
4
|
+
constructor(name, mode, config, onExit, extraArgs) {
|
|
5
|
+
super(name, mode, config, onExit, extraArgs);
|
|
6
6
|
this.prefix = `${name}:${mode}:`;
|
|
7
7
|
this.processes = [];
|
|
8
8
|
}
|
|
@@ -41,32 +41,46 @@ class CmdService extends BaseService {
|
|
|
41
41
|
console.log(`[${this.name}:${this.mode}] Pre-commands completed.`);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const { cmdArgs, directory } = (Array.isArray(commands) && typeof commands[0] === 'string' ?
|
|
45
|
-
{ cmdArgs: [commands], directory: [undefined] } :
|
|
44
|
+
const { cmdArgs, directory, restartOnError } = (Array.isArray(commands) && typeof commands[0] === 'string' ?
|
|
45
|
+
{ cmdArgs: [commands], directory: [undefined], restartOnError: [undefined] } :
|
|
46
46
|
(Array.isArray(commands) ?
|
|
47
47
|
{
|
|
48
48
|
cmdArgs: commands.map(({ command }) => command),
|
|
49
49
|
directory: commands.map(({ directory }) => directory),
|
|
50
|
+
restartOnError: commands.map(({ restartOnError }) => restartOnError),
|
|
50
51
|
} :
|
|
51
|
-
{
|
|
52
|
+
{
|
|
53
|
+
cmdArgs: [commands.command],
|
|
54
|
+
directory: [commands.directory],
|
|
55
|
+
restartOnError: [commands.restartOnError],
|
|
56
|
+
}
|
|
52
57
|
)
|
|
53
58
|
);
|
|
54
59
|
|
|
55
60
|
const useProcessIndex = cmdArgs.length > 1;
|
|
61
|
+
const exitedProcess = Array.from({ length: cmdArgs.length });
|
|
56
62
|
for (let index = 0; index < cmdArgs.length; index++) {
|
|
57
63
|
const command = cmdArgs[index];
|
|
58
|
-
|
|
64
|
+
|
|
65
|
+
const extraArgs = this.extraArgs?.[index] ?? [];
|
|
59
66
|
|
|
60
67
|
const process = CmdService._processManager.startManagedProcess(
|
|
61
68
|
command[0],
|
|
62
|
-
command.slice(1),
|
|
63
|
-
{ cwd },
|
|
69
|
+
command.slice(1).concat(extraArgs),
|
|
70
|
+
{ cwd: directory[index] },
|
|
64
71
|
(useProcessIndex ?
|
|
65
72
|
`${this.prefix}${index}:` :
|
|
66
73
|
this.prefix
|
|
67
74
|
),
|
|
68
|
-
|
|
69
|
-
() =>
|
|
75
|
+
restartOnError[index],
|
|
76
|
+
() => {
|
|
77
|
+
exitedProcess[index] = true;
|
|
78
|
+
if (exitedProcess.some(exited => !exited)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.onExit?.();
|
|
83
|
+
}
|
|
70
84
|
);
|
|
71
85
|
|
|
72
86
|
if (!process) {
|