@oussema_mili/test-pkg-123 1.1.42 → 1.1.44

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.
@@ -2,9 +2,11 @@ import os from 'os';
2
2
  import { spawn, exec } from 'child_process';
3
3
  import path from 'path';
4
4
  import chalk from 'chalk';
5
- import { loadConfig } from './store/configStore.js';
5
+ import { loadConfig, DEFAULT_WS_PORT, DEFAULT_CONTAINER_PORT } from './store/configStore.js';
6
+ import { getDaemonState } from './store/daemonStore.js';
7
+ import { findAvailableContainerPort, isPortAvailable } from './utils/portUtils.js';
6
8
 
7
- // Load configuration
9
+ // Load static configuration (ports are resolved dynamically)
8
10
  const config = loadConfig();
9
11
  const CONTAINER_NAME = config.containerName;
10
12
  const AGENT_ROOT_DIR = config.agentRootDir;
@@ -13,10 +15,24 @@ const HOST_DATA_DIR = path.join(os.homedir(), AGENT_ROOT_DIR, REGISTRIES_DIR);
13
15
  const HOST_FW_DIR = path.join(os.homedir(), AGENT_ROOT_DIR);
14
16
  const CONTAINER_DATA_DIR = config.containerDataDir;
15
17
  const CONTAINER_FW_DIR = '/app/.fenwave';
16
- const APP_PORT = config.containerPort;
17
- const WS_PORT = config.wsPort;
18
18
  const DOCKER_IMAGE = config.dockerImage;
19
19
 
20
+ /**
21
+ * Get the current WebSocket port (from daemon state or config)
22
+ */
23
+ function getCurrentWsPort() {
24
+ const state = getDaemonState();
25
+ return state?.port || config.wsPort || DEFAULT_WS_PORT;
26
+ }
27
+
28
+ /**
29
+ * Get the current container port (from daemon state or config)
30
+ */
31
+ function getCurrentContainerPort() {
32
+ const state = getDaemonState();
33
+ return state?.containerPort || config.containerPort || DEFAULT_CONTAINER_PORT;
34
+ }
35
+
20
36
  /**
21
37
  * Container Manager for Fenwave DevApp
22
38
  */
