Compare commits
No commits in common. "ad491d69e8c2309b20c41d4e10dc204a19905a3d" and "1bf4bc335c0c46afc33f78c7ec3c4cde2ee0ef26" have entirely different histories.
ad491d69e8
...
1bf4bc335c
9 changed files with 56 additions and 158 deletions
|
|
@ -3,24 +3,8 @@ import type { Config } from './src/types';
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
// host: '0.0.0.0', // Uncomment to bind to all interfaces (default is localhost only)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Dashboard configuration (optional)
|
|
||||||
// dashboard: {
|
|
||||||
// enabled: true,
|
|
||||||
// // Authentication options (choose one):
|
|
||||||
// auth: {
|
|
||||||
// // Option 1: Basic HTTP authentication
|
|
||||||
// type: 'basic',
|
|
||||||
// username: 'admin',
|
|
||||||
// password: 'changeme',
|
|
||||||
// // Option 2: Bearer token authentication
|
|
||||||
// // type: 'token',
|
|
||||||
// // token: 'your-secret-token-here',
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
|
|
||||||
repositories: [
|
repositories: [
|
||||||
// Leave empty to accept webhooks from any repo.
|
// Leave empty to accept webhooks from any repo.
|
||||||
// { owner: "myorg", repo: "myrepo" },
|
// { owner: "myorg", repo: "myrepo" },
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ import tseslint from '@typescript-eslint/eslint-plugin';
|
||||||
import tsparser from '@typescript-eslint/parser';
|
import tsparser from '@typescript-eslint/parser';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{ ignores: ['dist/**', 'node_modules/**', '**/*.d.ts'] },
|
|
||||||
{
|
{
|
||||||
files: ['src/**/*.ts', '**/*.js', 'config.example.ts', 'tsup.config.ts'],
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
ignores: ['dist/**', 'node_modules/**'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 2022,
|
ecmaVersion: 2022,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
|
|
@ -19,9 +19,8 @@ export default [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
'@typescript-eslint': tseslint as unknown as Record<string, unknown>,
|
'@typescript-eslint': tseslint,
|
||||||
},
|
},
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
...tseslint.configs.recommended.rules,
|
...tseslint.configs.recommended.rules,
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import type { Config } from 'prettier';
|
export default {
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
printWidth: 100,
|
printWidth: 100,
|
||||||
tabWidth: 2,
|
tabWidth: 2,
|
||||||
useTabs: false,
|
useTabs: false,
|
||||||
|
|
@ -15,5 +13,3 @@ const config: Config = {
|
||||||
endOfLine: 'lf',
|
endOfLine: 'lf',
|
||||||
plugins: [],
|
plugins: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
|
||||||
|
|
@ -97,10 +97,7 @@ export function loadConfig(): Config {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = deepMerge(
|
const config = deepMerge(defaults, fileConfig);
|
||||||
defaults as unknown as Record<string, unknown>,
|
|
||||||
fileConfig as unknown as Record<string, unknown>
|
|
||||||
) as unknown as Config;
|
|
||||||
|
|
||||||
// Environment variable overrides
|
// Environment variable overrides
|
||||||
if (process.env.PORT) {
|
if (process.env.PORT) {
|
||||||
|
|
@ -111,31 +108,6 @@ export function loadConfig(): Config {
|
||||||
config.server.port = parsed;
|
config.server.port = parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.HOST) {
|
|
||||||
config.server.host = process.env.HOST;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.DASHBOARD_TOKEN) {
|
|
||||||
config.dashboard = {
|
|
||||||
...(config.dashboard || { enabled: true }),
|
|
||||||
auth: {
|
|
||||||
type: 'token',
|
|
||||||
token: process.env.DASHBOARD_TOKEN,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.DASHBOARD_USERNAME && process.env.DASHBOARD_PASSWORD) {
|
|
||||||
config.dashboard = {
|
|
||||||
...(config.dashboard || { enabled: true }),
|
|
||||||
auth: {
|
|
||||||
type: 'basic',
|
|
||||||
username: process.env.DASHBOARD_USERNAME,
|
|
||||||
password: process.env.DASHBOARD_PASSWORD,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const validLogLevels = ['debug', 'info', 'warn', 'error'];
|
const validLogLevels = ['debug', 'info', 'warn', 'error'];
|
||||||
if (process.env.LOG_LEVEL) {
|
if (process.env.LOG_LEVEL) {
|
||||||
if (!validLogLevels.includes(process.env.LOG_LEVEL)) {
|
if (!validLogLevels.includes(process.env.LOG_LEVEL)) {
|
||||||
|
|
|
||||||
|
|
@ -9,43 +9,6 @@ export function createDashboardRouter(config: Config): express.Router {
|
||||||
|
|
||||||
router.use(express.json());
|
router.use(express.json());
|
||||||
|
|
||||||
// Authentication middleware
|
|
||||||
if (config.dashboard?.auth) {
|
|
||||||
router.use((req, res, next) => {
|
|
||||||
const auth = config.dashboard!.auth!;
|
|
||||||
|
|
||||||
if (auth.type === 'basic') {
|
|
||||||
const authHeader = req.headers.authorization;
|
|
||||||
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
||||||
res.setHeader('WWW-Authenticate', 'Basic realm="Troutbot Dashboard"');
|
|
||||||
res.status(401).json({ error: 'Authentication required' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const credentials = Buffer.from(authHeader.slice(6), 'base64').toString('utf8');
|
|
||||||
const [username, password] = credentials.split(':');
|
|
||||||
|
|
||||||
if (username !== auth.username || password !== auth.password) {
|
|
||||||
res.setHeader('WWW-Authenticate', 'Basic realm="Troutbot Dashboard"');
|
|
||||||
res.status(401).json({ error: 'Invalid credentials' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (auth.type === 'token') {
|
|
||||||
const authHeader = req.headers.authorization;
|
|
||||||
const token = req.query.token as string | undefined;
|
|
||||||
|
|
||||||
const providedToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : token;
|
|
||||||
|
|
||||||
if (!providedToken || providedToken !== auth.token) {
|
|
||||||
res.status(401).json({ error: 'Invalid or missing token' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// API routes
|
// API routes
|
||||||
router.get('/api/status', (_req, res) => {
|
router.get('/api/status', (_req, res) => {
|
||||||
const enabledBackends = Object.entries(config.engine.backends)
|
const enabledBackends = Object.entries(config.engine.backends)
|
||||||
|
|
@ -78,9 +41,9 @@ export function createDashboardRouter(config: Config): express.Router {
|
||||||
try {
|
try {
|
||||||
const partial = req.body as Partial<Config>;
|
const partial = req.body as Partial<Config>;
|
||||||
const merged = deepMerge(
|
const merged = deepMerge(
|
||||||
config as unknown as Record<string, unknown>,
|
config as Record<string, unknown>,
|
||||||
partial as unknown as Record<string, unknown>
|
partial as Record<string, unknown>
|
||||||
) as unknown as Config;
|
) as Config;
|
||||||
validate(merged);
|
validate(merged);
|
||||||
|
|
||||||
// Apply in-place
|
// Apply in-place
|
||||||
|
|
@ -92,7 +55,8 @@ export function createDashboardRouter(config: Config): express.Router {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dashboard HTML
|
// --- Dashboard HTML ---
|
||||||
|
|
||||||
router.get('/dashboard', (_req, res) => {
|
router.get('/dashboard', (_req, res) => {
|
||||||
res.type('html').send(dashboardHTML());
|
res.type('html').send(dashboardHTML());
|
||||||
});
|
});
|
||||||
|
|
|
||||||
11
src/index.ts
11
src/index.ts
|
|
@ -27,13 +27,12 @@ async function analyzeOne(target: string) {
|
||||||
initLogger(config.logging);
|
initLogger(config.logging);
|
||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
|
|
||||||
|
initGitHub(process.env.GITHUB_TOKEN);
|
||||||
if (!process.env.GITHUB_TOKEN) {
|
if (!process.env.GITHUB_TOKEN) {
|
||||||
logger.error('GITHUB_TOKEN is required for analyze mode');
|
logger.error('GITHUB_TOKEN is required for analyze mode');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
initGitHub(process.env.GITHUB_TOKEN);
|
|
||||||
|
|
||||||
const prData = await fetchPR(owner, repo, prNumber);
|
const prData = await fetchPR(owner, repo, prNumber);
|
||||||
if (!prData) {
|
if (!prData) {
|
||||||
logger.error(`Could not fetch PR ${owner}/${repo}#${prNumber}`);
|
logger.error(`Could not fetch PR ${owner}/${repo}#${prNumber}`);
|
||||||
|
|
@ -109,9 +108,8 @@ function serve() {
|
||||||
.filter(([, v]) => v.enabled)
|
.filter(([, v]) => v.enabled)
|
||||||
.map(([k]) => k);
|
.map(([k]) => k);
|
||||||
|
|
||||||
const host = config.server.host || '127.0.0.1';
|
const server = app.listen(port, async () => {
|
||||||
const server = app.listen(port, host, async () => {
|
logger.info(`Troutbot listening on port ${port}`);
|
||||||
logger.info(`Troutbot listening on ${host}:${port}`);
|
|
||||||
logger.info(`Enabled backends: ${enabledBackends.join(', ')}`);
|
logger.info(`Enabled backends: ${enabledBackends.join(', ')}`);
|
||||||
|
|
||||||
// Watched repos
|
// Watched repos
|
||||||
|
|
@ -142,8 +140,7 @@ function serve() {
|
||||||
// Comment update mode
|
// Comment update mode
|
||||||
logger.info(`Comment updates: ${config.response.allowUpdates ? 'enabled' : 'disabled'}`);
|
logger.info(`Comment updates: ${config.response.allowUpdates ? 'enabled' : 'disabled'}`);
|
||||||
|
|
||||||
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
|
logger.info(`Dashboard available at http://localhost:${port}/dashboard`);
|
||||||
logger.info(`Dashboard available at http://${displayHost}:${port}/dashboard`);
|
|
||||||
|
|
||||||
// Start polling if enabled
|
// Start polling if enabled
|
||||||
await startPolling(config);
|
await startPolling(config);
|
||||||
|
|
|
||||||
14
src/types.ts
14
src/types.ts
|
|
@ -1,6 +1,5 @@
|
||||||
export interface Config {
|
export interface Config {
|
||||||
server: ServerConfig;
|
server: ServerConfig;
|
||||||
dashboard?: DashboardConfig;
|
|
||||||
repositories: RepoConfig[];
|
repositories: RepoConfig[];
|
||||||
filters: FiltersConfig;
|
filters: FiltersConfig;
|
||||||
engine: EngineConfig;
|
engine: EngineConfig;
|
||||||
|
|
@ -17,22 +16,9 @@ export interface PollingConfig {
|
||||||
|
|
||||||
export interface ServerConfig {
|
export interface ServerConfig {
|
||||||
port: number;
|
port: number;
|
||||||
host?: string;
|
|
||||||
rateLimit?: number;
|
rateLimit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardConfig {
|
|
||||||
enabled: boolean;
|
|
||||||
auth?: DashboardAuthConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DashboardAuthConfig {
|
|
||||||
type: 'basic' | 'token';
|
|
||||||
username?: string;
|
|
||||||
password?: string;
|
|
||||||
token?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RepoConfig {
|
export interface RepoConfig {
|
||||||
owner: string;
|
owner: string;
|
||||||
repo: string;
|
repo: string;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue