go-dev 0.2.0 → 0.3.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/README.md CHANGED
@@ -20,7 +20,9 @@ In complex monorepos, starting your development environment can be a chore. You
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.
21
21
  * **Automatic Dependency Resolution:** `go-dev` builds an intelligent execution graph, starting services in the correct topological order.
22
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`).
23
+ * **Automatic Process Exit:** The `go-dev` process will automatically exit when all primary services (those directly listed in the chosen preset) exit cleanly (with a success code of `0`).
24
+ * **Precise Docker Cleanup:** `go-dev` intelligently tracks which Docker Compose services it **actively started** (i.e., those that were not already running). During cleanup, it will only stop these specific services, leaving any pre-existing containers untouched.
25
+ * **Robust Cleanup:** Handles graceful shutdown of all started processes and Docker services on exit (e.g., via `Ctrl+C` or automatic exit).
24
26
 
25
27
  ## 🤔 Why `go-dev`?
26
28
 
@@ -60,7 +62,7 @@ npx go-dev frontend # Start the environment for Frontend development
60
62
  npx go-dev all # Start the full development environment
61
63
  ```
62
64
 
63
- Press `Ctrl+C` at any time to gracefully shut down all running services.
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.
64
66
 
65
67
  ## ⚙️ Configuration (`go-dev.yml`)
66
68
 
@@ -86,15 +88,14 @@ services:
86
88
  api:
87
89
  type: hybrid # This service has multiple modes
88
90
  defaultMode: dev # Default mode if not specified by a preset or dependency
89
- # Note: The name of the modes is totally arbirtary
91
+ # Note: The name of the modes is totally arbitrary
90
92
  modes:
91
93
  # API in active development mode (runs directly on host)
92
94
  dev:
93
95
  type: cmd # This mode runs a command-line process
94
96
  commands:
95
- start:
96
- command: [npx, rollup, -c, -w] # The primary command for development
97
- directory: ./api # Directory to run the command from
97
+ command: [npx, rollup, -c, -w] # The primary command for development
98
+ directory: ./api # Directory to run the command from
98
99
  dependencies: # What this mode depends on
99
100
  - postgres # API dev needs PostgreSQL (will use postgres's default docker mode)
100
101
  - { service: frontend, mode: serve } # API dev needs frontend running in its 'serve' mode
@@ -117,9 +118,8 @@ services:
117
118
  dev:
118
119
  type: cmd
119
120
  commands:
120
- start:
121
- command: [npx, rollup, -c, -w]
122
- directory: ./frontend
121
+ command: [npx, rollup, -c, -w]
122
+ directory: ./frontend
123
123
  dependencies:
124
124
  # Frontend dev needs API (will use api's default docker mode for this preset)
125
125
  # Note: No direct circular dependency between dev modes.
@@ -129,12 +129,11 @@ services:
129
129
  # Frontend serving its built assets (e.g., when API depends on it)
130
130
  serve:
131
131
  type: cmd
132
- preCommands: # Commands to run and await completion BEFORE the main 'start' command
132
+ preCommands: # Commands to run and await completion BEFORE the main command
133
133
  - [npm, --prefix, frontend, run, build] # Build frontend assets first
134
134
  commands:
135
- start:
136
- command: [node, ./localserver.mjs] # Then start the local server
137
- directory: ./frontend
135
+ command: [node, ./localserver.mjs] # Then start the local server
136
+ directory: ./frontend
138
137
  dependencies:
139
138
  - api # Frontend serve needs API (will use api's default dev mode for this preset)
140
139
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "go-dev",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "main": "src/index.js",
5
5
  "bin": {
6
6
  "go-dev": "bin/go-dev"
@@ -9,6 +9,10 @@
9
9
  "test": "echo \"Error: no test specified\" && exit 1"
10
10
  },
11
11
  "author": "Giuliano Collacchioni",
