mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 16:24:03 -05:00
## Summary This PR significantly improves maintainability by breaking up the 880+ line monolithic app.ts file into smaller, focused modules with proper testing. The code is now organized into dedicated modules under the lib/app/ directory, making the codebase more maintainable and testable. ## Key changes - Extract functionality into modules under lib/app/ directory: - Command-line handling (cli.ts) - Configuration loading (config.ts) - Web server setup and middleware (server.ts) - Core application initialization (main.ts) - URL handlers, routing, rendering, and controllers - Add comprehensive unit tests for all new modules - Make compilationQueue non-optional in the compilation environment - Improve separation of concerns with dedicated interfaces - Ensure backward compatibility with existing functionality - Maintain cross-platform compatibility (Windows/Linux) ## Benefits - Improved code organization and modularity - Enhanced testability with proper unit tests - Better separation of concerns - Reduced complexity in individual files - Easier maintenance and future development This refactoring is a significant step toward a more maintainable codebase while preserving all existing functionality.
239 lines
9.0 KiB
TypeScript
239 lines
9.0 KiB
TypeScript
// Copyright (c) 2025, Compiler Explorer Authors
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import child_process from 'node:child_process';
|
|
import * as fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import {Command} from 'commander';
|
|
|
|
import {AppArguments} from '../app.interfaces.js';
|
|
import {logger} from '../logger.js';
|
|
import * as utils from '../utils.js';
|
|
|
|
/**
|
|
* Parses a command line option into a number.
|
|
*/
|
|
export function parsePortNumberForOptions(value: string): number {
|
|
// Ensure string contains only digits
|
|
if (!/^\d+$/.test(value)) {
|
|
throw new Error(`Invalid port number: "${value}"`);
|
|
}
|
|
|
|
const parsedValue = Number.parseInt(value, 10);
|
|
if (Number.isNaN(parsedValue) || parsedValue > 65535) {
|
|
throw new Error(`Invalid port number: "${value}"`);
|
|
}
|
|
return parsedValue;
|
|
}
|
|
|
|
/**
|
|
* Options parsed from command-line arguments
|
|
*/
|
|
export interface CompilerExplorerOptions {
|
|
env: string[];
|
|
rootDir: string;
|
|
host?: string;
|
|
port: number;
|
|
propDebug?: boolean;
|
|
debug?: boolean;
|
|
dist?: boolean;
|
|
remoteFetch: boolean;
|
|
tmpDir?: string;
|
|
wsl?: boolean;
|
|
language?: string[];
|
|
cache: boolean;
|
|
ensureNoIdClash?: boolean;
|
|
logHost?: string;
|
|
logPort?: number;
|
|
hostnameForLogging?: string;
|
|
suppressConsoleLog: boolean;
|
|
metricsPort?: number;
|
|
loki?: string;
|
|
discoveryOnly?: string;
|
|
prediscovered?: string;
|
|
static?: string;
|
|
local: boolean;
|
|
version: boolean;
|
|
devMode: boolean;
|
|
}
|
|
|
|
/**
|
|
* Parse command-line arguments and return parsed options
|
|
* @param argv The command-line arguments to parse
|
|
*/
|
|
export function parseCommandLine(argv: string[]): CompilerExplorerOptions {
|
|
const program = new Command();
|
|
program
|
|
.name('compiler-explorer')
|
|
.description('Interactively investigate compiler output')
|
|
.option('--env <environments...>', 'Environment(s) to use', ['dev'])
|
|
.option('--root-dir <dir>', 'Root directory for config files', './etc')
|
|
.option('--host <hostname>', 'Hostname to listen on')
|
|
.option('--port <port>', 'Port to listen on', parsePortNumberForOptions, 10240)
|
|
.option('--prop-debug', 'Debug properties')
|
|
.option('--debug', 'Enable debug output')
|
|
.option('--dist', 'Running in dist mode')
|
|
.option('--no-remote-fetch', 'Ignore fetch marks and assume every compiler is found locally')
|
|
.option('--tmpDir, --tmp-dir <dir>', 'Directory to use for temporary files')
|
|
.option('--wsl', 'Running under Windows Subsystem for Linux')
|
|
.option('--language <languages...>', 'Only load specified languages for faster startup')
|
|
.option('--no-cache', 'Do not use caching for compilation results')
|
|
.option('--ensure-no-id-clash', "Don't run if compilers have clashing ids")
|
|
.option('--logHost, --log-host <hostname>', 'Hostname for remote logging')
|
|
.option('--logPort, --log-port <port>', 'Port for remote logging', parsePortNumberForOptions)
|
|
.option('--hostnameForLogging, --hostname-for-logging <hostname>', 'Hostname to use in logs')
|
|
.option('--suppressConsoleLog, --suppress-console-log', 'Disable console logging')
|
|
.option('--metricsPort, --metrics-port <port>', 'Port to serve metrics on', parsePortNumberForOptions)
|
|
.option('--loki <url>', 'URL for Loki logging')
|
|
.option('--discoveryonly, --discovery-only <file>', 'Output discovery info to file and exit')
|
|
.option('--prediscovered <file>', 'Input discovery info from file')
|
|
.option('--static <dir>', 'Path to static content')
|
|
.option('--no-local', 'Disable local config')
|
|
.option('--version', 'Show version information')
|
|
.option(
|
|
'--dev-mode',
|
|
'Run in dev mode (default if NODE_ENV is not production)',
|
|
process.env.NODE_ENV !== 'production',
|
|
);
|
|
|
|
program.parse(argv);
|
|
return program.opts() as CompilerExplorerOptions;
|
|
}
|
|
|
|
/**
|
|
* Extract git release information from repository or file
|
|
*/
|
|
export function getGitReleaseName(distPath: string, isDist: boolean): string {
|
|
// Use the canned git_hash if provided
|
|
const gitHashFilePath = path.join(distPath, 'git_hash');
|
|
if (isDist && fs.existsSync(gitHashFilePath)) {
|
|
return fs.readFileSync(gitHashFilePath).toString().trim();
|
|
}
|
|
|
|
// Check if we have been cloned and not downloaded
|
|
if (fs.existsSync('.git')) {
|
|
return child_process.execSync('git rev-parse HEAD').toString().trim();
|
|
}
|
|
|
|
// unknown case
|
|
return '<no git hash found>';
|
|
}
|
|
|
|
/**
|
|
* Extract release build number from file
|
|
*/
|
|
export function getReleaseBuildNumber(distPath: string, isDist: boolean): string {
|
|
// Use the canned build only if provided
|
|
const releaseBuildPath = path.join(distPath, 'release_build');
|
|
if (isDist && fs.existsSync(releaseBuildPath)) {
|
|
return fs.readFileSync(releaseBuildPath).toString().trim();
|
|
}
|
|
return '<no build number found>';
|
|
}
|
|
|
|
/**
|
|
* Detect if running under Windows Subsystem for Linux
|
|
*/
|
|
export function detectWsl(): boolean {
|
|
if (process.platform === 'linux') {
|
|
try {
|
|
return child_process.execSync('uname -a').toString().toLowerCase().includes('microsoft');
|
|
} catch (e) {
|
|
logger.warn('Unable to detect WSL environment', e);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Convert command-line options to AppArguments for the application
|
|
*/
|
|
export function convertOptionsToAppArguments(
|
|
options: CompilerExplorerOptions,
|
|
gitReleaseName: string,
|
|
releaseBuildNumber: string,
|
|
isWsl: boolean,
|
|
): AppArguments {
|
|
return {
|
|
rootDir: options.rootDir,
|
|
env: options.env,
|
|
hostname: options.host,
|
|
port: options.port,
|
|
gitReleaseName: gitReleaseName,
|
|
releaseBuildNumber: releaseBuildNumber,
|
|
wantedLanguages: options.language,
|
|
doCache: options.cache,
|
|
fetchCompilersFromRemote: options.remoteFetch,
|
|
ensureNoCompilerClash: options.ensureNoIdClash,
|
|
prediscovered: options.prediscovered,
|
|
discoveryOnly: options.discoveryOnly,
|
|
staticPath: options.static,
|
|
metricsPort: options.metricsPort,
|
|
useLocalProps: options.local,
|
|
propDebug: options.propDebug || false,
|
|
tmpDir: options.tmpDir,
|
|
isWsl: isWsl,
|
|
devMode: options.devMode,
|
|
loggingOptions: {
|
|
debug: options.debug || false,
|
|
logHost: options.logHost,
|
|
logPort: options.logPort,
|
|
hostnameForLogging: options.hostnameForLogging,
|
|
loki: options.loki,
|
|
suppressConsoleLog: options.suppressConsoleLog,
|
|
paperTrailIdentifier: options.env.join('.'),
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parse command-line arguments into an AppArguments object
|
|
* @param argv The command-line arguments to parse
|
|
* @returns Application arguments
|
|
*/
|
|
export function parseArgsToAppArguments(argv: string[]): AppArguments {
|
|
const options = parseCommandLine(argv);
|
|
const isWsl = detectWsl();
|
|
|
|
const distPath = utils.resolvePathFromAppRoot('.');
|
|
logger.debug(`Distpath=${distPath}`);
|
|
|
|
const gitReleaseName = getGitReleaseName(distPath, options.dist === true);
|
|
const releaseBuildNumber = getReleaseBuildNumber(distPath, options.dist === true);
|
|
|
|
const appArgs = convertOptionsToAppArguments(options, gitReleaseName, releaseBuildNumber, isWsl);
|
|
|
|
if (options.version) {
|
|
// We can't use the `--version` support in Commander, as we need to parse the args
|
|
// to find the directory for the git release and whatnot.
|
|
logger.info('Compiler Explorer version info:');
|
|
logger.info(` git release ${appArgs.gitReleaseName}`);
|
|
logger.info(` release build ${appArgs.releaseBuildNumber}`);
|
|
logger.info('Exiting');
|
|
process.exit(0);
|
|
}
|
|
|
|
return appArgs;
|
|
}
|