Compare commits

...

1 Commits

Author SHA1 Message Date
simosmik
9f99f6ab53 refactor(server): extract URL detection and color utils from index.js
No behavioral changes — 1:1 code move with imports replacing inline definitions.
2026-04-14 22:15:05 +00:00
3 changed files with 94 additions and 81 deletions

View File

@@ -11,25 +11,7 @@ const __dirname = dirname(__filename);
const installMode = fs.existsSync(path.join(__dirname, '..', '.git')) ? 'git' : 'npm';
// ANSI color codes for terminal output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
cyan: '\x1b[36m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
dim: '\x1b[2m',
};
const c = {
info: (text) => `${colors.cyan}${text}${colors.reset}`,
ok: (text) => `${colors.green}${text}${colors.reset}`,
warn: (text) => `${colors.yellow}${text}${colors.reset}`,
tip: (text) => `${colors.blue}${text}${colors.reset}`,
bright: (text) => `${colors.bright}${text}${colors.reset}`,
dim: (text) => `${colors.dim}${text}${colors.reset}`,
};
import { c } from './utils/colors.js';
console.log('SERVER_PORT from env:', process.env.SERVER_PORT);
@@ -226,68 +208,7 @@ const server = http.createServer(app);
const ptySessionsMap = new Map();
const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
function stripAnsiSequences(value = '') {
return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, '');
}
function normalizeDetectedUrl(url) {
if (!url || typeof url !== 'string') return null;
const cleaned = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, '');
if (!cleaned) return null;
try {
const parsed = new URL(cleaned);
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return null;
}
return parsed.toString();
} catch {
return null;
}
}
function extractUrlsFromText(value = '') {
const directMatches = value.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/gi) || [];
// Handle wrapped terminal URLs split across lines by terminal width.
const wrappedMatches = [];
const continuationRegex = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$/;
const lines = value.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const startMatch = line.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/i);
if (!startMatch) continue;
let combined = startMatch[0];
let j = i + 1;
while (j < lines.length) {
const continuation = lines[j].trim();
if (!continuation) break;
if (!continuationRegex.test(continuation)) break;
combined += continuation;
j++;
}
wrappedMatches.push(combined.replace(/\r?\n\s*/g, ''));
}
return Array.from(new Set([...directMatches, ...wrappedMatches]));
}
function shouldAutoOpenUrlFromOutput(value = '') {
const normalized = value.toLowerCase();
return (
normalized.includes('browser didn\'t open') ||
normalized.includes('open this url') ||
normalized.includes('continue in your browser') ||
normalized.includes('press enter to open') ||
normalized.includes('open_url:')
);
}
import { stripAnsiSequences, normalizeDetectedUrl, extractUrlsFromText, shouldAutoOpenUrlFromOutput } from './utils/url-detection.js';
// Single WebSocket server that handles both paths
const wss = new WebSocketServer({

21
server/utils/colors.js Normal file
View File

@@ -0,0 +1,21 @@
// ANSI color codes for terminal output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
cyan: '\x1b[36m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
dim: '\x1b[2m',
};
const c = {
info: (text) => `${colors.cyan}${text}${colors.reset}`,
ok: (text) => `${colors.green}${text}${colors.reset}`,
warn: (text) => `${colors.yellow}${text}${colors.reset}`,
tip: (text) => `${colors.blue}${text}${colors.reset}`,
bright: (text) => `${colors.bright}${text}${colors.reset}`,
dim: (text) => `${colors.dim}${text}${colors.reset}`,
};
export { colors, c };

View File

@@ -0,0 +1,71 @@
const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
function stripAnsiSequences(value = '') {
return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, '');
}
function normalizeDetectedUrl(url) {
if (!url || typeof url !== 'string') return null;
const cleaned = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, '');
if (!cleaned) return null;
try {
const parsed = new URL(cleaned);
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return null;
}
return parsed.toString();
} catch {
return null;
}
}
function extractUrlsFromText(value = '') {
const directMatches = value.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/gi) || [];
// Handle wrapped terminal URLs split across lines by terminal width.
const wrappedMatches = [];
const continuationRegex = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$/;
const lines = value.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const startMatch = line.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/i);
if (!startMatch) continue;
let combined = startMatch[0];
let j = i + 1;
while (j < lines.length) {
const continuation = lines[j].trim();
if (!continuation) break;
if (!continuationRegex.test(continuation)) break;
combined += continuation;
j++;
}
wrappedMatches.push(combined.replace(/\r?\n\s*/g, ''));
}
return Array.from(new Set([...directMatches, ...wrappedMatches]));
}
function shouldAutoOpenUrlFromOutput(value = '') {
const normalized = value.toLowerCase();
return (
normalized.includes('browser didn\'t open') ||
normalized.includes('open this url') ||
normalized.includes('continue in your browser') ||
normalized.includes('press enter to open') ||
normalized.includes('open_url:')
);
}
export {
ANSI_ESCAPE_SEQUENCE_REGEX,
TRAILING_URL_PUNCTUATION_REGEX,
stripAnsiSequences,
normalizeDetectedUrl,
extractUrlsFromText,
shouldAutoOpenUrlFromOutput
};