go-dev 0.4.2 → 0.6.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.
@@ -1,172 +1,181 @@
1
- const { loadConfig } = require('./config');
2
- const { resolveServiceExecutionGraph } = require('./dependency-resolver');
3
- const ProcessManager = require('./process-manager');
4
- const { BaseService } = require('./services/base');
5
- const { CmdService } = require('./services/cmd');
6
- const { DockerService } = require('./services/docker');
7
-
8
- const serviceTypeMap = {
9
- cmd: CmdService,
10
- docker: DockerService,
11
- };
12
-
13
- class Orchestrator {
14
- constructor(configPath) {
15
- this.config = loadConfig(configPath);
16
-
17
- this.processManager = new ProcessManager();
18
- this.activeServiceInstances = new Map();
19
-
20
- BaseService.initialize(this.processManager);
21
- }
22
-
23
- async start(presetName) {
24
- try {
25
- const { dependencies, services: primaryServices } = resolveServiceExecutionGraph(
26
- this.config,
27
- presetName,
28
- );
29
-
30
- console.log(`Starting development environment for preset: ${presetName}`);
31
- console.log('--- Resolved Dependencies to Start First ---');
32
- dependencies.forEach(s => console.log(` - ${s.name} (mode: ${s.mode})`));
33
- console.log('--- Resolved Primary Services to Run ---');
34
- primaryServices.forEach(s => console.log(` - ${s.name} (mode: ${s.mode})`));
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
-
93
- console.log('\n--- Starting Dependencies ---');
94
- for (const { name, mode, config } of dependencies) {
95
- if (this.activeServiceInstances.has(name)) {
96
- console.log(`[${name}:${mode}] Already active, skipping start.`);
97
- continue;
98
- }
99
- const ServiceClass = serviceTypeMap[config.type];
100
- if (!ServiceClass) {
101
- throw new Error(`Unknown service type '${config.type}' for service '${name}'.`);
102
- }
103
- const serviceInstance = new ServiceClass(name, mode, config, () => {});
104
- this.activeServiceInstances.set(name, serviceInstance);
105
- await serviceInstance.start();
106
- }
107
-
108
- console.log('\n--- Starting Primary Services ---');
109
- const activePrimaryServices = new Map();
110
- const primaryServicePromises = [];
111
- for (const { name, mode, config } of primaryServices) {
112
- if (this.activeServiceInstances.has(name)) {
113
- console.log(`[${name}:${mode}] Already active, skipping start.`);
114
- continue;
115
- }
116
- const ServiceClass = serviceTypeMap[config.type];
117
- if (!ServiceClass) {
118
- throw new Error(`Unknown service type '${config.type}' for service '${name}'.`);
119
- }
120
- const serviceInstance = new ServiceClass(name, mode, config, () => {
121
- activePrimaryServices.delete(name);
122
- if (activePrimaryServices.size > 0) {
123
- return;
124
- }
125
-
126
- this.cleanup();
127
- }, extraArgs.get(name));
128
- this.activeServiceInstances.set(name, serviceInstance);
129
- activePrimaryServices.set(name, serviceInstance);
130
- primaryServicePromises.push(serviceInstance.start());
131
- }
132
- await Promise.all(primaryServicePromises);
133
-
134
- console.log('\n--- All services initiated. Press Ctrl+C to stop. ---');
135
-
136
- process.once('SIGINT', this.cleanup.bind(this));
137
- process.once('SIGTERM', this.cleanup.bind(this));
138
- process.stdin.resume();
139
-
140
- } catch (error) {
141
- console.error('\n❌ Orchestrator failed to start:', error.message);
142
- await this.cleanup(true);
143
- process.exit(1);
144
- }
145
- }
146
-
147
- async cleanup() {
148
- if (this.processManager.cleanupInProgress) {
149
- console.log('[Orchestrator] Cleanup already in progress.');
150
- return;
151
- }
152
-
153
- console.log('\n[Orchestrator] Initiating graceful cleanup...');
154
-
155
- for (const [name, instance] of this.activeServiceInstances.entries()) {
156
- try {
157
- console.log(`[Orchestrator] Requesting instance stop for ${name}:${instance.mode}`);
158
- await instance.stop();
159
- } catch (error) {
160
- console.error(`[Orchestrator] Error stopping instance ${name}:${instance.mode}: ${error.message}`);
161
- }
162
- }
163
-
164
- await DockerService.cleanup();
165
- await this.processManager.cleanupManagedProcesses();
166
-
167
- console.log('[Orchestrator] Cleanup complete.');
168
- process.exit(0);
169
- }
170
- }
171
-
1
+ const { loadConfig } = require('./config');
2
+ const { resolveServiceExecutionGraph } = require('./dependency-resolver');
3
+ const log = require('./logger');
4
+ const ProcessManager = require('./process-manager');
5
+ const { buildColoredTag, colorService, colorMode } = require('./service-colors');
6
+ const { BaseService } = require('./services/base');
7
+ const { CmdService } = require('./services/cmd');
8
+ const { DockerService } = require('./services/docker');
9
+
10
+ const serviceTypeMap = {
11
+ cmd: CmdService,
12
+ docker: DockerService,
13
+ };
14
+
15
+ const bold = (text) => `\x1b[1m${text}\x1b[0m`;
16
+
17
+ class Orchestrator {
18
+ constructor(configPath, options = {}) {
19
+ this.config = loadConfig(configPath);
20
+
21
+ const level = options.logLevel ?? this.config.logLevel;
22
+ if (level) {
23
+ log.setLogLevel(level);
24
+ }
25
+
26
+ this.processManager = new ProcessManager();
27
+ this.activeServiceInstances = new Map();
28
+
29
+ BaseService.initialize(this.processManager, this.config.services);
30
+ }
31
+
32
+ async start(presetName) {
33
+ try {
34
+ const { dependencies, services: primaryServices } = resolveServiceExecutionGraph(
35
+ this.config,
36
+ presetName,
37
+ );
38
+
39
+ log.info(bold(`Preset: ${presetName}`));
40
+ log.info(`\n${bold('Resolved dependencies')}`);
41
+ dependencies.forEach(s => log.info(` - ${colorService(s.name, s.mode)} (mode: ${colorMode(s.name, s.mode)})`));
42
+ log.info(`\n${bold('Resolved primary services')}`);
43
+ primaryServices.forEach(s => log.info(` - ${colorService(s.name, s.mode)} (mode: ${colorMode(s.name, s.mode)})`));
44
+
45
+ const extraArgs = new Map();
46
+ {
47
+ const argsToParse = process.argv.slice(3);
48
+ if (argsToParse.length > 0) {
49
+ log.info(`\n${bold('Gathering arguments')}`);
50
+ const serviceArgsKeyword = `--${this.config.serviceArgsKeyword ?? 'args-for'}`;
51
+ let isGettingService = false;
52
+ let currentService = null;
53
+ let currentIndex = 0;
54
+ let argsToPass = null;
55
+ for (const arg of argsToParse) {
56
+ if (arg === serviceArgsKeyword) {
57
+ isGettingService = true;
58
+ currentService = null;
59
+ currentIndex = 0;
60
+ continue;
61
+ }
62
+ if (currentService == null) {
63
+ if (isGettingService === false) {
64
+ throw new Error(`Invalid arguments, use format: npx go-dev ${presetName} ${serviceArgsKeyword} <service> <args>`);
65
+ }
66
+
67
+ const splitArg = arg.split(':');
68
+ if (splitArg.length > 2) {
69
+ throw new Error(`Invalid service name + index '${arg}': should be <service> or <service>:<command_index>`);
70
+ }
71
+
72
+ const index = splitArg.length > 1 ? parseInt(splitArg[1]) : 0;;
73
+ if (splitArg.length > 1 && `${index}` !== splitArg[1]) {
74
+ throw new Error(`Invalid service name + index '${arg}': should be <service> or <service>:<command_index>`);
75
+ }
76
+
77
+ currentService = splitArg[0];
78
+ currentIndex = index;
79
+ argsToPass = extraArgs.get(currentService) ?? [];
80
+ extraArgs.set(currentService, argsToPass);
81
+ isGettingService = false;
82
+ continue;
83
+ }
84
+
85
+ let args = argsToPass[currentIndex];
86
+ if (args == null) {
87
+ args = [];
88
+ argsToPass[currentIndex] = args;
89
+ }
90
+
91
+ args.push(arg);
92
+ }
93
+
94
+ for (const [service, indexedArgs] of extraArgs.entries()) {
95
+ indexedArgs.forEach((args, index) => {
96
+ log.info(` - ${service}:${index}: [${args.join(', ')}]`);
97
+ });
98
+ }
99
+ }
100
+ }
101
+
102
+ log.info(`\n${bold('Starting dependencies')}`);
103
+ for (const { name, mode, config } of dependencies) {
104
+ if (this.activeServiceInstances.has(name)) {
105
+ log.info(`[${buildColoredTag(name, mode)}] Already active, skipping start.`);
106
+ continue;
107
+ }
108
+ const ServiceClass = serviceTypeMap[config.type];
109
+ if (!ServiceClass) {
110
+ throw new Error(`Unknown service type '${config.type}' for service '${name}'.`);
111
+ }
112
+ const serviceInstance = new ServiceClass(name, mode, config, () => {});
113
+ this.activeServiceInstances.set(name, serviceInstance);
114
+ await serviceInstance.start();
115
+ }
116
+
117
+ log.info(`\n${bold('Starting primary services')}`);
118
+ const activePrimaryServices = new Map();
119
+ const primaryServicePromises = [];
120
+ for (const { name, mode, config } of primaryServices) {
121
+ if (this.activeServiceInstances.has(name)) {
122
+ log.info(`[${buildColoredTag(name, mode)}] Already active, skipping start.`);
123
+ continue;
124
+ }
125
+ const ServiceClass = serviceTypeMap[config.type];
126
+ if (!ServiceClass) {
127
+ throw new Error(`Unknown service type '${config.type}' for service '${name}'.`);
128
+ }
129
+ const serviceInstance = new ServiceClass(name, mode, config, () => {
130
+ activePrimaryServices.delete(name);
131
+ if (activePrimaryServices.size > 0) {
132
+ return;
133
+ }
134
+
135
+ this.cleanup();
136
+ }, extraArgs.get(name));
137
+ this.activeServiceInstances.set(name, serviceInstance);
138
+ activePrimaryServices.set(name, serviceInstance);
139
+ primaryServicePromises.push(serviceInstance.start());
140
+ }
141
+ await Promise.all(primaryServicePromises);
142
+
143
+ log.info(`\n${bold('Ready')} (press Ctrl+C to stop)`);
144
+
145
+ process.once('SIGINT', this.cleanup.bind(this));
146
+ process.once('SIGTERM', this.cleanup.bind(this));
147
+ process.stdin.resume();
148
+
149
+ } catch (error) {
150
+ log.error('\n❌ Orchestrator failed to start:', error.message);
151
+ await this.cleanup(true);
152
+ process.exit(1);
153
+ }
154
+ }
155
+
156
+ async cleanup() {
157
+ if (this.processManager.cleanupInProgress) {
158
+ log.debug('[Orchestrator] Cleanup already in progress.');
159
+ return;
160
+ }
161
+
162
+ log.info(`\n${bold('Shutting down')}`);
163
+
164
+ for (const [name, instance] of this.activeServiceInstances.entries()) {
165
+ try {
166
+ log.info(` - ${buildColoredTag(name, instance.mode)}`);
167
+ await instance.stop();
168
+ } catch (error) {
169
+ log.error(` - ${buildColoredTag(name, instance.mode)}: ${error.message}`);
170
+ }
171
+ }
172
+
173
+ await DockerService.cleanup();
174
+ await this.processManager.cleanupManagedProcesses();
175
+
176
+ log.info(`\n${bold('Done')}`);
177
+ process.exit(0);
178
+ }
179
+ }
180
+
172
181
  module.exports = Orchestrator;
