go-dev 0.4.2 → 0.5.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 +20 -20
- package/README.md +243 -230
- package/bin/go-dev +18 -14
- package/package.json +1 -1
- package/src/cli-args.js +83 -0
- package/src/config.js +133 -122
- package/src/dependency-resolver.js +103 -101
- package/src/index.js +9 -4
- package/src/logger.js +47 -0
- package/src/orchestrator.js +180 -171
- package/src/prefix-lines.js +19 -19
- package/src/process-manager.js +308 -306
- package/src/service-colors.js +58 -0
- package/src/services/base.js +63 -51
- package/src/services/cmd.js +237 -148
- package/src/services/docker.js +194 -197
- package/src/terminal-formatting/apply-formatting-codes.js +223 -223
- package/src/terminal-formatting/constants.js +150 -150
- package/src/terminal-formatting/detect-last-formatting.js +10 -10
- package/src/terminal-formatting/extract-formatting-codes.js +26 -26
- package/test/go-dev.yml +84 -0
package/src/orchestrator.js
CHANGED
|
@@ -1,172 +1,181 @@
|
|
|
1
|
-
const { loadConfig } = require('./config');
|
|
2
|
-
const { resolveServiceExecutionGraph } = require('./dependency-resolver');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
argsToPass
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.activeServiceInstances.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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;
|
package/src/prefix-lines.js
CHANGED
|
@@ -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;
|