Compare commits

..

4 Commits

Author SHA1 Message Date
autofix-ci[bot]
3e2eb7213d [autofix.ci] apply automated fixes 2026-04-05 19:41:38 +00:00
Mauricio Siu
dcb95374da fix: also collect added/removed paths and filter nullish values
commit.modified can be undefined causing micromatch to throw
"Expected input to be a string". Also includes added and removed
paths from commits so watch paths can match against all changed files.
2026-04-05 13:41:17 -06:00
autofix-ci[bot]
36e131cf12 [autofix.ci] apply automated fixes 2026-04-05 19:38:19 +00:00
Mauricio Siu
17b4c0fc58 fix: webhook crash when commits array is missing and watch paths enabled
When a GitHub webhook fires with undefined commits and watch paths are
configured, flatMap on undefined crashes the handler. Default to empty
array so shouldDeploy can handle it gracefully.

Closes #4081
2026-04-05 13:37:44 -06:00
10 changed files with 79 additions and 138 deletions

View File

@@ -56,17 +56,17 @@ export const ShowEnvironment = ({ id, type }: Props) => {
const [isEnvVisible, setIsEnvVisible] = useState(true); const [isEnvVisible, setIsEnvVisible] = useState(true);
const mutationMap = { const mutationMap = {
compose: () => api.compose.saveEnvironment.useMutation(), compose: () => api.compose.update.useMutation(),
libsql: () => api.libsql.saveEnvironment.useMutation(), libsql: () => api.libsql.update.useMutation(),
mariadb: () => api.mariadb.saveEnvironment.useMutation(), mariadb: () => api.mariadb.update.useMutation(),
mongo: () => api.mongo.saveEnvironment.useMutation(), mongo: () => api.mongo.update.useMutation(),
mysql: () => api.mysql.saveEnvironment.useMutation(), mysql: () => api.mysql.update.useMutation(),
postgres: () => api.postgres.saveEnvironment.useMutation(), postgres: () => api.postgres.update.useMutation(),
redis: () => api.redis.saveEnvironment.useMutation(), redis: () => api.redis.update.useMutation(),
}; };
const { mutateAsync, isPending } = mutationMap[type] const { mutateAsync, isPending } = mutationMap[type]
? mutationMap[type]() ? mutationMap[type]()
: api.mongo.saveEnvironment.useMutation(); : api.mongo.update.useMutation();
const form = useForm<EnvironmentSchema>({ const form = useForm<EnvironmentSchema>({
defaultValues: { defaultValues: {

View File

@@ -1,14 +1,7 @@
import { toast } from "sonner";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { HelpCircle } from "lucide-react";
import { toast } from "sonner";
interface Props { interface Props {
serverId?: string; serverId?: string;
@@ -59,36 +52,7 @@ export const ToggleDockerCleanup = ({ serverId }: Props) => {
return ( return (
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Switch checked={!!enabled} onCheckedChange={handleToggle} /> <Switch checked={!!enabled} onCheckedChange={handleToggle} />
<TooltipProvider delayDuration={0}> <Label className="text-primary">Daily Docker Cleanup</Label>
<Tooltip>
<TooltipTrigger asChild>
<Label className="text-primary flex items-center gap-1.5 cursor-pointer">
Daily Docker Cleanup
<HelpCircle className="size-4 text-muted-foreground" />
</Label>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-sm">
<p>
Runs a full Docker cleanup daily, pruning stopped containers,
unused images, volumes, build cache, and system resources. This
may remove images built for Compose services that run on-demand
(backup runners, cron jobs, one-off tasks).
</p>
<p className="mt-1">
For custom cleanup strategies, use{" "}
<a
href="https://docs.dokploy.com/docs/core/schedule-jobs#example-1-automatic-docker-cleanup"
target="_blank"
rel="noopener noreferrer"
className="underline text-primary"
>
Schedule Jobs
</a>{" "}
on your web server or remote servers.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div> </div>
); );
}; };

View File

@@ -53,9 +53,14 @@ export default async function handler(
if (sourceType === "github") { if (sourceType === "github") {
const branchName = extractBranchName(req.headers, req.body); const branchName = extractBranchName(req.headers, req.body);
const normalizedCommits = req.body?.commits?.flatMap( const normalizedCommits =
(commit: any) => commit.modified, req.body?.commits
); ?.flatMap((commit: any) => [
...(commit.modified || []),
...(commit.added || []),
...(commit.removed || []),
])
.filter(Boolean) || [];
const shouldDeployPaths = shouldDeploy( const shouldDeployPaths = shouldDeploy(
composeResult.watchPaths, composeResult.watchPaths,
@@ -73,9 +78,14 @@ export default async function handler(
} }
} else if (sourceType === "gitlab") { } else if (sourceType === "gitlab") {
const branchName = extractBranchName(req.headers, req.body); const branchName = extractBranchName(req.headers, req.body);
const normalizedCommits = req.body?.commits?.flatMap( const normalizedCommits =
(commit: any) => commit.modified, req.body?.commits
); ?.flatMap((commit: any) => [
...(commit.modified || []),
...(commit.added || []),
...(commit.removed || []),
])
.filter(Boolean) || [];
const shouldDeployPaths = shouldDeploy( const shouldDeployPaths = shouldDeploy(
composeResult.watchPaths, composeResult.watchPaths,
@@ -124,17 +134,32 @@ export default async function handler(
let normalizedCommits: string[] = []; let normalizedCommits: string[] = [];
if (provider === "github") { if (provider === "github") {
normalizedCommits = req.body?.commits?.flatMap( normalizedCommits =
(commit: any) => commit.modified, req.body?.commits
); ?.flatMap((commit: any) => [
...(commit.modified || []),
...(commit.added || []),
...(commit.removed || []),
])
.filter(Boolean) || [];
} else if (provider === "gitlab") { } else if (provider === "gitlab") {
normalizedCommits = req.body?.commits?.flatMap( normalizedCommits =
(commit: any) => commit.modified, req.body?.commits
); ?.flatMap((commit: any) => [
...(commit.modified || []),
...(commit.added || []),
...(commit.removed || []),
])
.filter(Boolean) || [];
} else if (provider === "gitea") { } else if (provider === "gitea") {
normalizedCommits = req.body?.commits?.flatMap( normalizedCommits =
(commit: any) => commit.modified, req.body?.commits
); ?.flatMap((commit: any) => [
...(commit.modified || []),
...(commit.added || []),
...(commit.removed || []),
])
.filter(Boolean) || [];
} }
const shouldDeployPaths = shouldDeploy( const shouldDeployPaths = shouldDeploy(
@@ -149,9 +174,14 @@ export default async function handler(
} else if (sourceType === "gitea") { } else if (sourceType === "gitea") {
const branchName = extractBranchName(req.headers, req.body); const branchName = extractBranchName(req.headers, req.body);
const normalizedCommits = req.body?.commits?.flatMap( const normalizedCommits =
(commit: any) => commit.modified, req.body?.commits
); ?.flatMap((commit: any) => [
...(commit.modified || []),
...(commit.added || []),
...(commit.removed || []),
])
.filter(Boolean) || [];
const shouldDeployPaths = shouldDeploy( const shouldDeployPaths = shouldDeploy(
composeResult.watchPaths, composeResult.watchPaths,

View File

@@ -213,9 +213,14 @@ export default async function handler(
const deploymentTitle = extractCommitMessage(req.headers, req.body); const deploymentTitle = extractCommitMessage(req.headers, req.body);
const deploymentHash = extractHash(req.headers, req.body); const deploymentHash = extractHash(req.headers, req.body);
const owner = githubBody?.repository?.owner?.name; const owner = githubBody?.repository?.owner?.name;
const normalizedCommits = githubBody?.commits?.flatMap( const normalizedCommits =
(commit: any) => commit.modified, githubBody?.commits
); ?.flatMap((commit: any) => [
...(commit.modified || []),
...(commit.added || []),
...(commit.removed || []),
])
.filter(Boolean) || [];
const apps = await db.query.applications.findMany({ const apps = await db.query.applications.findMany({
where: and( where: and(

View File

@@ -61,7 +61,6 @@ import {
apiFindCompose, apiFindCompose,
apiRandomizeCompose, apiRandomizeCompose,
apiRedeployCompose, apiRedeployCompose,
apiSaveEnvironmentVariablesCompose,
apiUpdateCompose, apiUpdateCompose,
compose as composeTable, compose as composeTable,
environments, environments,
@@ -202,31 +201,6 @@ export const composeRouter = createTRPCRouter({
}); });
return updated; return updated;
}), }),
saveEnvironment: protectedProcedure
.input(apiSaveEnvironmentVariablesCompose)
.mutation(async ({ input, ctx }) => {
await checkServicePermissionAndAccess(ctx, input.composeId, {
envVars: ["write"],
});
const updated = await updateCompose(input.composeId, {
env: input.env,
});
if (!updated) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error adding environment variables",
});
}
await audit(ctx, {
action: "update",
resourceType: "compose",
resourceId: input.composeId,
resourceName: updated?.name,
});
return true;
}),
delete: protectedProcedure delete: protectedProcedure
.input(apiDeleteCompose) .input(apiDeleteCompose)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@@ -316,7 +290,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFetchServices) .input(apiFetchServices)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
await checkServicePermissionAndAccess(ctx, input.composeId, { await checkServicePermissionAndAccess(ctx, input.composeId, {
service: ["read"], service: ["create"],
}); });
return await loadServices(input.composeId, input.type); return await loadServices(input.composeId, input.type);
}), }),

View File

@@ -677,10 +677,7 @@ export const notificationRouter = createTRPCRouter({
} catch (error) { } catch (error) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: message: "Error testing the notification",
error instanceof Error
? `Error testing the notification: ${error.message}`
: "Error testing the notification",
cause: error, cause: error,
}); });
} }

View File

@@ -225,13 +225,6 @@ export const apiUpdateCompose = createSchema
}) })
.omit({ serverId: true }); .omit({ serverId: true });
export const apiSaveEnvironmentVariablesCompose = createSchema
.pick({
composeId: true,
env: true,
})
.required();
export const apiRandomizeCompose = createSchema export const apiRandomizeCompose = createSchema
.pick({ .pick({
composeId: true, composeId: true,

View File

@@ -251,22 +251,15 @@ export const deployCompose = async ({
} else { } else {
await execAsync(commandWithLog); await execAsync(commandWithLog);
} }
command = "set -e;";
if (compose.sourceType !== "raw") { if (compose.sourceType !== "raw") {
command = "set -e;";
command += await generateApplyPatchesCommand({ command += await generateApplyPatchesCommand({
id: compose.composeId, id: compose.composeId,
type: "compose", type: "compose",
serverId: compose.serverId, serverId: compose.serverId,
}); });
commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (compose.serverId) {
await execAsyncRemote(compose.serverId, commandWithLog);
} else {
await execAsync(commandWithLog);
}
} }
command = "set -e;";
command += await getBuildComposeCommand(entity); command += await getBuildComposeCommand(entity);
commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`; commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (compose.serverId) { if (compose.serverId) {
@@ -364,23 +357,6 @@ export const rebuildCompose = async ({
} else { } else {
await execAsync(commandWithLog); await execAsync(commandWithLog);
} }
if (compose.sourceType !== "raw") {
command = "set -e;";
command += await generateApplyPatchesCommand({
id: compose.composeId,
type: "compose",
serverId: compose.serverId,
});
commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (compose.serverId) {
await execAsyncRemote(compose.serverId, commandWithLog);
} else {
await execAsync(commandWithLog);
}
}
command = "set -e;";
command += await getBuildComposeCommand(compose); command += await getBuildComposeCommand(compose);
commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`; commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (compose.serverId) { if (compose.serverId) {

View File

@@ -240,13 +240,14 @@ export const sendBuildErrorNotifications = async ({
value: `\`\`\`${errorMessage}\`\`\``, value: `\`\`\`${errorMessage}\`\`\``,
short: false, short: false,
}, },
],
actions: [
{ {
title: "Details", type: "button",
value: `<${buildLink}|View Build Details>`, text: "View Build Details",
short: false, url: buildLink,
}, },
], ],
mrkdwn_in: ["fields"],
}, },
], ],
}); });

View File

@@ -256,13 +256,14 @@ export const sendBuildSuccessNotifications = async ({
value: date.toLocaleString(), value: date.toLocaleString(),
short: true, short: true,
}, },
],
actions: [
{ {
title: "Details", type: "button",
value: `<${buildLink}|View Build Details>`, text: "View Build Details",
short: false, url: buildLink,
}, },
], ],
mrkdwn_in: ["fields"],
}, },
], ],
}); });