@oussema_mili/test-pkg-123 1.1.22

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.

Potentially problematic release.


This version of @oussema_mili/test-pkg-123 might be problematic. Click here for more details.

Files changed (49) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +220 -0
  3. package/auth-callback.html +97 -0
  4. package/auth.js +276 -0
  5. package/cli-commands.js +1923 -0
  6. package/containerManager.js +304 -0
  7. package/daemon/agentRunner.js +429 -0
  8. package/daemon/daemonEntry.js +64 -0
  9. package/daemon/daemonManager.js +271 -0
  10. package/daemon/logManager.js +227 -0
  11. package/dist/styles.css +504 -0
  12. package/docker-actions/apps.js +3938 -0
  13. package/docker-actions/config-transformer.js +380 -0
  14. package/docker-actions/containers.js +355 -0
  15. package/docker-actions/general.js +171 -0
  16. package/docker-actions/images.js +1128 -0
  17. package/docker-actions/logs.js +224 -0
  18. package/docker-actions/metrics.js +270 -0
  19. package/docker-actions/registry.js +1100 -0
  20. package/docker-actions/setup-tasks.js +859 -0
  21. package/docker-actions/terminal.js +247 -0
  22. package/docker-actions/volumes.js +696 -0
  23. package/helper-functions.js +193 -0
  24. package/index.html +83 -0
  25. package/index.js +341 -0
  26. package/package.json +82 -0
  27. package/postcss.config.mjs +5 -0
  28. package/scripts/release.sh +212 -0
  29. package/setup/setupWizard.js +403 -0
  30. package/store/agentSessionStore.js +51 -0
  31. package/store/agentStore.js +113 -0
  32. package/store/configStore.js +171 -0
  33. package/store/daemonStore.js +217 -0
  34. package/store/deviceCredentialStore.js +107 -0
  35. package/store/npmTokenStore.js +65 -0
  36. package/store/registryStore.js +329 -0
  37. package/store/setupState.js +147 -0
  38. package/styles.css +1 -0
  39. package/utils/appLogger.js +223 -0
  40. package/utils/deviceInfo.js +98 -0
  41. package/utils/ecrAuth.js +225 -0
  42. package/utils/encryption.js +112 -0
  43. package/utils/envSetup.js +44 -0
  44. package/utils/errorHandler.js +327 -0
  45. package/utils/portUtils.js +59 -0
  46. package/utils/prerequisites.js +323 -0
  47. package/utils/prompts.js +318 -0
  48. package/utils/ssl-certificates.js +256 -0
  49. package/websocket-server.js +415 -0
