This commit is contained in:
jarek committed 2026-03-02 07:59:58 +01:00
1 parent 4b430340db
commit 9c451aedf9
120 files changed
+7777 -6046

No files matched your search

+21 -7
View File
@@ -14,6 +14,7 @@ import { authorize } from '$lib/server/authorize';
import { registerSchedule } from '$lib/server/scheduler';
import { secureRandomBytes } from '$lib/server/crypto-fallback';
import { auditGitStack } from '$lib/server/audit';
import { createJobResponse } from '$lib/server/sse';
// Stack name validation: must start with alphanumeric, can contain alphanumeric, hyphens, underscores
const STACK_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
@@ -156,20 +157,33 @@ export const POST: RequestHandler = async (event) => {
}
}
// If deployNow is set, deploy immediately
// If deployNow is set, deploy immediately via SSE to keep connection alive
if (data.deployNow) {
const deployResult = await deployGitStack(gitStack.id);
await auditGitStack(event, 'deploy', gitStack.id, gitStack.stackName, gitStack.environmentId);
return json({
...gitStack,
deployResult: deployResult
});
return createJobResponse(async (send) => {
try {
const deployResult = await deployGitStack(gitStack.id);
await auditGitStack(event, 'deploy', gitStack.id, gitStack.stackName, gitStack.environmentId);
send('result', {
...gitStack,
deployResult: deployResult
});
} catch (error) {
console.error('Failed to deploy git stack:', error);
send('result', {
...gitStack,
deployResult: { success: false, error: 'Failed to deploy git stack' }
});
}
}, request);
}
return json(gitStack);
} catch (error: any) {
console.error('Failed to create git stack:', error);
if (error.message?.includes('UNIQUE constraint failed')) {
if (error.message?.includes('stack_environment_variables')) {
return json({ error: 'Duplicate environment variable keys detected' }, { status: 400 });
}
return json({ error: 'A git stack with this name already exists for this environment' }, { status: 400 });
}
return json({ error: 'Failed to create git stack' }, { status: 500 });
+25 -8
View File
@@ -1,11 +1,12 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getGitStack, updateGitStack, deleteGitStack, deleteStackSource, updateStackSourceName, updateStackEnvVarsName, setStackEnvVars, getStackEnvVars } from '$lib/server/db';
import { getGitStack, updateGitStack, deleteGitStack, deleteStackSource, updateStackSourceName, updateStackEnvVarsName, setStackEnvVars, getStackEnvVars, deleteStackEnvVars } from '$lib/server/db';
import { deleteGitStackFiles, deployGitStack } from '$lib/server/git';
import { authorize } from '$lib/server/authorize';
import { registerSchedule, unregisterSchedule } from '$lib/server/scheduler';
import { auditGitStack } from '$lib/server/audit';
import { computeAuditDiff } from '$lib/utils/diff';
import { createJobResponse } from '$lib/server/sse';
// Stack name validation: must start with alphanumeric, can contain alphanumeric, hyphens, underscores
const STACK_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
@@ -131,20 +132,33 @@ export const PUT: RequestHandler = async (event) => {
await setStackEnvVars(stackName, envId, varsToSave as any);
}
// If deployNow is set, deploy after saving
// If deployNow is set, deploy after saving via SSE to keep connection alive
if (data.deployNow) {
const deployResult = await deployGitStack(id);
await auditGitStack(event, 'deploy', updated.id, updated.stackName, updated.environmentId);
return json({
...updated,
deployResult
});
return createJobResponse(async (send) => {
try {
const deployResult = await deployGitStack(id);
await auditGitStack(event, 'deploy', updated.id, updated.stackName, updated.environmentId);
send('result', {
...updated,
deployResult
});
} catch (error) {
console.error('Failed to deploy git stack:', error);
send('result', {
...updated,
deployResult: { success: false, error: 'Failed to deploy git stack' }
});
}
}, request);
}
return json(updated);
} catch (error: any) {
console.error('Failed to update git stack:', error);
if (error.message?.includes('UNIQUE constraint failed')) {
if (error.message?.includes('stack_environment_variables')) {
return json({ error: 'Duplicate environment variable keys detected' }, { status: 400 });
}
return json({ error: 'A git stack with this name already exists for this environment' }, { status: 400 });
}
return json({ error: 'Failed to update git stack' }, { status: 500 });
@@ -176,6 +190,9 @@ export const DELETE: RequestHandler = async (event) => {
// Delete the stack_sources record to free up the stack name
await deleteStackSource(existing.stackName, existing.environmentId);
// Delete all env var overrides for this stack (all environments)
await deleteStackEnvVars(existing.stackName);
// Delete from database
await deleteGitStack(id);
@@ -3,8 +3,10 @@ import type { RequestHandler } from './$types';
import { getGitStack } from '$lib/server/db';
import { deployGitStackWithProgress } from '$lib/server/git';
import { authorize } from '$lib/server/authorize';
import { createJob, appendLine, completeJob, failJob } from '$lib/server/jobs';
import { prefersJSON, sseToJSON } from '$lib/server/sse';
export const POST: RequestHandler = async ({ params, cookies }) => {
export const POST: RequestHandler = async ({ params, cookies, request }) => {
const auth = await authorize(cookies);
const id = parseInt(params.id);
@@ -25,30 +27,43 @@ export const POST: RequestHandler = async ({ params, cookies }) => {
});
}
// Create a readable stream for SSE
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
const sendEvent = (data: any) => {
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
};
try {
await deployGitStackWithProgress(id, sendEvent);
} catch (error: any) {
sendEvent({ status: 'error', error: error.message || 'Unknown error' });
} finally {
controller.close();
// Backward compat: API clients sending Accept: application/json get synchronous SSE result
if (prefersJSON(request)) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
const sendEvent = (data: unknown) => {
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
};
try {
await deployGitStackWithProgress(id, sendEvent);
} catch (error: any) {
sendEvent({ status: 'error', error: error.message || 'Unknown error' });
} finally {
controller.close();
}
}
}
});
});
const sseResponse = new Response(stream, {
headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' }
});
return sseToJSON(sseResponse);
}
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
// Job pattern: fire and forget, return jobId immediately
const job = createJob();
deployGitStackWithProgress(id, (data: unknown) => {
appendLine(job, { data });
})
.then(() => {
const lastLine = job.lines[job.lines.length - 1];
const lastData = lastLine?.data as any;
completeJob(job, lastData ?? { status: 'complete' });
})
.catch((err: unknown) => {
failJob(job, err instanceof Error ? err.message : String(err));
});
return json({ jobId: job.id });
};
@@ -4,6 +4,7 @@ import { getGitStack } from '$lib/server/db';
import { deployGitStack } from '$lib/server/git';
import { authorize } from '$lib/server/authorize';
import { auditGitStack } from '$lib/server/audit';
import { createJobResponse } from '$lib/server/sse';
export const POST: RequestHandler = async (event) => {
const { params, cookies } = event;
@@ -21,12 +22,19 @@ export const POST: RequestHandler = async (event) => {
return json({ error: 'Permission denied' }, { status: 403 });
}
const result = await deployGitStack(id);
return createJobResponse(async (send) => {
try {
const result = await deployGitStack(id);
// Audit log
await auditGitStack(event, 'deploy', id, gitStack.stackName, gitStack.environmentId);
// Audit log
await auditGitStack(event, 'deploy', id, gitStack.stackName, gitStack.environmentId);
return json(result);
send('result', result);
} catch (error) {
console.error('Failed to deploy git stack:', error);
send('result', { success: false, error: 'Failed to deploy git stack' });
}
}, event.request);
} catch (error) {
console.error('Failed to deploy git stack:', error);
return json({ error: 'Failed to deploy git stack' }, { status: 500 });