@@ -147,9 +163,37 @@ class ContainerManager {
147
163
 
148
164
  /**
149
165
  * Start the Fenwave DevApp container
166
+ * @param {Object} options - Start options
167
+ * @param {number} options.wsPort - WebSocket port to connect to
168
+ * @param {number} options.containerPort - Port to expose the container on (0 for auto-assign)
150
169
  */
151
- async startContainer() {
170
+ async startContainer(options = {}) {
152
171
  try {
172
+ // Get ports - use provided values, daemon state, config, or defaults
173
+ const WS_PORT = options.wsPort || getCurrentWsPort();
174
+ let APP_PORT = options.containerPort || getCurrentContainerPort();
175
+
176
+ // If containerPort is 0, find an available port
177
+ if (APP_PORT === 0) {
178
+ try {
179
+ APP_PORT = await findAvailableContainerPort(DEFAULT_CONTAINER_PORT, 100, [WS_PORT]);
180
+ console.log(chalk.blue(`📍 Auto-assigned container port: ${APP_PORT}`));
181
+ } catch (error) {
182
+ throw new Error(`Failed to find available container port: ${error.message}`);
183
+ }
184
+ }
185
+
186
+ // If the desired container port conflicts with the WebSocket port or is unavailable, pick another port
187
+ if (APP_PORT === WS_PORT || !(await isPortAvailable(APP_PORT))) {
188
+ try {
189
+ const newPort = await findAvailableContainerPort(DEFAULT_CONTAINER_PORT, 100, [WS_PORT]);
190
+ console.log(chalk.yellow(`⚠️ Desired container port ${APP_PORT} is unavailable or conflicts with WebSocket port ${WS_PORT}. Using ${newPort} instead.`));
191
+ APP_PORT = newPort;
192
+ } catch (err) {
193
+ throw new Error(`Failed to resolve container port conflict: ${err.message}`);
194
+ }
195
+ }
196
+
153
197
  // Check Docker availability
154
198
  const dockerAvailable = await this.checkDockerAvailable();
155
199
  if (!dockerAvailable) {
@@ -223,13 +267,14 @@ class ContainerManager {
223
267
  runProcess.on('close', (code) => {
224
268
  if (code === 0) {
225
269
  this.isRunning = true;
270
+ this.currentContainerPort = APP_PORT; // Store the actual port used
226
271
  console.log(chalk.green('✅ Container started successfully'));
227
272
  console.log(chalk.gray(` Image: ${DOCKER_IMAGE}`));
228
273
  console.log(chalk.gray(` Container: ${CONTAINER_NAME}`));
229
274
  console.log(chalk.gray(` Port: ${APP_PORT}`));
230
275
  console.log(chalk.gray(` Data volume: ${HOST_DATA_DIR} -> ${CONTAINER_DATA_DIR}`));
231
276
  console.log(chalk.gray(` Token volume: ${HOST_FW_DIR} -> ${CONTAINER_FW_DIR}`));
232
- resolve();
277
+ resolve(APP_PORT); // Return the actual port used
233
278
  } else {
234
279
  console.error(chalk.red('❌ Failed to start container:'));
235
280
  if (errorOutput) {
@@ -275,11 +320,19 @@ class ContainerManager {
275
320
  return {
276
321
  isRunning: running,
277
322
  containerName: CONTAINER_NAME,
278
- port: APP_PORT,
323
+ port: this.currentContainerPort || getCurrentContainerPort(),
279
324
  dataDirectory: HOST_DATA_DIR,
280
325
  };
281
326
  }
282
327
 
328
+ /**
329
+ * Get the current container port
330
+ * @returns {number} The current container port
331
+ */
332
+ getContainerPort() {
333
+ return this.currentContainerPort || getCurrentContainerPort();
334
+ }
335
+
283
336
  /**
284
337
  * Show container logs
285
338
  */
@@ -10,7 +10,7 @@ import { setupWebSocketServer, readWsToken } from '../websocket-server.js';
10
10
  import containerManager from '../containerManager.js';
11
11
  import registryStore from '../store/registryStore.js';
12
12
  import agentStore from '../store/agentStore.js';
13
- import { loadConfig } from '../store/configStore.js';
13
+ import { loadConfig, DEFAULT_WS_PORT, DEFAULT_CONTAINER_PORT } from '../store/configStore.js';
14
14
  import { initializeAgentStartTime } from '../docker-actions/general.js';
15
15
  import { checkAppHasBeenRun } from '../docker-actions/apps.js';
16
16
  import {
@@ -34,7 +34,6 @@ import { createLogger, writeLog } from './logManager.js';
34
34
  // Load configuration
35
35
  const config = loadConfig();
36
36
  const BACKEND_URL = config.backendUrl;
37
- const CONTAINER_PORT = config.containerPort;
38
37
 
39
38
  // Store active connections
40
39
  const clients = new Map();
@@ -261,12 +260,17 @@ async function gracefulShutdown(signal = 'SIGTERM', drainTimeout = 10000) {
261
260
  /**
262
261
  * Run the agent in the current process
263
262
  * @param {Object} options - Options
264
- * @param {number} options.preferredPort - Preferred WebSocket port
263
+ * @param {number} options.preferredPort - Preferred WebSocket port (0 for auto-assign)
264
+ * @param {number} options.preferredContainerPort - Preferred container port (0 for auto-assign)
265
265
  * @param {boolean} options.isDaemon - Whether running as daemon
266
266
  * @returns {Promise<Object>} Server instances
267
267
  */
268
268
  export async function runAgent(options = {}) {
269
- const { preferredPort = config.wsPort, isDaemon = false } = options;
269
+ const {
270
+ preferredPort = config.wsPort || DEFAULT_WS_PORT,
271
+ preferredContainerPort = config.containerPort || DEFAULT_CONTAINER_PORT,
272
+ isDaemon = false
273
+ } = options;
270
274
 
271
275
  // Check for existing session
272
276
  log('Checking for existing session...');
@@ -290,12 +294,16 @@ export async function runAgent(options = {}) {
290
294
  throw new Error('Another instance of the agent is already running.');
291
295
  }
292
296
 
293
- // Find available port
297
+ // Find available port for WebSocket
294
298
  let actualPort;
295
299
  try {
296
- actualPort = await findAvailablePort(preferredPort);
297
- if (actualPort !== preferredPort) {
300
+ // If preferredPort is 0, find any available port starting from default
301
+ const startPort = preferredPort === 0 ? DEFAULT_WS_PORT : preferredPort;
302
+ actualPort = await findAvailablePort(startPort);
303
+ if (actualPort !== preferredPort && preferredPort !== 0) {
298
304
  log(`Port ${preferredPort} is in use, using port ${actualPort}`, 'warn');
305
+ } else if (preferredPort === 0) {
306
+ log(`Auto-assigned WebSocket port: ${actualPort}`);
299
307
  }
300
308
  } catch (error) {
301
309
  releaseLock();
@@ -305,16 +313,6 @@ export async function runAgent(options = {}) {
305
313
  // Initialize registry store
306
314
  await registryStore.initialize();
307
315
 
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
316
  // Create HTTP server with download endpoint handler
319
317
  const agentId = uuidv4();
320
318
  const existingWsToken = readWsToken();
@@ -388,13 +386,14 @@ export async function runAgent(options = {}) {
388
386
  // Initialize agent start time
389
387
  await initializeAgentStartTime();
390
388
 
391
- // Save daemon state
389
+ // Save daemon pid and preliminary state (containerPort updated after container start)
392
390
  saveDaemonPid(process.pid);
393
391
  saveDaemonState({
394
392
  pid: process.pid,
395
393
  port: actualPort,
394
+ containerPort: null,
396
395
  startTime: new Date().toISOString(),
397
- status: 'running',
396
+ status: 'starting',
398
397
  userEntityRef: session.userEntityRef,
399
398
  isDaemon,
400
399
  });
@@ -428,14 +427,34 @@ export async function runAgent(options = {}) {
428
427
  pollInterval,
429
428
  };
430
429
 
430
+ // Now that WebSocket is bound, start container (so it cannot take the WS port)
431
+ log('Starting container (after WebSocket bind)...');
432
+ let actualContainerPort = preferredContainerPort;
433
+ try {
434
+ actualContainerPort = await containerManager.startContainer({
435
+ wsPort: actualPort,
436
+ containerPort: preferredContainerPort,
437
+ });
438
+ log(`Container started successfully on port ${actualContainerPort}`);
439
+ } catch (containerError) {
440
+ log(`Failed to start container: ${containerError.message}`, 'warn');
441
+ log('Continuing without container...');
442
+ actualContainerPort = preferredContainerPort || DEFAULT_CONTAINER_PORT;
443
+ }
444
+
445
+ // Update daemon state now that containerPort is known
446
+ updateDaemonState({
447
+ port: actualPort,
448
+ containerPort: actualContainerPort,
449
+ status: 'running',
450
+ });
451
+
431
452
  // Display startup info
432
- console.log(
433
- chalk.green('\n' + '='.repeat(66))
434
- );
453
+ console.log(chalk.green('\n' + '='.repeat(66)));
435
454
  console.log(chalk.green(' Fenwave Agent Started Successfully'));
436
455
  console.log(chalk.green('='.repeat(66) + '\n'));
437
456
  console.log(chalk.white(' Fenwave DevApp Dashboard:'));
438
- console.log(chalk.cyan(` http://localhost:${CONTAINER_PORT}\n`));
457
+ console.log(chalk.cyan(` http://localhost:${actualContainerPort}\n`));
439
458
  console.log(chalk.white(' WebSocket Server:'));
440
459
  console.log(chalk.cyan(` ws://localhost:${actualPort}\n`));
441
460
  console.log(chalk.white(' User:'));
package/dist/styles.css CHANGED
@@ -179,6 +179,9 @@
179
179
  .relative {
180
180
  position: relative;
181
181
  }
182
+ .static {
183
+ position: static;
184
+ }
182
185
  .container {
183
186
  width: 100%;
184
187
  @media (width >= 40rem) {
@@ -5,7 +5,7 @@ import util from 'util';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import os from 'os';
8
- import { loadConfig } from '../store/configStore.js';
8
+ import { loadConfig, DEFAULT_WS_PORT } from '../store/configStore.js';
9
9
  import { getDaemonState } from '../store/daemonStore.js';
10
10
 
11
11
  const execPromise = util.promisify(exec);
@@ -23,7 +23,7 @@ function getAgentPort() {
23
23
  return state.port;
24
24
  }
25
25
  const config = loadConfig();
26
- return config.wsPort || 3001;
26
+ return config.wsPort || DEFAULT_WS_PORT;
27
27
  }
28
28
 
29
29
  // Cache for docker system df result to avoid concurrent operations
package/index.html CHANGED
@@ -12,6 +12,10 @@
12
12
  <script>
13
13
  // Store WebSocket token in localStorage for App Builder to use
14
14
  // These values are injected by the agent server
15
+ // Default ports: WS=3001, Container=3003 (may vary if ports are in use)
16
+ const DEFAULT_WS_PORT = "3001";
17
+ const DEFAULT_CONTAINER_PORT = "3003";
18
+
15
19
  const wsToken = new URLSearchParams(window.location.search).get(
16
20
  "wsToken",
17
21
  );
@@ -22,8 +26,8 @@
22
26
 
23
27
  if (wsToken) {
24
28
  localStorage.setItem("fenwave-ws-token", wsToken);
25
- localStorage.setItem("fenwave-ws-port", wsPort || "3001");
26
- localStorage.setItem("fenwave-container-port", containerPort || "3003");
29
+ localStorage.setItem("fenwave-ws-port", wsPort || DEFAULT_WS_PORT);
30
+ localStorage.setItem("fenwave-container-port", containerPort || DEFAULT_CONTAINER_PORT);
27
31
  }
28
32
  </script>
29
33
  <style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oussema_mili/test-pkg-123",
3
- "version": "1.1.42",
3
+ "version": "1.1.44",
4
4
  "description": "Fenwave Docker Agent and CLI",
5
5
  "keywords": [
6
6
  "fenwave",
@@ -6,6 +6,13 @@ const FENWAVE_DIR = path.join(os.homedir(), ".fenwave");
6
6
  const CONFIG_DIR = path.join(FENWAVE_DIR, "config");
7
7
  const CONFIG_FILE = path.join(CONFIG_DIR, "agent.json");
8
8
 
9
+ /**
10
+ * Default port values (used when no port is specified)
11
+ * Set to 0 to use a random available port
12
+ */
13
+ export const DEFAULT_WS_PORT = 3001;
14
+ export const DEFAULT_CONTAINER_PORT = 3003;
15
+
9
16
  /**
10
17
  * Default configuration values
11
18
  */
@@ -16,16 +23,16 @@ const DEFAULT_CONFIG = {
16
23
 
17
24
  // Container Configuration
18
25
  containerName: "fenwave-devapp",
19
- containerPort: 3003,
20
- wsPort: 3001,
26
+ containerPort: DEFAULT_CONTAINER_PORT,
27
+ wsPort: DEFAULT_WS_PORT,
21
28
 
22
29
  // Directory Configuration
23
30
  agentRootDir: ".fenwave",
24
31
  registriesDir: "registries",
25
32
  containerDataDir: "/data",
26
33
 
27
- // Docker Image (GHCR - public registry, no auth required)
28
- dockerImage: "ghcr.io/fenleap/fenwave/dev-app:latest",
34
+ // Docker Image (Docker Hub - testing, will be moved to GHCR later)
35
+ dockerImage: "oussemamili/dev-app:latest",
29
36
 
30
37
  // Other Settings
31
38
  authTimeoutMs: 60000,
package/utils/envSetup.js CHANGED
@@ -22,10 +22,13 @@ export function ensureEnvironmentFiles(agentDir, showFoundMessages = false) {
22
22
  // Check and create .env.agent for agent if not exists
23
23
  const agentEnvPath = path.join(fenwareConfigDir, ".env.agent");
24
24
  if (!fs.existsSync(agentEnvPath)) {
25
+ // Note: WS_PORT and CONTAINER_PORT can be set to 0 for auto-assignment
26
+ // or left empty to use defaults (3001 and 3003)
25
27
  const defaultAgentEnv = `BACKEND_URL=http://localhost:7007
26
28
  FRONTEND_URL=http://localhost:3000
27
- WS_PORT=3001
28
- CONTAINER_PORT=3003
29
+ # Port configuration (leave empty for defaults: 3001/3003, set to 0 for random available port)
30
+ WS_PORT=
31
+ CONTAINER_PORT=
29
32
  AGENT_ROOT_DIR=.fenwave
30
33
  REGISTRIES_DIR=registries
31
34
  AUTH_TIMEOUT_MS=60000
@@ -1,5 +1,9 @@
1
1
  import net from 'net';
2
2
 
3
+ // Default ports (can be overridden via environment or config)
4
+ export const DEFAULT_WS_PORT = 3001;
5
+ export const DEFAULT_CONTAINER_PORT = 3003;
6
+
3
7
  /**
4
8
  * Check if a port is available
5
9
  * @param {number} port - Port number to check
@@ -28,12 +32,12 @@ export async function isPortAvailable(port) {
28
32
 
29
33
  /**
30
34
  * Find an available port starting from a given port
31
- * @param {number} startPort - Starting port number (default: 3001)
35
+ * @param {number} startPort - Starting port number (default: DEFAULT_WS_PORT)
32
36
  * @param {number} maxAttempts - Maximum number of ports to try (default: 10)
33
37
  * @returns {Promise<number>} Available port number
34
38
  * @throws {Error} If no available port found in range
35
39
  */
36
- export async function findAvailablePort(startPort = 3001, maxAttempts = 10) {
40
+ export async function findAvailablePort(startPort = DEFAULT_WS_PORT, maxAttempts = 10) {
37
41
  for (let i = 0; i < maxAttempts; i++) {
38
42
  const port = startPort + i;
39
43
  if (await isPortAvailable(port)) {
@@ -52,8 +56,44 @@ export async function isPortInUse(port) {
52
56
  return !(await isPortAvailable(port));
53
57
  }
54
58
 
59
+ /**
60
+ * Find an available port for container, starting from a given port
61
+ * @param {number} startPort - Starting port number (default: DEFAULT_CONTAINER_PORT)
62
+ * @param {number} maxAttempts - Maximum number of ports to try (default: 10)
63
+ * @returns {Promise<number>} Available port number
64
+ */
65
+ export async function findAvailableContainerPort(startPort = DEFAULT_CONTAINER_PORT, maxAttempts = 100, excludePorts = []) {
66
+ for (let i = 0; i < maxAttempts; i++) {
67
+ const port = startPort + i;
68
+ if (excludePorts && excludePorts.includes(port)) continue;
69
+ if (await isPortAvailable(port)) {
70
+ return port;
71
+ }
72
+ }
73
+ throw new Error(`No available container port found in range ${startPort}-${startPort + maxAttempts - 1}`);
74
+ }
75
+
76
+ /**
77
+ * Find a random available port in the ephemeral range
78
+ * @returns {Promise<number>} Available port number
79
+ */
80
+ export async function findRandomAvailablePort() {
81
+ return new Promise((resolve, reject) => {
82
+ const server = net.createServer();
83
+ server.listen(0, '127.0.0.1', () => {
84
+ const { port } = server.address();
85
+ server.close(() => resolve(port));
86
+ });
87
+ server.on('error', reject);
88
+ });
89
+ }
90
+
55
91
  export default {
56
92
  isPortAvailable,
57
93
  findAvailablePort,
94
+ findAvailableContainerPort,
95
+ findRandomAvailablePort,
58
96
  isPortInUse,
97
+ DEFAULT_WS_PORT,
98
+ DEFAULT_CONTAINER_PORT,
59
99
  };
@@ -1,5 +0,0 @@
1
- export default {
2
- plugins: {
3
- "@tailwindcss/postcss": {},
4
- },
5
- };