@@ -0,0 +1,429 @@
1
+ import http from 'http';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import chalk from 'chalk';
6
+ import axios from 'axios';
7
+ import { v4 as uuidv4 } from 'uuid';
8
+
9
+ import { setupWebSocketServer, readWsToken } from '../websocket-server.js';
10
+ import containerManager from '../containerManager.js';
11
+ import registryStore from '../store/registryStore.js';
12
+ import agentStore from '../store/agentStore.js';
13
+ import { loadConfig } from '../store/configStore.js';
14
+ import { initializeAgentStartTime } from '../docker-actions/general.js';
15
+ import { checkAppHasBeenRun } from '../docker-actions/apps.js';
16
+ import {
17
+ loadSession,
18
+ isSessionValid,
19
+ handleSessionExpiry,
20
+ setupSessionWatcher,
21
+ } from '../auth.js';
22
+ import { findAvailablePort } from '../utils/portUtils.js';
23
+ import {
24
+ saveDaemonState,
25
+ updateDaemonState,
26
+ clearDaemonState,
27
+ saveDaemonPid,
28
+ clearDaemonPid,
29
+ acquireLock,
30
+ releaseLock,
31
+ } from '../store/daemonStore.js';
32
+ import { createLogger, writeLog } from './logManager.js';
33
+
34
+ // Load configuration
35
+ const config = loadConfig();
36
+ const BACKEND_URL = config.backendUrl;
37
+ const CONTAINER_PORT = config.containerPort;
38
+
39
+ // Store active connections
40
+ const clients = new Map();
41
+
42
+ // Server instances for cleanup
43
+ let serverInstances = null;
44
+
45
+ // Logger for daemon mode
46
+ const logger = createLogger();
47
+
48
+ /**
49
+ * Log message to both console and file
50
+ */
51
+ function log(message, level = 'info') {
52
+ const coloredMessage =
53
+ level === 'error'
54
+ ? chalk.red(message)
55
+ : level === 'warn'
56
+ ? chalk.yellow(message)
57
+ : chalk.blue(message);
58
+
59
+ console.log(coloredMessage);
60
+ writeLog(message, level);
61
+ }
62
+
63
+ /**
64
+ * Acknowledge a version_published event
65
+ */
66
+ async function acknowledgeVersionPublishedEvent(sessionToken, eventId, changeId) {
67
+ try {
68
+ await axios.post(
69
+ `${BACKEND_URL}/api/agent-cli/acknowledge-event`,
70
+ {
71
+ token: sessionToken,
72
+ eventId: eventId,
73
+ changeId: changeId,
74
+ },
75
+ {
76
+ headers: { 'Content-Type': 'application/json' },
77
+ timeout: 5000,
78
+ }
79
+ );
80
+ } catch (error) {
81
+ log(`Failed to acknowledge event ${changeId}: ${error.message}`, 'warn');
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Poll for app update events and broadcast to connected clients
87
+ */
88
+ async function pollAppUpdates(sessionToken, wss) {
89
+ try {
90
+ const response = await axios.post(
91
+ `${BACKEND_URL}/api/agent-cli/poll-events`,
92
+ { token: sessionToken },
93
+ {
94
+ headers: { 'Content-Type': 'application/json' },
95
+ timeout: 10000,
96
+ }
97
+ );
98
+
99
+ if (response.data?.events?.length > 0) {
100
+ const events = response.data.events;
101
+ let broadcastedCount = 0;
102
+
103
+ for (const event of events) {
104
+ const eventType = event.event_type || 'blueprint_changed';
105
+
106
+ if (eventType === 'blueprint_changed') {
107
+ const hasBeenRun = await checkAppHasBeenRun(event.app_name);
108
+ if (!hasBeenRun) {
109
+ acknowledgeVersionPublishedEvent(sessionToken, event.id, event.change_id);
110
+ continue;
111
+ }
112
+ }
113
+
114
+ broadcastedCount++;
115
+
116
+ const message = JSON.stringify({
117
+ type: eventType,
118
+ appId: event.app_id,
119
+ fwId: event.app_id,
120
+ appName: event.app_name,
121
+ version: event.version,
122
+ changeId: event.change_id,
123
+ timestamp: event.created_at,
124
+ actor: event.actor,
125
+ changedAt: event.created_at,
126
+ });
127
+
128
+ wss.clients.forEach((client) => {
129
+ if (client.readyState === 1) {
130
+ client.send(message);
131
+ }
132
+ });
133
+
134
+ if (eventType === 'version_published') {
135
+ setTimeout(() => {
136
+ acknowledgeVersionPublishedEvent(sessionToken, event.id, event.change_id);
137
+ }, 2000);
138
+ }
139
+ }
140
+
141
+ if (broadcastedCount > 0) {
142
+ log(
143
+ `Received ${broadcastedCount} app update ${broadcastedCount === 1 ? "event" : "events"}`,
144
+ );
145
+ }
146
+ }
147
+ } catch (error) {
148
+ if (error.response?.status !== 401) {
149
+ const isConnRefused =
150
+ error.message?.includes('ECONNREFUSED') || error.code === 'ECONNREFUSED';
151
+
152
+ if (isConnRefused) {
153
+ log('App update polling error: Please ensure Fenwave is running.', 'error');
154
+ } else {
155
+ log(`App update polling error: ${error.message}`, 'error');
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Start periodic polling for app updates
163
+ */
164
+ function startAppUpdatePolling(sessionToken, wss) {
165
+ const pollInterval = setInterval(() => {
166
+ pollAppUpdates(sessionToken, wss);
167
+ }, 30000);
168
+
169
+ log('Started polling for app updates (every 30 seconds)');
170
+ return pollInterval;
171
+ }
172
+
173
+ /**
174
+ * Enhanced graceful shutdown with connection draining
175
+ */
176
+ async function gracefulShutdown(signal = 'SIGTERM', drainTimeout = 10000) {
177
+ log(`Agent received ${signal}, initiating graceful shutdown...`);
178
+
179
+ // Update daemon state to indicate shutdown
180
+ updateDaemonState({ status: 'shutting_down' });
181
+
182
+ if (serverInstances) {
183
+ // Clear polling interval
184
+ if (serverInstances.pollInterval) {
185
+ clearInterval(serverInstances.pollInterval);
186
+ }
187
+
188
+ // Close session watcher
189
+ if (serverInstances.sessionWatcher) {
190
+ serverInstances.sessionWatcher.close();
191
+ }
192
+
193
+ // Notify clients about shutdown with reconnect hint
194
+ if (serverInstances.wss) {
195
+ const shutdownMessage = JSON.stringify({
196
+ type: 'agent_shutdown',
197
+ message: 'Agent is shutting down',
198
+ reconnectAfter: 5000, // Hint to reconnect after 5 seconds
199
+ });
200
+
201
+ serverInstances.wss.clients.forEach((client) => {
202
+ try {
203
+ client.send(shutdownMessage);
204
+ } catch (e) {
205
+ // Ignore send errors during shutdown
206
+ }
207
+ });
208
+
209
+ // Wait for active operations to complete (connection draining)
210
+ log(`Waiting up to ${drainTimeout / 1000}s for active operations...`);
211
+
212
+ await new Promise((resolve) => {
213
+ const drainTimer = setTimeout(resolve, drainTimeout);
214
+
215
+ // Check if all clients have disconnected
216
+ const checkInterval = setInterval(() => {
217
+ if (serverInstances.wss.clients.size === 0) {
218
+ clearInterval(checkInterval);
219
+ clearTimeout(drainTimer);
220
+ resolve();
221
+ }
222
+ }, 500);
223
+ });
224
+
225
+ // Close remaining connections
226
+ serverInstances.wss.clients.forEach((client) => {
227
+ client.close();
228
+ });
229
+ serverInstances.wss.close();
230
+ }
231
+
232
+ // Close HTTP server
233
+ if (serverInstances.server) {
234
+ serverInstances.server.close();
235
+ }
236
+ }
237
+
238
+ // Clear agent info
239
+ try {
240
+ await agentStore.clearAgentInfo();
241
+ } catch (err) {
242
+ log(`Failed to clear agent info: ${err.message}`, 'warn');
243
+ }
244
+
245
+ // Stop container
246
+ try {
247
+ await containerManager.stopContainerGracefully();
248
+ } catch (error) {
249
+ log(`Error stopping container: ${error.message}`, 'warn');
250
+ }
251
+
252
+ // Clean up daemon files
253
+ clearDaemonState();
254
+ clearDaemonPid();
255
+ releaseLock();
256
+
257
+ log('Agent shutdown complete');
258
+ process.exit(0);
259
+ }
260
+
261
+ /**
262
+ * Run the agent in the current process
263
+ * @param {Object} options - Options
264
+ * @param {number} options.preferredPort - Preferred WebSocket port
265
+ * @param {boolean} options.isDaemon - Whether running as daemon
266
+ * @returns {Promise<Object>} Server instances
267
+ */
268
+ export async function runAgent(options = {}) {
269
+ const { preferredPort = config.wsPort, isDaemon = false } = options;
270
+
271
+ // Check for existing session
272
+ log('Checking for existing session...');
273
+ const session = loadSession();
274
+
275
+ if (!session) {
276
+ log('No session found. Please run "fenwave login" first.', 'error');
277
+ throw new Error('No session found. Please run "fenwave login" first.');
278
+ }
279
+
280
+ if (!isSessionValid(session)) {
281
+ log('Session has expired. Please run "fenwave login" again.', 'error');
282
+ throw new Error('Session expired. Please run "fenwave login" again.');
283
+ }
284
+
285
+ log(`Found valid session for ${session.userEntityRef}`);
286
+
287
+ // Acquire lock for single instance enforcement
288
+ if (!acquireLock()) {
289
+ log('Another instance of the agent is already running.', 'error');
290
+ throw new Error('Another instance of the agent is already running.');
291
+ }
292
+
293
+ // Find available port
294
+ let actualPort;
295
+ try {
296
+ actualPort = await findAvailablePort(preferredPort);
297
+ if (actualPort !== preferredPort) {
298
+ log(`Port ${preferredPort} is in use, using port ${actualPort}`, 'warn');
299
+ }
300
+ } catch (error) {
301
+ releaseLock();
302
+ throw error;
303
+ }
304
+
305
+ // Initialize registry store
306
+ await registryStore.initialize();
307
+
308
+ // Start container
309
+ log('Starting container...');
310
+ try {
311
+ await containerManager.startContainer();
312
+ log('Container started successfully');
313
+ } catch (containerError) {
314
+ log(`Failed to start container: ${containerError.message}`, 'warn');
315
+ log('Starting agent without container...');
316
+ }
317
+
318
+ // Create HTTP server
319
+ const agentId = uuidv4();
320
+ const existingWsToken = readWsToken();
321
+ const server = http.createServer();
322
+
323
+ return new Promise((resolve, reject) => {
324
+ server.listen(actualPort, async () => {
325
+ try {
326
+ // Initialize agent start time
327
+ await initializeAgentStartTime();
328
+
329
+ // Save daemon state
330
+ saveDaemonPid(process.pid);
331
+ saveDaemonState({
332
+ pid: process.pid,
333
+ port: actualPort,
334
+ startTime: new Date().toISOString(),
335
+ status: 'running',
336
+ userEntityRef: session.userEntityRef,
337
+ isDaemon,
338
+ });
339
+
340
+ // Setup WebSocket server
341
+ const wss = setupWebSocketServer(
342
+ server,
343
+ clients,
344
+ agentId,
345
+ {
346
+ userEntityRef: session.userEntityRef,
347
+ expiresAt: session.expiresAt,
348
+ },
349
+ existingWsToken
350
+ );
351
+
352
+ // Setup session watcher
353
+ const sessionWatcher = setupSessionWatcher(handleSessionExpiry, server, wss);
354
+
355
+ // Start app update polling
356
+ const pollInterval = startAppUpdatePolling(session.token, wss);
357
+
358
+ // Store server instances for cleanup
359
+ serverInstances = {
360
+ server,
361
+ wss,
362
+ sessionToken: session.token,
363
+ userEntityRef: session.userEntityRef,
364
+ agentId,
365
+ sessionWatcher,
366
+ pollInterval,
367
+ };
368
+
369
+ // Display startup info
370
+ console.log(
371
+ chalk.green('\n' + '='.repeat(66))
372
+ );
373
+ console.log(chalk.green(' Fenwave Agent Started Successfully'));
374
+ console.log(chalk.green('='.repeat(66) + '\n'));
375
+ console.log(chalk.white(' Fenwave DevApp Dashboard:'));
376
+ console.log(chalk.cyan(` http://localhost:${CONTAINER_PORT}\n`));
377
+ console.log(chalk.white(' WebSocket Server:'));
378
+ console.log(chalk.cyan(` ws://localhost:${actualPort}\n`));
379
+ console.log(chalk.white(' User:'));
380
+ console.log(chalk.cyan(` ${session.userEntityRef}\n`));
381
+ if (isDaemon) {
382
+ console.log(chalk.white(' Mode:'));
383
+ console.log(chalk.cyan(' Background daemon\n'));
384
+ }
385
+ console.log(chalk.green('='.repeat(66) + '\n'));
386
+
387
+ log('Agent is ready to receive connections');
388
+
389
+ resolve(serverInstances);
390
+ } catch (error) {
391
+ log(`Failed to setup WebSocket server: ${error.message}`, 'error');
392
+ releaseLock();
393
+ reject(error);
394
+ }
395
+ });
396
+
397
+ server.on('error', (error) => {
398
+ log(`Server error: ${error.message}`, 'error');
399
+ releaseLock();
400
+ reject(error);
401
+ });
402
+ });
403
+ }
404
+
405
+ /**
406
+ * Setup signal handlers for graceful shutdown
407
+ */
408
+ export function setupSignalHandlers() {
409
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
410
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
411
+ }
412
+
413
+ /**
414
+ * Get current server instances
415
+ * @returns {Object|null} Server instances
416
+ */
417
+ export function getServerInstances() {
418
+ return serverInstances;
419
+ }
420
+
421
+ export { clients, gracefulShutdown };
422
+
423
+ export default {
424
+ runAgent,
425
+ setupSignalHandlers,
426
+ getServerInstances,
427
+ gracefulShutdown,
428
+ clients,
429
+ };
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Daemon Entry Point
5
+ *
6
+ * This script is spawned as a detached child process when running
7
+ * "fenwave service start". It runs the agent in daemon mode.
8
+ */
9
+
10
+ import { runAgent, setupSignalHandlers } from './agentRunner.js';
11
+ import { writeLog } from './logManager.js';
12
+ import { loadConfig } from '../store/configStore.js';
13
+
14
+ // Parse command line arguments
15
+ function parseArgs() {
16
+ const args = process.argv.slice(2);
17
+ const options = {};
18
+
19
+ for (let i = 0; i < args.length; i++) {
20
+ if (args[i] === '--port' && args[i + 1]) {
21
+ options.port = parseInt(args[i + 1], 10);
22
+ i++;
23
+ }
24
+ }
25
+
26
+ return options;
27
+ }
28
+
29
+ async function main() {
30
+ const options = parseArgs();
31
+ const config = loadConfig();
32
+
33
+ writeLog('Daemon starting...');
34
+ writeLog(`PID: ${process.pid}`);
35
+ writeLog(`Preferred port: ${options.port || config.wsPort}`);
36
+
37
+ // Setup signal handlers
38
+ setupSignalHandlers();
39
+
40
+ // Handle uncaught exceptions
41
+ process.on('uncaughtException', (error) => {
42
+ writeLog(`Uncaught exception: ${error.message}`, 'error');
43
+ writeLog(error.stack, 'error');
44
+ process.exit(1);
45
+ });
46
+
47
+ process.on('unhandledRejection', (reason, promise) => {
48
+ writeLog(`Unhandled rejection: ${reason}`, 'error');
49
+ });
50
+
51
+ try {
52
+ await runAgent({
53
+ preferredPort: options.port || config.wsPort,
54
+ isDaemon: true,
55
+ });
56
+
57
+ writeLog('Daemon started successfully');
58
+ } catch (error) {
59
+ writeLog(`Daemon failed to start: ${error.message}`, 'error');
60
+ process.exit(1);
61
+ }
62
+ }
63
+
64
+ main();