@@ -1,20 +1,20 @@
1
- const detectLastFormatting = require("./terminal-formatting/detect-last-formatting");
2
-
3
- function prefixLines(text, prefix, latestFormatting) {
4
- let prefixedText = text.split('\n').map(line => {
5
- latestFormatting = detectLastFormatting(line, latestFormatting);
6
- return `${prefix} ${latestFormatting}${line}${latestFormatting !== '' ? '\x1b[0m' : ''}`;
7
- }).join('\n');
8
-
9
- if (prefixedText.endsWith(`${prefix} `)) {
10
- prefixedText = prefixedText.slice(0, -(`${prefix} `).length);
11
- }
12
- if (!prefixedText.endsWith('\n')) {
13
- prefixedText = prefixedText + '\n';
14
- }
15
- prefixedText = prefixedText.replace(/\x1b\[(?:2J|3J|H)|\x1bc|\x1b\[2K|\x1b\[K/gi, '');
16
-
17
- return { prefixedText, lastFormatting: latestFormatting };
18
- }
19
-
1
+ const detectLastFormatting = require("./terminal-formatting/detect-last-formatting");
2
+
3
+ function prefixLines(text, prefix, latestFormatting) {
4
+ let prefixedText = text.split('\n').map(line => {
5
+ latestFormatting = detectLastFormatting(line, latestFormatting);
6
+ return `${prefix} ${latestFormatting}${line}${latestFormatting !== '' ? '\x1b[0m' : ''}`;
7
+ }).join('\n');
8
+
9
+ if (prefixedText.endsWith(`${prefix} `)) {
10
+ prefixedText = prefixedText.slice(0, -(`${prefix} `).length);
11
+ }
12
+ if (!prefixedText.endsWith('\n')) {
13
+ prefixedText = prefixedText + '\n';
14
+ }
15
+ prefixedText = prefixedText.replace(/\x1b\[(?:2J|3J|H)|\x1bc|\x1b\[2K|\x1b\[K/gi, '');
16
+
17
+ return { prefixedText, lastFormatting: latestFormatting };
18
+ }
19
+
20
20
  module.exports = prefixLines;