123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
import * as plugins from './mod.plugins.js';
|
|
import * as net from 'net';
|
|
import { logger } from '../gitzone.logging.js';
|
|
|
|
/**
|
|
* Check if a port is available
|
|
*/
|
|
export const isPortAvailable = async (port: number): Promise<boolean> => {
|
|
return new Promise((resolve) => {
|
|
const server = net.createServer();
|
|
|
|
server.once('error', () => {
|
|
resolve(false);
|
|
});
|
|
|
|
server.once('listening', () => {
|
|
server.close();
|
|
resolve(true);
|
|
});
|
|
|
|
server.listen(port, '0.0.0.0');
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get a random available port between 20000 and 30000
|
|
*/
|
|
export const getRandomAvailablePort = async (): Promise<number> => {
|
|
const maxAttempts = 100;
|
|
|
|
for (let i = 0; i < maxAttempts; i++) {
|
|
const port = Math.floor(Math.random() * 10001) + 20000;
|
|
if (await isPortAvailable(port)) {
|
|
return port;
|
|
}
|
|
}
|
|
|
|
// Fallback: let the system assign a port
|
|
return 0;
|
|
};
|
|
|
|
/**
|
|
* Get the project name from package.json or directory
|
|
*/
|
|
export const getProjectName = (): string => {
|
|
try {
|
|
const packageJsonPath = plugins.path.join(process.cwd(), 'package.json');
|
|
if (plugins.smartfile.fs.fileExistsSync(packageJsonPath)) {
|
|
const packageJson = plugins.smartfile.fs.toObjectSync(packageJsonPath);
|
|
if (packageJson.name) {
|
|
// Sanitize: @fin.cx/skr → fin-cx-skr
|
|
return packageJson.name.replace(/@/g, '').replace(/[\/\.]/g, '-');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// Ignore errors and fall back to directory name
|
|
}
|
|
|
|
return plugins.path.basename(process.cwd());
|
|
};
|
|
|
|
/**
|
|
* Print a header with decorative lines
|
|
*/
|
|
export const printHeader = (title: string) => {
|
|
console.log();
|
|
logger.log('info', '═══════════════════════════════════════════════════════════════');
|
|
logger.log('info', ` ${title}`);
|
|
logger.log('info', '═══════════════════════════════════════════════════════════════');
|
|
console.log();
|
|
};
|
|
|
|
/**
|
|
* Format bytes to human readable string
|
|
*/
|
|
export const formatBytes = (bytes: number): string => {
|
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
let size = bytes;
|
|
let unitIndex = 0;
|
|
|
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
size /= 1024;
|
|
unitIndex++;
|
|
}
|
|
|
|
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
|
};
|
|
|
|
/**
|
|
* Get the local network IP address
|
|
*/
|
|
export const getLocalNetworkIp = async (): Promise<string> => {
|
|
const smartnetworkInstance = new plugins.smartnetwork.SmartNetwork();
|
|
const gateways = await smartnetworkInstance.getGateways();
|
|
|
|
// Find the best local IP from network interfaces
|
|
for (const interfaceName of Object.keys(gateways)) {
|
|
const interfaces = gateways[interfaceName];
|
|
for (const iface of interfaces) {
|
|
// Skip loopback and internal interfaces
|
|
if (!iface.internal && iface.family === 'IPv4') {
|
|
const address = iface.address;
|
|
// Prefer LAN IPs
|
|
if (address.startsWith('192.168.') || address.startsWith('10.') || address.startsWith('172.')) {
|
|
return address;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback: try to get any non-internal IPv4
|
|
for (const interfaceName of Object.keys(gateways)) {
|
|
const interfaces = gateways[interfaceName];
|
|
for (const iface of interfaces) {
|
|
if (!iface.internal && iface.family === 'IPv4') {
|
|
return iface.address;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Last resort: localhost
|
|
return 'localhost';
|
|
}; |