fix: systematic bug fixes — code-quality parser, env key, config warnings, auth cleanup, deployment safety
- code-quality/repository.ts: fix tsErrorMatch[3] → [4] for type field (group 3 is column, 4 is error|warning) - code-quality/repository.ts: fix ESLint regex to make rule brackets optional (not all formatters include them) - code-quality/repository.ts: fix Vitest test count — parse 'Tests' line (individual tests) instead of 'Test Files' (file count); improve Jest regex to capture pass/fail independently - env/repository.ts: replace raw process.env.ENCRYPTION_KEY with config.ENCRYPTION_KEY so the validated default flows through a single source of truth - config.ts: add startup console.warn when CSRF_SECRET or ENCRYPTION_KEY are using insecure defaults - deployments/orchestrator.ts: refactor runDeploymentScript to use try/catch/finally — deployment record is now always written in the finally block, preventing zombie 'running' states if updateDeployment itself throws - auth.tsx: remove dead 'user &&' guard (user is always truthy after the !user check above); remove debug console.log calls, keep console.error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cdc23696b2
commit
31b414d62b
@ -31,5 +31,13 @@ const envSchema = z.object({
|
||||
|
||||
export const config = envSchema.parse(process.env);
|
||||
|
||||
// Warn loudly when insecure default keys are in use
|
||||
if (config.CSRF_SECRET === 'default-csrf-secret-change-in-production') {
|
||||
console.warn('[config] WARNING: CSRF_SECRET is using the insecure default — set CSRF_SECRET in .env before deploying to production');
|
||||
}
|
||||
if (config.ENCRYPTION_KEY === 'default-encryption-key-change-in-production') {
|
||||
console.warn('[config] WARNING: ENCRYPTION_KEY is using the insecure default — set ENCRYPTION_KEY in .env before deploying to production');
|
||||
}
|
||||
|
||||
export const productId = productIdentity.productId;
|
||||
export const productName = productIdentity.name;
|
||||
|
||||
@ -148,7 +148,7 @@ function parseTypeScriptOutput(output: string, projectPath: string): CodeQuality
|
||||
if (tsErrorMatch) {
|
||||
issues.push({
|
||||
id: `ts-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||||
type: tsErrorMatch[3] as 'error' | 'warning',
|
||||
type: tsErrorMatch[4] as 'error' | 'warning', // group 4 = type; group 3 = column
|
||||
category: 'typescript',
|
||||
file: tsErrorMatch[1],
|
||||
line: parseInt(tsErrorMatch[2]),
|
||||
@ -167,10 +167,12 @@ function parseEslintOutput(output: string, projectPath: string): CodeQualityIssu
|
||||
const lines = output.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
// ESLint format: file:line:col message [rule]
|
||||
const eslintMatch = line.match(/(.+\.tsx?):(\d+):(\d+)\s+(.+?)\s+\[(.+)\]/);
|
||||
// ESLint unix format: file:line:col: message [rule]
|
||||
// Rule part in brackets may or may not be present depending on formatter
|
||||
const eslintMatch = line.match(/(.+\.tsx?):(\d+):(\d+)[:\s]+(.+?)(?:\s+\[([^\]]+)\])?$/);
|
||||
if (eslintMatch) {
|
||||
const severity = eslintMatch[4].includes('error') ? 'error' : 'warning';
|
||||
const msgAndLevel = eslintMatch[4];
|
||||
const severity = /\berror\b/i.test(msgAndLevel) ? 'error' : 'warning';
|
||||
issues.push({
|
||||
id: `eslint-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||||
type: severity,
|
||||
@ -178,8 +180,8 @@ function parseEslintOutput(output: string, projectPath: string): CodeQualityIssu
|
||||
file: eslintMatch[1],
|
||||
line: parseInt(eslintMatch[2]),
|
||||
column: parseInt(eslintMatch[3]),
|
||||
message: eslintMatch[4],
|
||||
rule: eslintMatch[5],
|
||||
message: msgAndLevel,
|
||||
rule: eslintMatch[5] ?? 'unknown',
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -210,18 +212,24 @@ function parseTestOutput(output: string): { passed: number; failed: number } {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
// Try to parse Vitest output
|
||||
const vitestMatch = output.match(/Test Files\s+(\d+)\s+\((\d+)\s+failed/);
|
||||
if (vitestMatch) {
|
||||
failed = parseInt(vitestMatch[2]);
|
||||
passed = parseInt(vitestMatch[1]) - failed;
|
||||
// Try to parse Vitest output — use "Tests" line (individual tests), not "Test Files" line
|
||||
// Format: " Tests 3 failed | 5 passed (8)" or " Tests 8 passed (8)"
|
||||
const vitestFailMatch = output.match(/\bTests\b\s+(\d+)\s+failed[^|]*\|\s*(\d+)\s+passed/);
|
||||
const vitestPassMatch = output.match(/\bTests\b\s+(\d+)\s+passed/);
|
||||
if (vitestFailMatch) {
|
||||
failed = parseInt(vitestFailMatch[1]);
|
||||
passed = parseInt(vitestFailMatch[2]);
|
||||
} else if (vitestPassMatch) {
|
||||
passed = parseInt(vitestPassMatch[1]);
|
||||
failed = 0;
|
||||
}
|
||||
|
||||
// Try to parse Jest output
|
||||
const jestMatch = output.match(/Tests:\s+(\d+)\s+passed,?\s*(\d+)\s+failed/);
|
||||
if (jestMatch) {
|
||||
passed = parseInt(jestMatch[1]);
|
||||
failed = parseInt(jestMatch[2]);
|
||||
// Try to parse Jest output: "Tests: 5 passed, 2 failed" or "Tests: 2 failed, 5 passed"
|
||||
const jestPassMatch = output.match(/Tests:.*?(\d+)\s+passed/);
|
||||
const jestFailMatch = output.match(/Tests:.*?(\d+)\s+failed/);
|
||||
if (jestPassMatch || jestFailMatch) {
|
||||
passed = jestPassMatch ? parseInt(jestPassMatch[1]) : 0;
|
||||
failed = jestFailMatch ? parseInt(jestFailMatch[1]) : 0;
|
||||
}
|
||||
|
||||
return { passed, failed };
|
||||
|
||||
@ -30,6 +30,10 @@ async function runDeploymentScript(service: Service, deploymentId: string) {
|
||||
const scriptDir = join(process.cwd(), '../../'); // Go to bytelyst-devops-tools root
|
||||
const scriptPath = join(scriptDir, service.scriptPath);
|
||||
|
||||
let finalStatus: 'success' | 'failed' = 'failed';
|
||||
let logs = '';
|
||||
let version: string | undefined;
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(`bash ${scriptPath}`, {
|
||||
cwd: scriptDir,
|
||||
@ -40,15 +44,9 @@ async function runDeploymentScript(service: Service, deploymentId: string) {
|
||||
},
|
||||
});
|
||||
|
||||
const logs = `STDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`;
|
||||
|
||||
// Update deployment as success
|
||||
await updateDeployment(deploymentId, {
|
||||
status: 'success',
|
||||
logs,
|
||||
completedAt: new Date().toISOString(),
|
||||
version: extractVersion(stdout + stderr) || 'unknown',
|
||||
});
|
||||
logs = `STDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`;
|
||||
finalStatus = 'success';
|
||||
version = extractVersion(stdout + stderr) || 'unknown';
|
||||
|
||||
// Update service status
|
||||
const { getServiceById, updateService } = await import('../services/repository.js');
|
||||
@ -57,21 +55,14 @@ async function runDeploymentScript(service: Service, deploymentId: string) {
|
||||
await updateService(service.id, {
|
||||
status: 'up',
|
||||
lastDeployedAt: new Date().toISOString(),
|
||||
version: extractVersion(stdout + stderr) || svc.version,
|
||||
version: version || svc.version,
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
const logs = error instanceof Error
|
||||
logs = error instanceof Error
|
||||
? `ERROR: ${error.message}\n\n${(error as any).stdout ? `STDOUT:\n${(error as any).stdout}\n\n` : ''}${(error as any).stderr ? `STDERR:\n${(error as any).stderr}` : ''}`
|
||||
: String(error);
|
||||
|
||||
// Update deployment as failed
|
||||
await updateDeployment(deploymentId, {
|
||||
status: 'failed',
|
||||
logs,
|
||||
completedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Update service status to down
|
||||
const { getServiceById, updateService } = await import('../services/repository.js');
|
||||
const svc = await getServiceById(service.id);
|
||||
@ -80,6 +71,18 @@ async function runDeploymentScript(service: Service, deploymentId: string) {
|
||||
status: 'down',
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
// Always write final status — ensures the deployment never gets stuck in 'running'
|
||||
try {
|
||||
await updateDeployment(deploymentId, {
|
||||
status: finalStatus,
|
||||
logs,
|
||||
completedAt: new Date().toISOString(),
|
||||
...(version ? { version } : {}),
|
||||
});
|
||||
} catch (updateError) {
|
||||
console.error(`Failed to persist final deployment status for ${deploymentId}:`, updateError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -73,13 +73,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
try {
|
||||
const token = getAccessTokenFromStorage();
|
||||
if (!token) {
|
||||
console.log('No token found in storage');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Checking auth with token...');
|
||||
|
||||
// Add timeout to prevent hanging
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Auth check timeout')), 10000)
|
||||
@ -90,15 +87,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
timeoutPromise
|
||||
]) as MeResponse;
|
||||
|
||||
console.log('User data received:', userData);
|
||||
setUser(userData);
|
||||
|
||||
// Simplified admin check - just check global admin role
|
||||
const globalRole = userData.role;
|
||||
const hasAdminAccess = globalRole === 'admin';
|
||||
setIsAdmin(hasAdminAccess);
|
||||
|
||||
console.log('Admin access:', hasAdminAccess);
|
||||
} catch (error) {
|
||||
console.error('Auth check failed:', error);
|
||||
clearAuthTokens();
|
||||
@ -109,9 +103,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
async function login(email: string, password: string, productId: string) {
|
||||
try {
|
||||
console.log('Attempting login for:', email, 'with productId:', productId);
|
||||
const response = await authApi.login({ email, password, productId });
|
||||
console.log('Login response received:', response);
|
||||
|
||||
setAccessToken(response.accessToken);
|
||||
setRefreshToken(response.refreshToken);
|
||||
@ -120,8 +112,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
// Check if user has admin access (global admin role)
|
||||
const hasAdminAccess = response.user.role === 'admin';
|
||||
setIsAdmin(hasAdminAccess);
|
||||
|
||||
console.log('Login successful, admin access:', hasAdminAccess);
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error);
|
||||
throw error;
|
||||
@ -148,7 +138,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
return <div className="min-h-screen flex items-center justify-center">Redirecting to login...</div>;
|
||||
}
|
||||
|
||||
if (user && !isAdmin) {
|
||||
if (!isAdmin) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user