12
+ "repository": {
13
+ "type": "github",
14
+ "url": "https://github.com/Kal-Aster/go-dev"
15
+ },
12
16
  "license": "MIT",
13
17
  "description": "",
14
18
  "dependencies": {
package/src/config.js CHANGED
@@ -24,13 +24,10 @@ const commandConfigSchema = Joi.alternatives().try(
24
24
  const cmdServiceConfigSchema = Joi.object({
25
25
  type: Joi.string().valid('cmd').required(),
26
26
  preCommands: Joi.array().items(commandConfigSchema).default([]),
27
- commands: Joi.object().pattern(
28
- Joi.string(),
29
- Joi.alternatives().try(
30
- commandConfigSchema,
31
- Joi.array().items(commandObjectSchema).min(1)
32
- )
33
- ).min(1).required(),
27
+ commands: Joi.alternatives().try(
28
+ commandConfigSchema,
29
+ Joi.array().items(commandObjectSchema).min(1)
30
+ ),
34
31
  defaultCommand: Joi.string().default('start'),
35
32
  directory: Joi.string(),
36
33
  dependencies: Joi.array().items(dependencyEntrySchema).default([]),
@@ -43,12 +43,13 @@ class Orchestrator {
43
43
  if (!ServiceClass) {
44
44
  throw new Error(`Unknown service type '${config.type}' for service '${name}'.`);
45
45
  }
46
- const serviceInstance = new ServiceClass(name, mode, config);
46
+ const serviceInstance = new ServiceClass(name, mode, config, () => {});
47
47
  this.activeServiceInstances.set(name, serviceInstance);
48
48
  await serviceInstance.start();
49
49
  }
50
50
 
51
51
  console.log('\n--- Starting Primary Services ---');
52
+ const activePrimaryServices = new Map();
52
53
  const primaryServicePromises = [];
53
54
  for (const { name, mode, config } of primaryServices) {
54
55
  if (this.activeServiceInstances.has(name)) {
@@ -59,8 +60,16 @@ class Orchestrator {
59
60
  if (!ServiceClass) {
60
61
  throw new Error(`Unknown service type '${config.type}' for service '${name}'.`);
61
62
  }
62
- const serviceInstance = new ServiceClass(name, mode, config);
63
+ const serviceInstance = new ServiceClass(name, mode, config, () => {
64
+ activePrimaryServices.delete(name);
65
+ if (activePrimaryServices.size > 0) {
66
+ return;
67
+ }
68
+
69
+ this.cleanup();
70
+ });
63
71
  this.activeServiceInstances.set(name, serviceInstance);
72
+ activePrimaryServices.set(name, serviceInstance);
64
73
  primaryServicePromises.push(serviceInstance.start());
65
74
  }
66
75
  await Promise.all(primaryServicePromises);
@@ -93,10 +93,12 @@ class ProcessManager {
93
93
  * @param {string[]} args - Arguments for the command.
94
94
  * @param {object} options - Options for spawn (e.g., cwd).
95
95
  * @param {string} prefix - Prefix for stdout/stderr lines (e.g., 'frontend:').
96
- * @param {boolean} restartOnExit - Whether to restart the process if it exits with non-zero code.
96
+ * @param {boolean} restartOnError - Whether to restart the process if it exits with non-zero code.
97
+ * @param {Function} onExit
98
+ * @param {ChildProcess[] | undefined} processReference - An array where to save the process to gain a reference to it.
97
99
  * @returns {ChildProcess} The spawned child process instance.
98
100
  */
99
- startManagedProcess(command, args, options, prefix, restartOnExit) {
101
+ startManagedProcess(command, args, options, prefix, restartOnError, onExit, processReference) {
100
102
  if (this.cleanupInProgress) {
101
103
  console.warn(`[ProcessManager] Skipping managed process '${command}' during cleanup.`);
102
104
  return null;
@@ -107,13 +109,18 @@ class ProcessManager {
107
109
  )} (prefix: ${prefix})`,
108
110
  );
109
111
 
110
- const proc = spawn(command, args, {
112
+ if (processReference == null) {
113
+ processReference = {}
114
+ }
115
+
116
+ const startedProcess = spawn(command, args, {
111
117
  shell: true,
112
118
  stdio: 'pipe',
113
119
  ...options,
114
120
  });
121
+ processReference.process = startedProcess;
115
122
 
116
- proc.stdout.on('data', (data) => {
123
+ startedProcess.stdout.on('data', (data) => {
117
124
  let prefixedData = data.toString().split('\n').map(line => `${prefix} ${line}`).join('\n');
118
125
  if (prefixedData.endsWith(`${prefix} `)) {
119
126
  prefixedData = prefixedData.slice(0, -(`${prefix} `).length);
@@ -123,9 +130,8 @@ class ProcessManager {
123
130
  }
124
131
  prefixedData = prefixedData.replace(/\x1b\[(?:2J|3J|H)/gi, '');
125
132
  process.stdout.write(prefixedData);
126
- appendFileSync('./banana.log', prefixedData);
127
133
  });
128
- proc.stderr.on('data', (data) => {
134
+ startedProcess.stderr.on('data', (data) => {
129
135
  let prefixedData = data.toString().split('\n').map(line => `${prefix} ${line}`).join('\n');
130
136
  if (prefixedData.endsWith(`${prefix} `)) {
131
137
  prefixedData = prefixedData.slice(0, -(`${prefix} `).length);
@@ -135,52 +141,56 @@ class ProcessManager {
135
141
  }
136
142
  prefixedData = prefixedData.replace(/\x1b\[(?:2J|H)/gi, '');
137
143
  process.stderr.write(prefixedData);
138
- appendFileSync('./banana.log', prefixedData);
139
144
  });
140
145
 
141
- this.managedProcesses.add(proc);
146
+ this.managedProcesses.add(startedProcess);
142
147
 
143
- proc.on('error', (err) => {
148
+ startedProcess.on('error', (err) => {
144
149
  console.error(
145
150
  `[ProcessManager] Error starting managed process '${command}': ${err.message}`,
146
151
  );
147
- this.managedProcesses.delete(proc);
152
+ this.managedProcesses.delete(startedProcess);
148
153
  });
149
154
 
150
- proc.on('exit', (code, signal) => {
151
- if (!this.managedProcesses.has(proc)) {
155
+ startedProcess.on('exit', (code) => {
156
+ if (!this.managedProcesses.has(startedProcess)) {
152
157
  return;
153
158
  }
154
159
 
155
- this.managedProcesses.delete(proc);
160
+ this.managedProcesses.delete(startedProcess);
156
161
  if (this.cleanupInProgress) {
157
162
  console.log(
158
- `[ProcessManager] Managed process '${command}' (PID: ${proc.pid}) exited due to cleanup.`,
163
+ `[ProcessManager] Managed process '${command}' (PID: ${startedProcess.pid}) exited due to cleanup.`,
159
164
  );
160
165
  return;
161
166
  }
162
167
 
163
168
  if (code !== 0) {
164
169
  console.error(
165
- `[ProcessManager] Managed process '${command}' (PID: ${proc.pid}) exited with code ${code}.`,
170
+ `[ProcessManager] Managed process '${command}' (PID: ${startedProcess.pid}) exited with code ${code}.`,
166
171
  );
167
- if (restartOnExit) {
172
+ if (restartOnError) {
168
173
  console.warn(
169
174
  `[ProcessManager] Restarting managed process '${command}'...`,
170
175
  );
171
- this.startManagedProcess(command, args, options, prefix, restartOnExit);
176
+ this.startManagedProcess(command, args, options, prefix, restartOnError, onExit, processReference);
172
177
  }
173
178
  } else {
174
179
  console.log(
175
- `[ProcessManager] Managed process '${command}' (PID: ${proc.pid}) exited cleanly.`,
180
+ `[ProcessManager] Managed process '${command}' (PID: ${startedProcess.pid}) exited cleanly.`,
176
181
  );
182
+ onExit?.();
177
183
  }
178
184
  });
179
185
 
180
- return proc;
186
+ return processReference;
181
187
  }
182
188
 
183
189
  killProcess(process) {
190
+ if (process.killed || process.exitCode != null) {
191
+ return;
192
+ }
193
+
184
194
  this.managedProcesses.delete(process);
185
195
 
186
196
  return new Promise((resolve, reject) => {
@@ -231,7 +241,7 @@ class ProcessManager {
231
241
  }
232
242
  for (let index = this.managedProcesses.length - 1; index >= 0; index--) {
233
243
  const process = this.managedProcesses[index];
234
- if (process.killed) {
244
+ if (process.killed || process.exitCode != null) {
235
245
  this.managedProcesses.splice(index, 1);
236
246
  }
237
247
  }
@@ -241,7 +251,7 @@ class ProcessManager {
241
251
  console.log('\n[ProcessManager] Initiating cleanup of managed processes...');
242
252
 
243
253
  for (const proc of [...this.managedProcesses]) {
244
- if (proc.killed) {
254
+ if (proc.killed || proc.exitCode != null) {
245
255
  continue;
246
256
  }
247
257
 
@@ -255,7 +265,7 @@ class ProcessManager {
255
265
  await new Promise(resolve => setTimeout(resolve, 500));
256
266
 
257
267
  for (const proc of [...this.managedProcesses]) {
258
- if (proc.killed) {
268
+ if (proc.killed || proc.exitCode != null) {
259
269
  continue;
260
270
  }
261
271
 
@@ -28,10 +28,11 @@ 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) {
31
+ constructor(name, mode, config, onExit) {
32
32
  this.name = name;
33
33
  this.mode = mode;
34
34
  this.config = config;
35
+ this.onExit = onExit;
35
36
  }
36
37
 
37
38
  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) {
5
- super(name, mode, config);
4
+ constructor(name, mode, config, onExit) {
5
+ super(name, mode, config, onExit);
6
6
  this.prefix = `${name}:${mode}:`;
7
7
  this.processes = [];
8
8
  }
@@ -10,7 +10,12 @@ class CmdService extends BaseService {
10
10
  async start() {
11
11
  console.log(`[${this.name}:${this.mode}] Starting cmd service...`);
12
12
 
13
- const { preCommands, commands, defaultCommand } = this.config;
13
+ const { preCommands, commands } = this.config;
14
+ if (!commands) {
15
+ throw new Error(
16
+ `[${this.name}:${this.mode}] Commands not found for service.`,
17
+ );
18
+ }
14
19
 
15
20
  if (preCommands && preCommands.length > 0) {
16
21
  console.log(`[${this.name}:${this.mode}] Running pre-commands...`);
@@ -36,21 +41,14 @@ class CmdService extends BaseService {
36
41
  console.log(`[${this.name}:${this.mode}] Pre-commands completed.`);
37
42
  }
38
43
 
39
- const mainCommand = commands[defaultCommand];
40
- if (!mainCommand) {
41
- throw new Error(
42
- `[${this.name}:${this.mode}] Default command '${defaultCommand}' not found for service.`,
43
- );
44
- }
45
-
46
- const { cmdArgs, directory } = (Array.isArray(mainCommand) && typeof mainCommand[0] === 'string' ?
47
- { cmdArgs: [mainCommand], directory: [undefined] } :
48
- (Array.isArray(mainCommand) ?
44
+ const { cmdArgs, directory } = (Array.isArray(commands) && typeof commands[0] === 'string' ?
45
+ { cmdArgs: [commands], directory: [undefined] } :
46
+ (Array.isArray(commands) ?
49
47
  {
50
- cmdArgs: mainCommand.map(({ command }) => command),
51
- directory: mainCommand.map(({ directory }) => directory),
48
+ cmdArgs: commands.map(({ command }) => command),
49
+ directory: commands.map(({ directory }) => directory),
52
50
  } :
53
- { cmdArgs: [mainCommand.command], directory: [mainCommand.directory] }
51
+ { cmdArgs: [commands.command], directory: [commands.directory] }
54
52
  )
55
53
  );
56
54
 
@@ -68,6 +66,7 @@ class CmdService extends BaseService {
68
66
  this.prefix
69
67
  ),
70
68
  true,
69
+ () => this.onExit?.()
71
70
  );
72
71
 
73
72
  if (!process) {
@@ -78,13 +77,13 @@ class CmdService extends BaseService {
78
77
 
79
78
  this.processes.push(process);
80
79
  console.log(
81
- `[${this.name}:${this.mode}] Process started (PID: ${process.pid}).`,
80
+ `[${this.name}:${this.mode}] Process started (PID: ${process.process.pid}).`,
82
81
  );
83
82
  }
84
83
  }
85
84
 
86
85
  async stop() {
87
- const promises = this.processes.map(process => {
86
+ const promises = this.processes.map(({ process }) => {
88
87
  console.log(`[${this.name}:${this.mode}] Stopping process (PID: ${process.pid}).`);
89
88
  return CmdService._processManager.killProcess(process);
90
89
  });
@@ -1,8 +1,8 @@
1
1
  const { BaseService } = require('./base');
2
2
 
3
3
  class DockerService extends BaseService {
4
- /** @type {Set<string>} */
5
- static _composeFilesToStop = new Set();
4
+ /** @type {Map<string, string[]>} */
5
+ static _servicesToStop = new Map();
6
6
 
7
7
  static async cleanup() {
8
8
  if (!DockerService._processManager) {
@@ -10,23 +10,23 @@ class DockerService extends BaseService {
10
10
  return;
11
11
  }
12
12
 
13
- if (DockerService._composeFilesToStop.size > 0) {
13
+ if (DockerService._servicesToStop.size > 0) {
14
14
  console.log('[DockerService] Stopping all docker services from used compose files...');
15
- for (const composeFile of DockerService._composeFilesToStop) {
15
+ for (const [composeFile, services] of this._servicesToStop.entries()) {
16
16
  try {
17
- console.log(`[DockerService] Running 'docker compose -f ${composeFile} stop'`);
17
+ console.log(`[DockerService] Stopping services ${services.map(service => `'${service}'`).join(', ')} of '${composeFile}'`);
18
18
  DockerService._processManager.runSync(
19
19
  'docker',
20
- ['compose', '-f', composeFile, 'stop'],
20
+ ['compose', '-f', composeFile, 'stop', ...services],
21
21
  { stdio: 'inherit' },
22
22
  );
23
23
  } catch (error) {
24
24
  console.error(
25
- `[DockerService] Failed to execute 'docker compose -f ${composeFile} stop': ${error.message}`,
25
+ `[DockerService] Failed to stop services of '${composeFile}': ${error.message}`,
26
26
  );
27
27
  }
28
28
  }
29
- DockerService._composeFilesToStop.clear();
29
+ DockerService._servicesToStop.clear();
30
30
  } else {
31
31
  console.log('[DockerService] No docker services were started by orchestrator, skipping global stop.');
32
32
  }
@@ -47,16 +47,33 @@ class DockerService extends BaseService {
47
47
  console.log(
48
48
  `[${this.name}:${this.mode}] Docker container for '${this.dockerServiceName}' is already running.`,
49
49
  );
50
- DockerService._composeFilesToStop.add(this.dockerComposeFile);
51
50
  } else {
52
51
  console.log(`[${this.name}:${this.mode}] Bringing up docker service '${this.dockerServiceName}'...`);
53
52
  try {
53
+ const servicesBeforeStart = this._getCurrentlyRunningServices();
54
54
  await DockerService._processManager.runInherited(
55
55
  'docker',
56
56
  ['compose', '-f', this.dockerComposeFile, 'up', this.dockerServiceName, '-d'],
57
57
  );
58
- DockerService._composeFilesToStop.add(this.dockerComposeFile);
58
+ const servicesAfterStart = this._getCurrentlyRunningServices();
59
+ let servicesOfComposeFile = DockerService._servicesToStop.get(this.dockerComposeFile);
60
+ if (servicesOfComposeFile == null) {
61
+ servicesOfComposeFile = [];
62
+ }
63
+ const newServices = servicesAfterStart.filter(service => {
64
+ return !(
65
+ servicesBeforeStart.includes(service) ||
66
+ servicesOfComposeFile.includes(service)
67
+ );
68
+ });
69
+ DockerService._servicesToStop.set(
70
+ this.dockerComposeFile,
71
+ servicesOfComposeFile.concat(newServices)
72
+ );
59
73
  console.log(`[${this.name}:${this.mode}] Docker service '${this.dockerServiceName}' brought up.`);
74
+ if (newServices.length > 1) {
75
+ console.log(`[${this.name}:${this.mode}] Dependency service${newServices.length > 2 ? 's' : ''} for '${this.dockerServiceName}': ${newServices.filter(service => service.name !== this.dockerServiceName).join(', ')}`);
76
+ }
60
77
  } catch (error) {
61
78
  throw new Error(
62
79
  `[${this.name}:${this.mode}] Failed to bring up docker service '${this.dockerServiceName}': ${error.message}`,
@@ -88,6 +105,23 @@ class DockerService extends BaseService {
88
105
  return await this._checkServiceHealthiness();
89
106
  }
90
107
 
108
+ _getCurrentlyRunningServices() {
109
+ try {
110
+ const services = DockerService._processManager.runSync(
111
+ 'docker',
112
+ ['compose', '-f', this.dockerComposeFile, 'ps', '--services'],
113
+ );
114
+ return (services
115
+ .split('\n')
116
+ .map(serviceName => serviceName.trim())
117
+ .filter(serviceName => serviceName !== '')
118
+ );
119
+ } catch (error) {
120
+ // console.warn(...);
121
+ }
122
+ return null;
123
+ }
124
+
91
125
  _getContainerName() {
92
126
  if (this.containerName) {
93
127
  return this.containerName;
@@ -143,7 +177,7 @@ class DockerService extends BaseService {
143
177
  );
144
178
 
145
179
  if (healthStatus === 'healthy' || healthStatus === 'none') {
146
- console.log(`[${this.name}:${this.mode}] Container '${containerName}' healty!\n`);
180
+ console.log(`[${this.name}:${this.mode}] Container '${containerName}' healthy!`);
147
181
  return true;
148
182
  }
149
183
 
package/test.js DELETED
@@ -1,136 +0,0 @@
1
- const { loadConfig } = require('./src/config');
2
- const { resolveServiceExecutionGraph } = require('./src/dependency-resolver');
3
- const fs = require('fs');
4
-
5
- const testConfigContent = `
6
- composeFile: infrastructure/docker-compose.yml
7
-
8
- services:
9
- postgres:
10
- type: docker
11
- service: postgres
12
- healthCheck: true
13
-
14
- api:
15
- type: hybrid
16
- defaultMode: dev
17
- modes:
18
- dev:
19
- type: cmd
20
- commands:
21
- start: [npx, rollup, -c, -w]
22
- directory: ./api
23
- dependencies:
24
- - postgres
25
- - { service: frontend, mode: serve }
26
-
27
- docker:
28
- type: docker
29
- service: api
30
- healthCheck: true
31
- dependencies:
32
- - postgres
33
-
34
- frontend:
35
- type: hybrid
36
- defaultMode: dev
37
- modes:
38
- dev:
39
- type: cmd
40
- commands:
41
- start: [npx, rollup, -c, -w]
42
- directory: ./frontend
43
- dependencies:
44
- - { service: api, mode: docker }
45
-
46
- serve:
47
- type: cmd
48
- preCommands:
49
- - [npm, --prefix, frontend, run, build]
50
- commands:
51
- start: [node, ./localserver.mjs]
52
- directory: ./frontend
53
- dependencies:
54
- - api
55
-
56
- presets:
57
- api:
58
- services: [api]
59
-
60
- frontend:
61
- services: [frontend]
62
-
63
- all:
64
- services: [api, frontend]
65
-
66
- # Example of a preset that would cause a cycle if dependencies were set up naively
67
- # (and without the \`dev\` modes being peers)
68
- # cycle-test:
69
- # services: [api, frontend]
70
- # modes:
71
- # api: dev
72
- # frontend: cycle-dev # Imagine 'cycle-dev' depends on 'api:dev'
73
- # # To trigger a cycle, you'd need api:dev -> frontend:dev and frontend:dev -> api:dev
74
-
75
- `;
76
-
77
- const testConfigPath = 'temp-dev-orchestrator.yml';
78
- fs.writeFileSync(testConfigPath, testConfigContent);
79
-
80
- console.log('--- Testing Dependency Resolver ---');
81
-
82
- try {
83
- const config = loadConfig(testConfigPath);
84
- console.log('✅ Config loaded successfully');
85
-
86
- console.log('\n--- Resolving "api" preset ---');
87
- const apiExecutionGraph = resolveServiceExecutionGraph(config, 'api');
88
- console.log('✅ "api" preset resolved successfully.');
89
- console.log('Dependencies:');
90
- apiExecutionGraph.dependencies.forEach((node) => {
91
- console.log(` - ${node.name} (mode: ${node.mode})`);
92
- });
93
- console.log('Services:');
94
- apiExecutionGraph.services.forEach((node) => {
95
- console.log(` - ${node.name} (mode: ${node.mode})`);
96
- });
97
-
98
- console.log('\n--- Resolving "frontend" preset ---');
99
- const frontendExecutionGraph = resolveServiceExecutionGraph(config, 'frontend');
100
- console.log('✅ "frontend" preset resolved successfully.');
101
- console.log('Dependencies:');
102
- frontendExecutionGraph.dependencies.forEach((node) => {
103
- console.log(` - ${node.name} (mode: ${node.mode})`);
104
- });
105
- console.log('Services:');
106
- frontendExecutionGraph.services.forEach((node) => {
107
- console.log(` - ${node.name} (mode: ${node.mode})`);
108
- });
109
-
110
- console.log('\n--- Resolving "all" preset ---');
111
- const allExecutionGraph = resolveServiceExecutionGraph(config, 'all');
112
- console.log('✅ "all" preset resolved successfully.');
113
- console.log('Dependencies:');
114
- allExecutionGraph.dependencies.forEach((node) => {
115
- console.log(` - ${node.name} (mode: ${node.mode})`);
116
- });
117
- console.log('Services:');
118
- allExecutionGraph.services.forEach((node) => {
119
- console.log(` - ${node.name} (mode: ${node.mode})`);
120
- });
121
-
122
- console.log('\n--- Testing non-existent preset ---');
123
- try {
124
- resolveServiceExecutionGraph(config, 'non-existent');
125
- } catch (error) {
126
- console.log(`✅ Caught expected error: ${error.message}`);
127
- }
128
- } catch (error) {
129
- console.error('❌ An unexpected error occurred during testing:', error);
130
- } finally {
131
- // Clean up the temporary config file
132
- if (fs.existsSync(testConfigPath)) {
133
- fs.unlinkSync(testConfigPath);
134
- console.log(`\nCleaned up temporary config file: ${testConfigPath}`);
135
- }
136
- }