wilfredwake 1.0.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/.env.example +52 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +400 -0
- package/README.md +831 -0
- package/bin/cli.js +133 -0
- package/index.js +222 -0
- package/package.json +41 -0
- package/src/cli/commands/health.js +238 -0
- package/src/cli/commands/init.js +192 -0
- package/src/cli/commands/status.js +180 -0
- package/src/cli/commands/wake.js +182 -0
- package/src/cli/config.js +263 -0
- package/src/config/services.yaml +49 -0
- package/src/orchestrator/orchestrator.js +397 -0
- package/src/orchestrator/registry.js +283 -0
- package/src/orchestrator/server.js +402 -0
- package/src/shared/colors.js +154 -0
- package/src/shared/logger.js +224 -0
- package/tests/cli.test.js +328 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ╔═══════════════════════════════════════════════════════════════╗
|
|
3
|
+
* ║ INIT COMMAND - First-Time Setup ║
|
|
4
|
+
* ║ Guides user through configuration initialization ║
|
|
5
|
+
* ║ Sets orchestrator URL and API token ║
|
|
6
|
+
* ╚═══════════════════════════════════════════════════════════════╝
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import readline from 'readline';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import ConfigManager from '../config.js';
|
|
12
|
+
import { Logger, utils } from '../../shared/logger.js';
|
|
13
|
+
import { format } from '../../shared/colors.js';
|
|
14
|
+
|
|
15
|
+
const logger = new Logger();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize wilfredwake configuration
|
|
19
|
+
* Prompts user for orchestrator URL and API token
|
|
20
|
+
* Creates ~/.wilfredwake/config.json
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} options - Command options
|
|
23
|
+
* @param {string} options.orchestrator - Orchestrator URL
|
|
24
|
+
* @param {string} options.token - API token
|
|
25
|
+
*/
|
|
26
|
+
export async function initCommand(options) {
|
|
27
|
+
try {
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════
|
|
29
|
+
// INITIALIZATION HEADER
|
|
30
|
+
// ═══════════════════════════════════════════════════════════════
|
|
31
|
+
console.log('\n' + chalk.cyanBright.bold('╔════════════════════════════════════╗'));
|
|
32
|
+
console.log(chalk.cyanBright.bold('║ WILFREDWAKE INITIALIZATION WIZARD ║'));
|
|
33
|
+
console.log(chalk.cyanBright.bold('╚════════════════════════════════════╝\n'));
|
|
34
|
+
|
|
35
|
+
logger.info('Welcome to wilfredwake! Let\'s set up your environment.');
|
|
36
|
+
|
|
37
|
+
const configManager = new ConfigManager();
|
|
38
|
+
await configManager.init();
|
|
39
|
+
|
|
40
|
+
// ═══════════════════════════════════════════════════════════════
|
|
41
|
+
// GATHER CONFIGURATION DETAILS
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
const answers = await _promptForConfig(options);
|
|
45
|
+
|
|
46
|
+
logger.section('Validating Configuration');
|
|
47
|
+
|
|
48
|
+
// ═══════════════════════════════════════════════════════════════
|
|
49
|
+
// VALIDATE ORCHESTRATOR URL
|
|
50
|
+
// ═══════════════════════════════════════════════════════════════
|
|
51
|
+
process.stdout.write(
|
|
52
|
+
chalk.dim(`Validating orchestrator at ${answers.orchestrator}... `)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const isReachable = await utils.isUrlReachable(
|
|
56
|
+
`${answers.orchestrator}/health`,
|
|
57
|
+
5000
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (isReachable) {
|
|
61
|
+
logger.success('Orchestrator is reachable!');
|
|
62
|
+
} else {
|
|
63
|
+
logger.warn(
|
|
64
|
+
`Could not reach orchestrator. Make sure it's running at ${answers.orchestrator}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ═══════════════════════════════════════════════════════════════
|
|
69
|
+
// SAVE CONFIGURATION
|
|
70
|
+
// ═══════════════════════════════════════════════════════════════
|
|
71
|
+
logger.section('Saving Configuration');
|
|
72
|
+
|
|
73
|
+
const config = await configManager.loadConfig();
|
|
74
|
+
config.orchestratorUrl = answers.orchestrator;
|
|
75
|
+
if (answers.token) {
|
|
76
|
+
config.token = answers.token;
|
|
77
|
+
}
|
|
78
|
+
config.environment = answers.environment;
|
|
79
|
+
|
|
80
|
+
await configManager.saveConfig(config);
|
|
81
|
+
|
|
82
|
+
logger.success(`Configuration saved to ${configManager.configFile}`);
|
|
83
|
+
|
|
84
|
+
// ═══════════════════════════════════════════════════════════════
|
|
85
|
+
// COMPLETION MESSAGE
|
|
86
|
+
// ═══════════════════════════════════════════════════════════════
|
|
87
|
+
console.log('\n' + chalk.greenBright.bold('✓ Setup Complete!\n'));
|
|
88
|
+
|
|
89
|
+
console.log(chalk.cyan('Configuration Summary:'));
|
|
90
|
+
console.log(chalk.dim(` • Orchestrator: ${answers.orchestrator}`));
|
|
91
|
+
console.log(chalk.dim(` • Environment: ${answers.environment}`));
|
|
92
|
+
if (answers.token) {
|
|
93
|
+
console.log(chalk.dim(` • Token: ${answers.token.substring(0, 10)}...`));
|
|
94
|
+
}
|
|
95
|
+
console.log('');
|
|
96
|
+
|
|
97
|
+
console.log(chalk.cyan('Next steps:'));
|
|
98
|
+
console.log(chalk.yellow(' $ wilfredwake status # Check service status'));
|
|
99
|
+
console.log(chalk.yellow(' $ wilfredwake wake all # Wake all services'));
|
|
100
|
+
console.log(chalk.yellow(' $ wilfredwake --help # View all commands\n'));
|
|
101
|
+
|
|
102
|
+
process.exit(0);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
logger.error(`Initialization failed: ${error.message}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Prompt user for configuration values
|
|
111
|
+
* @private
|
|
112
|
+
* @param {Object} options - Pre-provided options
|
|
113
|
+
* @returns {Promise<Object>} User answers
|
|
114
|
+
*/
|
|
115
|
+
async function _promptForConfig(options) {
|
|
116
|
+
const rl = readline.createInterface({
|
|
117
|
+
input: process.stdin,
|
|
118
|
+
output: process.stdout,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const question = (query) =>
|
|
122
|
+
new Promise((resolve) => {
|
|
123
|
+
rl.question(chalk.blueBright(`? ${query}`), resolve);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════
|
|
128
|
+
// ORCHESTRATOR URL
|
|
129
|
+
// ═══════════════════════════════════════════════════════════════
|
|
130
|
+
let orchestrator = options.orchestrator;
|
|
131
|
+
|
|
132
|
+
if (!orchestrator) {
|
|
133
|
+
console.log(
|
|
134
|
+
chalk.dim(
|
|
135
|
+
'\nFirst, we need the URL of your orchestrator service.'
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
console.log(
|
|
139
|
+
chalk.dim(
|
|
140
|
+
'This is the always-on backend that manages your services.'
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
console.log('');
|
|
144
|
+
|
|
145
|
+
orchestrator = await question(
|
|
146
|
+
`Orchestrator URL ${chalk.gray('(default: http://localhost:3000)')} `
|
|
147
|
+
);
|
|
148
|
+
orchestrator = orchestrator || 'http://localhost:3000';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Validate URL format
|
|
152
|
+
if (!utils.isValidUrl(orchestrator)) {
|
|
153
|
+
throw new Error(`Invalid URL format: ${orchestrator}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ═══════════════════════════════════════════════════════════════
|
|
157
|
+
// ENVIRONMENT SELECTION
|
|
158
|
+
// ═══════════════════════════════════════════════════════════════
|
|
159
|
+
console.log('');
|
|
160
|
+
const envOptions = ['dev', 'staging', 'prod'];
|
|
161
|
+
let environment = 'dev';
|
|
162
|
+
|
|
163
|
+
console.log(chalk.dim('Select your working environment:'));
|
|
164
|
+
envOptions.forEach((env, idx) => {
|
|
165
|
+
console.log(chalk.gray(` ${idx + 1}. ${env}`));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const envChoice = await question('Environment (1-3, default: 1) ');
|
|
169
|
+
const envIdx = parseInt(envChoice || '1') - 1;
|
|
170
|
+
|
|
171
|
+
if (envIdx >= 0 && envIdx < envOptions.length) {
|
|
172
|
+
environment = envOptions[envIdx];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════
|
|
176
|
+
// API TOKEN (Optional)
|
|
177
|
+
// ═══════════════════════════════════════════════════════════════
|
|
178
|
+
console.log('');
|
|
179
|
+
const token = options.token ||
|
|
180
|
+
(await question(
|
|
181
|
+
`API Token ${chalk.gray('(optional, press Enter to skip)')} `
|
|
182
|
+
));
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
orchestrator: orchestrator.trim(),
|
|
186
|
+
token: token ? token.trim() : null,
|
|
187
|
+
environment,
|
|
188
|
+
};
|
|
189
|
+
} finally {
|
|
190
|
+
rl.close();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ╔═══════════════════════════════════════════════════════════════╗
|
|
3
|
+
* ║ STATUS COMMAND - Check Service Status ║
|
|
4
|
+
* ║ Queries orchestrator for real-time service states ║
|
|
5
|
+
* ║ Displays status in table or JSON format ║
|
|
6
|
+
* ╚═══════════════════════════════════════════════════════════════╝
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import axios from 'axios';
|
|
10
|
+
import ConfigManager from '../config.js';
|
|
11
|
+
import { Logger, utils } from '../../shared/logger.js';
|
|
12
|
+
import { colors, format } from '../../shared/colors.js';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
|
|
15
|
+
const logger = new Logger();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check status of services
|
|
19
|
+
* Defaults to showing all services if none specified
|
|
20
|
+
* Can also filter by specific service or service group
|
|
21
|
+
*
|
|
22
|
+
* @param {string} service - Optional service name to filter (defaults to 'all')
|
|
23
|
+
* @param {Object} options - Command options
|
|
24
|
+
* @param {string} options.env - Environment (dev, staging, prod)
|
|
25
|
+
* @param {string} options.format - Output format (table, json)
|
|
26
|
+
*/
|
|
27
|
+
export async function statusCommand(service, options) {
|
|
28
|
+
try {
|
|
29
|
+
// ═══════════════════════════════════════════════════════════════
|
|
30
|
+
// LOAD CONFIGURATION
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════
|
|
32
|
+
const configManager = new ConfigManager();
|
|
33
|
+
const config = await configManager.loadConfig();
|
|
34
|
+
|
|
35
|
+
const env = options.env || config.environment;
|
|
36
|
+
const outputFormat = options.format || config.preferences.outputFormat;
|
|
37
|
+
// DEFAULT: Show all services if none specified
|
|
38
|
+
const serviceFilter = service || 'all';
|
|
39
|
+
|
|
40
|
+
logger.section('Service Status Check');
|
|
41
|
+
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════
|
|
43
|
+
// FETCH STATUS FROM ORCHESTRATOR
|
|
44
|
+
// ═══════════════════════════════════════════════════════════════
|
|
45
|
+
let stopSpinner;
|
|
46
|
+
try {
|
|
47
|
+
stopSpinner = logger.spinner('Fetching service status...');
|
|
48
|
+
|
|
49
|
+
const response = await axios.get(
|
|
50
|
+
`${config.orchestratorUrl}/api/status`,
|
|
51
|
+
{
|
|
52
|
+
params: {
|
|
53
|
+
environment: env,
|
|
54
|
+
service: serviceFilter !== 'all' ? serviceFilter : undefined,
|
|
55
|
+
},
|
|
56
|
+
timeout: 10000,
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
stopSpinner();
|
|
61
|
+
console.log(''); // New line after spinner
|
|
62
|
+
|
|
63
|
+
const services = response.data.services || [];
|
|
64
|
+
|
|
65
|
+
// ═══════════════════════════════════════════════════════════════
|
|
66
|
+
// DISPLAY RESULTS
|
|
67
|
+
// ═══════════════════════════════════════════════════════════════
|
|
68
|
+
|
|
69
|
+
if (outputFormat === 'json') {
|
|
70
|
+
_displayJsonStatus(services);
|
|
71
|
+
} else {
|
|
72
|
+
_displayTableStatus(services, env);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ═══════════════════════════════════════════════════════════════
|
|
76
|
+
// SUMMARY STATISTICS
|
|
77
|
+
// ═══════════════════════════════════════════════════════════════
|
|
78
|
+
_displaySummary(services);
|
|
79
|
+
|
|
80
|
+
process.exit(0);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
stopSpinner?.();
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
logger.error(`Failed to fetch status: ${error.message}`);
|
|
87
|
+
|
|
88
|
+
if (error.code === 'ECONNREFUSED') {
|
|
89
|
+
console.log(
|
|
90
|
+
chalk.yellowBright(
|
|
91
|
+
'\n⚠ Could not connect to orchestrator. Make sure it\'s running.'
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
console.log(chalk.dim('Run: wilfredwake init'));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Display status in table format with colors
|
|
103
|
+
* @private
|
|
104
|
+
* @param {Array} services - Services array
|
|
105
|
+
* @param {string} environment - Environment name
|
|
106
|
+
*/
|
|
107
|
+
function _displayTableStatus(services, environment) {
|
|
108
|
+
if (services.length === 0) {
|
|
109
|
+
logger.info('No services found in registry.');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(chalk.cyanBright.bold(`\n📊 Services (${environment.toUpperCase()})\n`));
|
|
114
|
+
|
|
115
|
+
// ═══════════════════════════════════════════════════════════════
|
|
116
|
+
// TABLE HEADER
|
|
117
|
+
// ═══════════════════════════════════════════════════════════════
|
|
118
|
+
const headers = ['Service', 'Status', 'Last Woken', 'URL'];
|
|
119
|
+
console.log(format.tableHeader(headers));
|
|
120
|
+
|
|
121
|
+
// ═══════════════════════════════════════════════════════════════
|
|
122
|
+
// TABLE ROWS
|
|
123
|
+
// ═══════════════════════════════════════════════════════════════
|
|
124
|
+
services.forEach((service) => {
|
|
125
|
+
const statusColor = colors.status[service.status] || colors.status.unknown;
|
|
126
|
+
const lastWoken = service.lastWakeTime
|
|
127
|
+
? new Date(service.lastWakeTime).toLocaleString()
|
|
128
|
+
: 'Never';
|
|
129
|
+
|
|
130
|
+
const cells = [
|
|
131
|
+
chalk.cyan(service.name.padEnd(20)),
|
|
132
|
+
statusColor(service.status.toUpperCase().padEnd(20)),
|
|
133
|
+
chalk.yellow(lastWoken.padEnd(20)),
|
|
134
|
+
chalk.gray(service.url.substring(0, 20).padEnd(20)),
|
|
135
|
+
];
|
|
136
|
+
console.log(format.tableRow(cells));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
console.log(''); // Spacing
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Display status in JSON format
|
|
144
|
+
* @private
|
|
145
|
+
* @param {Array} services - Services array
|
|
146
|
+
*/
|
|
147
|
+
function _displayJsonStatus(services) {
|
|
148
|
+
console.log(JSON.stringify(services, null, 2));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Display summary statistics
|
|
153
|
+
* @private
|
|
154
|
+
* @param {Array} services - Services array
|
|
155
|
+
*/
|
|
156
|
+
function _displaySummary(services) {
|
|
157
|
+
if (services.length === 0) return;
|
|
158
|
+
|
|
159
|
+
const stats = {
|
|
160
|
+
total: services.length,
|
|
161
|
+
ready: services.filter(s => s.status === 'ready').length,
|
|
162
|
+
sleeping: services.filter(s => s.status === 'sleeping').length,
|
|
163
|
+
waking: services.filter(s => s.status === 'waking').length,
|
|
164
|
+
failed: services.filter(s => s.status === 'failed').length,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
console.log(chalk.magentaBright.bold('Summary'));
|
|
168
|
+
console.log(
|
|
169
|
+
` ${colors.status.ready('✓')} Ready: ${colors.status.ready(
|
|
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
|
+
)}`
|
|
178
|
+
);
|
|
179
|
+
console.log(` Total: ${stats.total} services\n`);
|
|
180
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ╔═══════════════════════════════════════════════════════════════╗
|
|
3
|
+
* ║ WAKE COMMAND - Wake Services On Demand ║
|
|
4
|
+
* ║ Initiates service wake with dependency ordering ║
|
|
5
|
+
* ║ Polls health checks until ready or timeout ║
|
|
6
|
+
* ╚═══════════════════════════════════════════════════════════════╝
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import axios from 'axios';
|
|
10
|
+
import ConfigManager from '../config.js';
|
|
11
|
+
import { Logger, utils } from '../../shared/logger.js';
|
|
12
|
+
import { colors, format } from '../../shared/colors.js';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
|
|
15
|
+
const logger = new Logger();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Wake services on demand
|
|
19
|
+
* Defaults to waking all services if none specified
|
|
20
|
+
* Can also wake specific service or service group
|
|
21
|
+
* Respects dependency order and waits for readiness
|
|
22
|
+
*
|
|
23
|
+
* @param {string} target - Wake target (defaults to 'all', or <service>, or <group>)
|
|
24
|
+
* @param {Object} options - Command options
|
|
25
|
+
* @param {string} options.env - Environment (dev, staging, prod)
|
|
26
|
+
* @param {boolean} options.wait - Wait for services to be ready
|
|
27
|
+
* @param {string} options.timeout - Timeout in seconds
|
|
28
|
+
*/
|
|
29
|
+
export async function wakeCommand(target, options) {
|
|
30
|
+
try {
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════
|
|
32
|
+
// LOAD CONFIGURATION
|
|
33
|
+
// ═══════════════════════════════════════════════════════════════
|
|
34
|
+
const configManager = new ConfigManager();
|
|
35
|
+
const config = await configManager.loadConfig();
|
|
36
|
+
|
|
37
|
+
const env = options.env || config.environment;
|
|
38
|
+
const shouldWait = options.wait !== false;
|
|
39
|
+
const timeout = parseInt(options.timeout || config.preferences.timeout);
|
|
40
|
+
// DEFAULT: Wake all services if none specified
|
|
41
|
+
const wakeTarget = target || 'all';
|
|
42
|
+
|
|
43
|
+
logger.section('Service Wake Request');
|
|
44
|
+
|
|
45
|
+
logger.info(
|
|
46
|
+
`Waking ${chalk.cyan(wakeTarget)} in ${chalk.yellow(env)} environment...`
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// ═══════════════════════════════════════════════════════════════
|
|
50
|
+
// SEND WAKE REQUEST TO ORCHESTRATOR
|
|
51
|
+
// ═══════════════════════════════════════════════════════════════
|
|
52
|
+
let stopSpinner;
|
|
53
|
+
try {
|
|
54
|
+
stopSpinner = logger.spinner(
|
|
55
|
+
`Sending wake request for ${chalk.cyan(wakeTarget)}...`
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const response = await axios.post(
|
|
59
|
+
`${config.orchestratorUrl}/api/wake`,
|
|
60
|
+
{
|
|
61
|
+
target: wakeTarget,
|
|
62
|
+
environment: env,
|
|
63
|
+
wait: shouldWait,
|
|
64
|
+
timeout,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
timeout: (timeout + 10) * 1000,
|
|
68
|
+
headers: {
|
|
69
|
+
Authorization: config.token ? `Bearer ${config.token}` : undefined,
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
stopSpinner();
|
|
75
|
+
console.log(''); // New line after spinner
|
|
76
|
+
|
|
77
|
+
// ═══════════════════════════════════════════════════════════════
|
|
78
|
+
// PROCESS WAKE RESPONSE
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════
|
|
80
|
+
const results = response.data;
|
|
81
|
+
|
|
82
|
+
_displayWakeResults(results);
|
|
83
|
+
|
|
84
|
+
// ═══════════════════════════════════════════════════════════════
|
|
85
|
+
// DISPLAY FINAL STATUS
|
|
86
|
+
// ═══════════════════════════════════════════════════════════════
|
|
87
|
+
if (shouldWait) {
|
|
88
|
+
_displayFinalStatus(results);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
process.exit(results.success ? 0 : 1);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
stopSpinner?.();
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error.response?.status === 404) {
|
|
98
|
+
logger.error(`Service not found: ${error.response.data.message}`);
|
|
99
|
+
} else if (error.code === 'ECONNREFUSED') {
|
|
100
|
+
logger.error('Could not connect to orchestrator. Is it running?');
|
|
101
|
+
console.log(chalk.dim('Run: wilfredwake init'));
|
|
102
|
+
} else {
|
|
103
|
+
logger.error(`Wake request failed: ${error.message}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Display wake operation results
|
|
112
|
+
* @private
|
|
113
|
+
* @param {Object} results - Wake results from orchestrator
|
|
114
|
+
*/
|
|
115
|
+
function _displayWakeResults(results) {
|
|
116
|
+
console.log(chalk.cyanBright.bold('\n🌅 Wake Operation Results\n'));
|
|
117
|
+
|
|
118
|
+
if (!results.services || results.services.length === 0) {
|
|
119
|
+
logger.info('No services to wake.');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ═══════════════════════════════════════════════════════════════
|
|
124
|
+
// SERVICE WAKE TIMELINE
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════
|
|
126
|
+
console.log(chalk.cyan('Timeline:'));
|
|
127
|
+
|
|
128
|
+
results.services.forEach((service, index) => {
|
|
129
|
+
const duration = service.duration
|
|
130
|
+
? ` (${utils.formatDuration(service.duration)})`
|
|
131
|
+
: '';
|
|
132
|
+
|
|
133
|
+
let statusIndicator;
|
|
134
|
+
if (service.status === 'live') {
|
|
135
|
+
statusIndicator = chalk.greenBright('✓');
|
|
136
|
+
} else if (service.status === 'failed') {
|
|
137
|
+
statusIndicator = chalk.redBright('✗');
|
|
138
|
+
} else {
|
|
139
|
+
statusIndicator = chalk.yellowBright('⟳');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const arrow = index < results.services.length - 1 ? '│' : '└';
|
|
143
|
+
console.log(
|
|
144
|
+
` ${chalk.dim(arrow)} ${statusIndicator} ${chalk.cyan(
|
|
145
|
+
service.name
|
|
146
|
+
)}${chalk.yellow(duration)}`
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (service.error) {
|
|
150
|
+
console.log(` ${chalk.redBright(`Error: ${service.error}`)}`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
console.log('');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Display final status summary
|
|
159
|
+
* @private
|
|
160
|
+
* @param {Object} results - Wake results from orchestrator
|
|
161
|
+
*/
|
|
162
|
+
function _displayFinalStatus(results) {
|
|
163
|
+
const succeeded = results.services.filter(s => s.status === 'live').length;
|
|
164
|
+
const failed = results.services.filter(s => s.status === 'failed').length;
|
|
165
|
+
const totalTime = utils.formatDuration(results.totalDuration || 0);
|
|
166
|
+
|
|
167
|
+
console.log(chalk.magentaBright.bold('✓ Wake Complete\n'));
|
|
168
|
+
|
|
169
|
+
console.log(` ${colors.status.live('✓')} Live: ${colors.status.live(succeeded)}`);
|
|
170
|
+
if (failed > 0) {
|
|
171
|
+
console.log(` ${colors.status.failed('✗')} Failed: ${colors.status.failed(failed)}`);
|
|
172
|
+
}
|
|
173
|
+
console.log(` ${chalk.yellow('⏱')} Total Time: ${chalk.yellow(totalTime)}\n`);
|
|
174
|
+
|
|
175
|
+
if (results.success) {
|
|
176
|
+
logger.success('All services are live!');
|
|
177
|
+
} else {
|
|
178
|
+
logger.warn('Some services failed to wake. Check errors above.');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log('');
|
|
182
|
+
}
|