wilfredwake 1.0.5 → 1.0.7
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/bin/cli.js +14 -4
- package/package.json +1 -1
- package/src/cli/commands/status.js +6 -13
- package/src/cli/commands/wake.js +175 -2
- package/src/orchestrator/orchestrator.js +8 -12
package/bin/cli.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { Command } from 'commander';
|
|
16
16
|
import chalk from 'chalk';
|
|
17
|
+
import fs from 'fs';
|
|
17
18
|
import { initCommand } from '../src/cli/commands/init.js';
|
|
18
19
|
import { statusCommand } from '../src/cli/commands/status.js';
|
|
19
20
|
import { wakeCommand } from '../src/cli/commands/wake.js';
|
|
@@ -30,7 +31,16 @@ program
|
|
|
30
31
|
.description(
|
|
31
32
|
chalk.cyan('🌅 CLI Tool for Multi-Developer Development Environment Wake & Status Management')
|
|
32
33
|
)
|
|
33
|
-
.version(
|
|
34
|
+
.version(
|
|
35
|
+
(() => {
|
|
36
|
+
try {
|
|
37
|
+
const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url)));
|
|
38
|
+
return pkg.version || '0.0.0';
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return '0.0.0';
|
|
41
|
+
}
|
|
42
|
+
})()
|
|
43
|
+
)
|
|
34
44
|
.usage(chalk.yellow('[command] [options]'));
|
|
35
45
|
|
|
36
46
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -59,7 +69,7 @@ program
|
|
|
59
69
|
program
|
|
60
70
|
.command('status [service]')
|
|
61
71
|
.description(chalk.green('Check status of all services or a specific service'))
|
|
62
|
-
.option('-e, --env <environment>', 'Environment (dev, staging, prod)'
|
|
72
|
+
.option('-e, --env <environment>', 'Environment (dev, staging, prod)')
|
|
63
73
|
.option('-f, --format <format>', 'Output format (table, json)', 'table')
|
|
64
74
|
.action(statusCommand);
|
|
65
75
|
|
|
@@ -72,7 +82,7 @@ program
|
|
|
72
82
|
program
|
|
73
83
|
.command('wake [target]')
|
|
74
84
|
.description(chalk.blue('Wake services on demand (all, <service>, or <group>)'))
|
|
75
|
-
.option('-e, --env <environment>', 'Environment (dev, staging, prod)'
|
|
85
|
+
.option('-e, --env <environment>', 'Environment (dev, staging, prod)')
|
|
76
86
|
.option('--no-wait', 'Don\'t wait for services to be ready')
|
|
77
87
|
.option('--timeout <seconds>', 'Timeout for service readiness', '300')
|
|
78
88
|
.action(wakeCommand);
|
|
@@ -85,7 +95,7 @@ program
|
|
|
85
95
|
program
|
|
86
96
|
.command('health [service]')
|
|
87
97
|
.description(chalk.cyan('Check health status of services'))
|
|
88
|
-
.option('-e, --env <environment>', 'Environment (dev, staging, prod)'
|
|
98
|
+
.option('-e, --env <environment>', 'Environment (dev, staging, prod)')
|
|
89
99
|
.action(healthCommand);
|
|
90
100
|
|
|
91
101
|
// ═══════════════════════════════════════════════════════════════
|
package/package.json
CHANGED
|
@@ -126,14 +126,14 @@ function _displayTableStatus(services, environment) {
|
|
|
126
126
|
const lastWoken = service.lastWakeTime
|
|
127
127
|
? new Date(service.lastWakeTime).toLocaleString()
|
|
128
128
|
: 'Never';
|
|
129
|
-
|
|
130
129
|
const cells = [
|
|
131
130
|
chalk.cyan(service.name.padEnd(20)),
|
|
132
131
|
statusColor(service.status.toUpperCase().padEnd(20)),
|
|
133
132
|
chalk.yellow(lastWoken.padEnd(20)),
|
|
134
|
-
chalk.gray(service.url.substring(0, 20).padEnd(20)),
|
|
133
|
+
chalk.gray((service.url || '').substring(0, 20).padEnd(20)),
|
|
135
134
|
];
|
|
136
135
|
console.log(format.tableRow(cells));
|
|
136
|
+
console.log(''); // Extra spacing between rows for clarity
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
console.log(''); // Spacing
|
|
@@ -158,23 +158,16 @@ function _displaySummary(services) {
|
|
|
158
158
|
|
|
159
159
|
const stats = {
|
|
160
160
|
total: services.length,
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
live: services.filter(s => s.status === 'live').length,
|
|
162
|
+
dead: services.filter(s => s.status === 'dead').length,
|
|
163
163
|
waking: services.filter(s => s.status === 'waking').length,
|
|
164
164
|
failed: services.filter(s => s.status === 'failed').length,
|
|
165
|
+
unknown: services.filter(s => s.status === 'unknown').length,
|
|
165
166
|
};
|
|
166
167
|
|
|
167
168
|
console.log(chalk.magentaBright.bold('Summary'));
|
|
168
169
|
console.log(
|
|
169
|
-
` ${colors.status.
|
|
170
|
-
stats.ready
|
|
171
|
-
)} | ${colors.status.sleeping('⚫')} Sleeping: ${colors.status.sleeping(
|
|
172
|
-
stats.sleeping
|
|
173
|
-
)} | ${colors.status.waking('⟳')} Waking: ${colors.status.waking(
|
|
174
|
-
stats.waking
|
|
175
|
-
)} | ${colors.status.failed('✗')} Failed: ${colors.status.failed(
|
|
176
|
-
stats.failed
|
|
177
|
-
)}`
|
|
170
|
+
` ${colors.status.live('✓')} Live: ${colors.status.live(stats.live)} | ${colors.status.dead('⚫')} Dead: ${colors.status.dead(stats.dead)} | ${colors.status.waking('⟳')} Waking: ${colors.status.waking(stats.waking)} | ${colors.status.failed('✗')} Failed: ${colors.status.failed(stats.failed)} | ${colors.status.unknown('?')} Unknown: ${colors.status.unknown(stats.unknown)}`
|
|
178
171
|
);
|
|
179
172
|
console.log(` Total: ${stats.total} services\n`);
|
|
180
173
|
}
|
package/src/cli/commands/wake.js
CHANGED
|
@@ -82,13 +82,22 @@ export async function wakeCommand(target, options) {
|
|
|
82
82
|
_displayWakeResults(results);
|
|
83
83
|
|
|
84
84
|
// ═══════════════════════════════════════════════════════════════
|
|
85
|
-
// DISPLAY FINAL STATUS
|
|
85
|
+
// DISPLAY FINAL STATUS AND START MONITORING
|
|
86
86
|
// ═══════════════════════════════════════════════════════════════
|
|
87
87
|
if (shouldWait) {
|
|
88
88
|
_displayFinalStatus(results);
|
|
89
|
+
|
|
90
|
+
// Start 5-minute monitoring period
|
|
91
|
+
await _monitorServicesForDuration(
|
|
92
|
+
config.orchestratorUrl,
|
|
93
|
+
config.token,
|
|
94
|
+
env,
|
|
95
|
+
wakeTarget,
|
|
96
|
+
5 * 60 * 1000 // 5 minutes in milliseconds
|
|
97
|
+
);
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
process.exit(
|
|
100
|
+
process.exit(0);
|
|
92
101
|
} catch (error) {
|
|
93
102
|
stopSpinner?.();
|
|
94
103
|
throw error;
|
|
@@ -180,3 +189,167 @@ function _displayFinalStatus(results) {
|
|
|
180
189
|
|
|
181
190
|
console.log('');
|
|
182
191
|
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Monitor services for a duration and display live status updates
|
|
195
|
+
* @private
|
|
196
|
+
* @param {string} orchestratorUrl - Orchestrator URL
|
|
197
|
+
* @param {string} token - Authorization token
|
|
198
|
+
* @param {string} env - Environment name
|
|
199
|
+
* @param {string} wakeTarget - Wake target (all, <service>, or <group>)
|
|
200
|
+
* @param {number} duration - Duration in milliseconds
|
|
201
|
+
*/
|
|
202
|
+
async function _monitorServicesForDuration(
|
|
203
|
+
orchestratorUrl,
|
|
204
|
+
token,
|
|
205
|
+
env,
|
|
206
|
+
wakeTarget,
|
|
207
|
+
duration
|
|
208
|
+
) {
|
|
209
|
+
const pollInterval = 10000; // Poll every 10 seconds
|
|
210
|
+
const startTime = Date.now();
|
|
211
|
+
const endTime = startTime + duration;
|
|
212
|
+
let pollCount = 0;
|
|
213
|
+
|
|
214
|
+
console.log(
|
|
215
|
+
chalk.magentaBright.bold(
|
|
216
|
+
`📡 Monitoring services for ${Math.round(duration / 1000)}s...\n`
|
|
217
|
+
)
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
while (Date.now() < endTime) {
|
|
221
|
+
pollCount++;
|
|
222
|
+
const elapsedSeconds = Math.round((Date.now() - startTime) / 1000);
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
// Fetch current status from orchestrator
|
|
226
|
+
const response = await axios.get(
|
|
227
|
+
`${orchestratorUrl}/api/status`,
|
|
228
|
+
{
|
|
229
|
+
params: {
|
|
230
|
+
environment: env,
|
|
231
|
+
},
|
|
232
|
+
timeout: 5000,
|
|
233
|
+
headers: {
|
|
234
|
+
Authorization: token ? `Bearer ${token}` : undefined,
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const services = response.data.services || [];
|
|
240
|
+
|
|
241
|
+
// Clear screen and display updated status
|
|
242
|
+
console.clear();
|
|
243
|
+
console.log(chalk.cyanBright.bold('📡 Live Service Monitoring\n'));
|
|
244
|
+
console.log(
|
|
245
|
+
chalk.gray(`Elapsed: ${chalk.yellow(elapsedSeconds)}s / ${Math.round(duration / 1000)}s | Poll #${pollCount}\n`)
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Display table with current status
|
|
249
|
+
_displayLiveMonitoringTable(services, env);
|
|
250
|
+
|
|
251
|
+
// Display summary stats
|
|
252
|
+
_displayLiveMonitoringSummary(services);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.clear();
|
|
255
|
+
console.log(chalk.cyanBright.bold('📡 Live Service Monitoring\n'));
|
|
256
|
+
console.log(
|
|
257
|
+
chalk.gray(`Elapsed: ${chalk.yellow(elapsedSeconds)}s / ${Math.round(duration / 1000)}s | Poll #${pollCount}\n`)
|
|
258
|
+
);
|
|
259
|
+
logger.warn(`Status check failed: ${error.message}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Wait before next poll (unless we're at the end)
|
|
263
|
+
if (Date.now() < endTime) {
|
|
264
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Final summary
|
|
269
|
+
console.clear();
|
|
270
|
+
console.log(chalk.cyanBright.bold('📡 Monitoring Complete\n'));
|
|
271
|
+
console.log(chalk.yellow(`Total monitoring duration: ${Math.round(duration / 1000)}s\n`));
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
// Fetch final status
|
|
275
|
+
const response = await axios.get(
|
|
276
|
+
`${orchestratorUrl}/api/status`,
|
|
277
|
+
{
|
|
278
|
+
params: {
|
|
279
|
+
environment: env,
|
|
280
|
+
},
|
|
281
|
+
timeout: 5000,
|
|
282
|
+
headers: {
|
|
283
|
+
Authorization: token ? `Bearer ${token}` : undefined,
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const services = response.data.services || [];
|
|
289
|
+
_displayLiveMonitoringTable(services, env);
|
|
290
|
+
_displayLiveMonitoringSummary(services);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
logger.warn(`Could not fetch final status: ${error.message}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
console.log('');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Display live monitoring status table
|
|
300
|
+
* @private
|
|
301
|
+
* @param {Array} services - Services array
|
|
302
|
+
* @param {string} environment - Environment name
|
|
303
|
+
*/
|
|
304
|
+
function _displayLiveMonitoringTable(services, environment) {
|
|
305
|
+
if (services.length === 0) {
|
|
306
|
+
logger.info('No services to display.');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log(chalk.cyanBright.bold(`Services (${environment.toUpperCase()})\n`));
|
|
311
|
+
|
|
312
|
+
const headers = ['Service', 'Status', 'Last Woken', 'URL'];
|
|
313
|
+
console.log(format.tableHeader(headers));
|
|
314
|
+
|
|
315
|
+
services.forEach((service) => {
|
|
316
|
+
const statusColor = colors.status[service.status] || colors.status.unknown;
|
|
317
|
+
const lastWoken = service.lastWakeTime
|
|
318
|
+
? new Date(service.lastWakeTime).toLocaleString()
|
|
319
|
+
: 'Never';
|
|
320
|
+
const cells = [
|
|
321
|
+
chalk.cyan(service.name.padEnd(20)),
|
|
322
|
+
statusColor(service.status.toUpperCase().padEnd(20)),
|
|
323
|
+
chalk.yellow(lastWoken.padEnd(20)),
|
|
324
|
+
chalk.gray((service.url || '').substring(0, 20).padEnd(20)),
|
|
325
|
+
];
|
|
326
|
+
console.log(format.tableRow(cells));
|
|
327
|
+
console.log('');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
console.log('');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Display live monitoring summary stats
|
|
335
|
+
* @private
|
|
336
|
+
* @param {Array} services - Services array
|
|
337
|
+
*/
|
|
338
|
+
function _displayLiveMonitoringSummary(services) {
|
|
339
|
+
if (services.length === 0) return;
|
|
340
|
+
|
|
341
|
+
const stats = {
|
|
342
|
+
total: services.length,
|
|
343
|
+
live: services.filter(s => s.status === 'live').length,
|
|
344
|
+
dead: services.filter(s => s.status === 'dead').length,
|
|
345
|
+
waking: services.filter(s => s.status === 'waking').length,
|
|
346
|
+
failed: services.filter(s => s.status === 'failed').length,
|
|
347
|
+
unknown: services.filter(s => s.status === 'unknown').length,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
console.log(chalk.magentaBright.bold('Summary'));
|
|
351
|
+
console.log(
|
|
352
|
+
` ${colors.status.live('✓')} Live: ${colors.status.live(stats.live)} | ${colors.status.dead('⚫')} Dead: ${colors.status.dead(stats.dead)} | ${colors.status.waking('⟳')} Waking: ${colors.status.waking(stats.waking)} | ${colors.status.failed('✗')} Failed: ${colors.status.failed(stats.failed)} | ${colors.status.unknown('?')} Unknown: ${colors.status.unknown(stats.unknown)}`
|
|
353
|
+
);
|
|
354
|
+
console.log(` Total: ${stats.total} services\n`);
|
|
355
|
+
}
|
|
@@ -283,17 +283,13 @@ export class Orchestrator {
|
|
|
283
283
|
// ═══════════════════════════════════════════════════════════════
|
|
284
284
|
// DETERMINE STATE FROM STATUS CODE
|
|
285
285
|
// ═══════════════════════════════════════════════════════════════
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
state = ServiceState.FAILED;
|
|
294
|
-
} else if (response.status >= 400) {
|
|
295
|
-
state = ServiceState.UNKNOWN;
|
|
296
|
-
}
|
|
286
|
+
// NEW LOGIC: If we get ANY response from the API (2xx, 3xx, 4xx, 5xx),
|
|
287
|
+
// mark the service as LIVE. This means the service is responsive.
|
|
288
|
+
// Only mark as FAILED if no response is received at all.
|
|
289
|
+
let state = ServiceState.LIVE;
|
|
290
|
+
|
|
291
|
+
// If we got a response, the service is responsive = LIVE
|
|
292
|
+
// No need to check status code, any response means service can be reached
|
|
297
293
|
|
|
298
294
|
this._logTimestamp(
|
|
299
295
|
service.name,
|
|
@@ -316,7 +312,7 @@ export class Orchestrator {
|
|
|
316
312
|
);
|
|
317
313
|
|
|
318
314
|
return {
|
|
319
|
-
state: ServiceState.DEAD, //
|
|
315
|
+
state: ServiceState.DEAD, // Only dead if no response received
|
|
320
316
|
statusCode: null,
|
|
321
317
|
responseTime,
|
|
322
318
|
error: error.message,
|