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 mutationMap = {
compose: () => api.compose.saveEnvironment.useMutation(),
libsql: () => api.libsql.saveEnvironment.useMutation(),
mariadb: () => api.mariadb.saveEnvironment.useMutation(),
mongo: () => api.mongo.saveEnvironment.useMutation(),
mysql: () => api.mysql.saveEnvironment.useMutation(),
postgres: () => api.postgres.saveEnvironment.useMutation(),
redis: () => api.redis.saveEnvironment.useMutation(),
compose: () => api.compose.update.useMutation(),
libsql: () => api.libsql.update.useMutation(),
mariadb: () => api.mariadb.update.useMutation(),
mongo: () => api.mongo.update.useMutation(),
mysql: () => api.mysql.update.useMutation(),
postgres: () => api.postgres.update.useMutation(),
redis: () => api.redis.update.useMutation(),
};
const { mutateAsync, isPending } = mutationMap[type]
? mutationMap[type]()
: api.mongo.saveEnvironment.useMutation();
: api.mongo.update.useMutation();
const form = useForm<EnvironmentSchema>({
defaultValues: {

View File

@@ -1,14 +1,7 @@
import { toast } from "sonner";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import { HelpCircle } from "lucide-react";
import { toast } from "sonner";
interface Props {
serverId?: string;
@@ -59,36 +52,7 @@ export const ToggleDockerCleanup = ({ serverId }: Props) => {
return (
<div className="flex items-center gap-4">
<Switch checked={!!enabled} onCheckedChange={handleToggle} />
<TooltipProvider delayDuration={0}>
<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>
<Label className="text-primary">Daily Docker Cleanup</Label>
</div>
);
};

View File

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

View File

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

View File

@@ -61,7 +61,6 @@ import {
apiFindCompose,
apiRandomizeCompose,
apiRedeployCompose,
apiSaveEnvironmentVariablesCompose,
apiUpdateCompose,
compose as composeTable,
environments,
@@ -202,31 +201,6 @@ export const composeRouter = createTRPCRouter({
});
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
.input(apiDeleteCompose)
.mutation(async ({ input, ctx }) => {
@@ -316,7 +290,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFetchServices)
.query(async ({ input, ctx }) => {
await checkServicePermissionAndAccess(ctx, input.composeId, {
service: ["read"],
service: ["create"],
});
return await loadServices(input.composeId, input.type);
}),

View File

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

View File

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

View File

@@ -251,22 +251,15 @@ export const deployCompose = async ({
} else {
await execAsync(commandWithLog);
}
command = "set -e;";
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(entity);
commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (compose.serverId) {
@@ -364,23 +357,6 @@ export const rebuildCompose = async ({
} else {
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);
commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
if (compose.serverId) {

View File

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

View File

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