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 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
- **Example:**
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 api # Start the environment for API development
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
- `go-dev` will automatically exit once all primary services (those directly listed in your chosen preset) have completed their execution cleanly. If you need to stop `go-dev` before all services complete, possibly because you're running a web service, press `Ctrl+C` at any time to gracefully shut down all running services.
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
- directory: ./api # Directory to run the command from
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> [config_path]');
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(configPath);
13
+ const orchestrator = new Orchestrator();
15
14
 
16
15
  orchestrator.start(presetName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "go-dev",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "main": "src/index.js",
5
5
  "bin": {
6
6
  "go-dev": "bin/go-dev"
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
 
@@ -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());
@@ -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(
@@ -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() {
@@ -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
- { cmdArgs: [commands.command], directory: [commands.directory] }
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
- const cwd = directory[index];
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
- true,
69
- () => this.onExit?.()
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) {