From 4900db402c9cbd6a5b4bdd8bf5a6a79c84a70218 Mon Sep 17 00:00:00 2001
From: Aleksy Rybicki <alekso.php@gmail.com>
Date: Thu, 27 Jun 2024 11:31:34 +0100
Subject: [PATCH 1/4] migrating to fetch from axios using codemod axios/fetch

---
 apps/backend/src/plugins/authPlugin.ts        |  226 ++--
 apps/backend/src/publishHandler.ts            |   22 +-
 apps/backend/src/services/GithubProvider.ts   |  342 +++---
 apps/backend/src/services/PostHogService.ts   |   89 +-
 apps/cli/src/fileDownloadService.ts           |    8 +-
 apps/modgpt/src/plugins/authPlugin.ts         |    6 +-
 apps/task-manager/src/services/Auth.ts        |   80 +-
 apps/task-manager/src/util.ts                 |   77 +-
 .../src/components/webview/MainProvider.ts    | 1053 +++++++++--------
 .../fetch/__testfixtures__/fixture1.input.ts  |    6 +-
 .../fetch/__testfixtures__/fixture2.input.ts  |   35 +-
 pnpm-lock.yaml                                |   32 +-
 12 files changed, 1020 insertions(+), 956 deletions(-)

diff --git a/apps/backend/src/plugins/authPlugin.ts b/apps/backend/src/plugins/authPlugin.ts
index 8561b9017..9d0fe3a83 100644
--- a/apps/backend/src/plugins/authPlugin.ts
+++ b/apps/backend/src/plugins/authPlugin.ts
@@ -1,128 +1,124 @@
-import type { OrganizationMembership, User } from "@codemod-com/utilities";
-import axios from "axios";
-import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
-import fp from "fastify-plugin";
-import { environment } from "../util";
+import type { OrganizationMembership, User } from '@codemod-com/utilities';
+import axios from 'axios';
+import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
+import fp from 'fastify-plugin';
+import { environment } from '../util';
 
 export interface UserDataPopulatedRequest extends FastifyRequest {
-  user?: User;
-  organizations?: OrganizationMembership[];
-  allowedNamespaces?: string[];
+	user?: User;
+	organizations?: OrganizationMembership[];
+	allowedNamespaces?: string[];
 }
 
 export interface OAuthTokenPopulatedRequest extends FastifyRequest {
-  token?: string;
+	token?: string;
 }
 
-declare module "fastify" {
-  interface FastifyInstance {
-    authenticate: (
-      request: FastifyRequest,
-      reply: FastifyReply,
-    ) => Promise<void>;
-    getUserData: (
-      request: FastifyRequest & UserDataPopulatedRequest,
-      reply: FastifyReply,
-    ) => Promise<void>;
-    getOAuthToken: (
-      request: FastifyRequest & OAuthTokenPopulatedRequest,
-      reply: FastifyReply,
-    ) => Promise<void>;
-  }
+declare module 'fastify' {
+	interface FastifyInstance {
+		authenticate: (
+			request: FastifyRequest,
+			reply: FastifyReply,
+		) => Promise<void>;
+		getUserData: (
+			request: FastifyRequest & UserDataPopulatedRequest,
+			reply: FastifyReply,
+		) => Promise<void>;
+		getOAuthToken: (
+			request: FastifyRequest & OAuthTokenPopulatedRequest,
+			reply: FastifyReply,
+		) => Promise<void>;
+	}
 }
 
 async function authPlugin(fastify: FastifyInstance, _opts: unknown) {
-  fastify.decorate(
-    "authenticate",
-    async (request: FastifyRequest, reply: FastifyReply) => {
-      try {
-        const authHeader = request.headers.authorization;
-
-        if (!authHeader) reply.code(401).send({ error: "Unauthorized" });
-
-        await axios.get(`${environment.AUTH_SERVICE_URL}/verifyToken`, {
-          headers: {
-            Authorization: authHeader,
-          },
-        });
-      } catch (error) {
-        console.error(error);
-        reply.code(401).send({ error: "Unauthorized" });
-      }
-    },
-  );
-
-  fastify.decorate(
-    "getUserData",
-    async (
-      request: FastifyRequest & {
-        user?: User;
-        organizations?: OrganizationMembership[];
-        allowedNamespaces?: string[];
-      },
-      reply: FastifyReply,
-    ) => {
-      try {
-        const authHeader = request.headers.authorization;
-
-        if (!authHeader) {
-          request.user = undefined;
-          request.organizations = undefined;
-          request.allowedNamespaces = undefined;
-          return;
-        }
-
-        const { data } = await axios.get(
-          `${environment.AUTH_SERVICE_URL}/userData`,
-          {
-            headers: {
-              Authorization: authHeader,
-            },
-          },
-        );
-
-        const { user, organizations, allowedNamespaces } = data;
-
-        request.user = user;
-        request.organizations = organizations;
-        request.allowedNamespaces = allowedNamespaces;
-      } catch (error) {
-        console.error(error);
-        reply.code(401).send({ error: "Unauthorized" });
-      }
-    },
-  );
-
-  fastify.decorate(
-    "getOAuthToken",
-    async (
-      request: FastifyRequest & {
-        token?: string;
-      },
-      reply: FastifyReply,
-    ) => {
-      try {
-        const authHeader = request.headers.authorization;
-
-        if (!authHeader) reply.code(401).send({ error: "Unauthorized" });
-
-        const { data } = await axios.get(
-          `${environment.AUTH_SERVICE_URL}/oAuthToken`,
-          {
-            headers: {
-              Authorization: authHeader,
-            },
-          },
-        );
-
-        const { token } = data;
-
-        request.token = token;
-      } catch {
-        reply.code(401).send({ error: "Unauthorized" });
-      }
-    },
-  );
+	fastify.decorate(
+		'authenticate',
+		async (request: FastifyRequest, reply: FastifyReply) => {
+			try {
+				const authHeader = request.headers.authorization;
+
+				if (!authHeader)
+					reply.code(401).send({ error: 'Unauthorized' });
+
+				await fetch(`${environment.AUTH_SERVICE_URL}/verifyToken`, {
+					headers: { Authorization: authHeader },
+				});
+			} catch (error) {
+				console.error(error);
+				reply.code(401).send({ error: 'Unauthorized' });
+			}
+		},
+	);
+
+	fastify.decorate(
+		'getUserData',
+		async (
+			request: FastifyRequest & {
+				user?: User;
+				organizations?: OrganizationMembership[];
+				allowedNamespaces?: string[];
+			},
+			reply: FastifyReply,
+		) => {
+			try {
+				const authHeader = request.headers.authorization;
+
+				if (!authHeader) {
+					request.user = undefined;
+					request.organizations = undefined;
+					request.allowedNamespaces = undefined;
+					return;
+				}
+
+				const response = await fetch(
+					`${environment.AUTH_SERVICE_URL}/userData`,
+					{ headers: { Authorization: authHeader } },
+				);
+				if (!response.ok) throw new Error('Failed to fetch userData');
+				const { data } = { data: await response.json() };
+
+				const { user, organizations, allowedNamespaces } = data;
+
+				request.user = user;
+				request.organizations = organizations;
+				request.allowedNamespaces = allowedNamespaces;
+			} catch (error) {
+				console.error(error);
+				reply.code(401).send({ error: 'Unauthorized' });
+			}
+		},
+	);
+
+	fastify.decorate(
+		'getOAuthToken',
+		async (
+			request: FastifyRequest & {
+				token?: string;
+			},
+			reply: FastifyReply,
+		) => {
+			try {
+				const authHeader = request.headers.authorization;
+
+				if (!authHeader)
+					reply.code(401).send({ error: 'Unauthorized' });
+
+				const response = await fetch(
+					`${environment.AUTH_SERVICE_URL}/oAuthToken`,
+					{ headers: { Authorization: authHeader } },
+				);
+				if (!response.ok) throw new Error('Failed to fetch oAuthToken');
+				const { data } = { data: await response.json() };
+
+				const { token } = data;
+
+				request.token = token;
+			} catch {
+				reply.code(401).send({ error: 'Unauthorized' });
+			}
+		},
+	);
 }
 
 export default fp(authPlugin);
diff --git a/apps/backend/src/publishHandler.ts b/apps/backend/src/publishHandler.ts
index 410709ed7..1982b81c5 100644
--- a/apps/backend/src/publishHandler.ts
+++ b/apps/backend/src/publishHandler.ts
@@ -350,27 +350,7 @@ export const publishHandler: RouteHandler<{
 
     if (latestVersion === null) {
       try {
-        await axios.post(
-          "https://hooks.zapier.com/hooks/catch/18983913/2ybuovt/",
-          {
-            codemod: {
-              name,
-              from: codemodRc.applicability?.from?.map((tuple) =>
-                tuple.join(" "),
-              ),
-              to: codemodRc.applicability?.to?.map((tuple) => tuple.join(" ")),
-              engine: codemodRc.engine,
-              publishedAt: createdAtTimestamp,
-            },
-            author: {
-              username,
-              name: `${firstName ?? ""} ${lastName ?? ""}`.trim() || null,
-              email:
-                emailAddresses.find((e) => e.id === primaryEmailAddressId)
-                  ?.emailAddress ?? null,
-            },
-          },
-        );
+        await const controller = new AbortController();const signal = controller.signal;setTimeout(() => controller.abort(), 5000);try {  const response = await fetch(    "https://hooks.zapier.com/hooks/catch/18983913/2ybuovt/",    {      method: 'POST',      body: JSON.stringify({        codemod: {          name,          from: codemodRc.applicability?.from?.map((tuple) => tuple.join(" ")),          to: codemodRc.applicability?.to?.map((tuple) => tuple.join(" ")),          engine: codemodRc.engine,          publishedAt: createdAtTimestamp,        },        author: {          username,          name: `${firstName ?? ""} ${lastName ?? ""}`.trim() || null,          email: emailAddresses.find((e) => e.id === primaryEmailAddressId)?.emailAddress ?? null,        },      }),      headers: {        'Content-Type': 'application/json'      },      signal: signal    }  );  if (!response.ok) {    throw new Error('Network response was not ok');  }  const result = { data: await response.json() };} catch (err) {  console.error("Failed calling Zapier hook:", err);};
       } catch (err) {
         console.error("Failed calling Zapier hook:", err);
       }
diff --git a/apps/backend/src/services/GithubProvider.ts b/apps/backend/src/services/GithubProvider.ts
index 64af7ebe5..c886c389c 100644
--- a/apps/backend/src/services/GithubProvider.ts
+++ b/apps/backend/src/services/GithubProvider.ts
@@ -1,185 +1,195 @@
-import axios, { type AxiosResponse } from "axios";
-import gh from "parse-github-url";
+import axios, { type AxiosResponse } from 'axios';
+import gh from 'parse-github-url';
 import type {
-  Assignee,
-  CreatePRParams,
-  GHBranch,
-  GithubContent,
-  GithubRepository,
-  Issue,
-  ListPRParams,
-  NewIssueParams,
-  PullRequest,
-  SourceControlProvider,
-} from "./SourceControl.js";
+	Assignee,
+	CreatePRParams,
+	GHBranch,
+	GithubContent,
+	GithubRepository,
+	Issue,
+	ListPRParams,
+	NewIssueParams,
+	PullRequest,
+	SourceControlProvider,
+} from './SourceControl.js';
 
 type Repository = {
-  owner: string;
-  name: string;
+	owner: string;
+	name: string;
 };
 
 class InvalidGithubUrlError extends Error {}
 class ParseGithubUrlError extends Error {}
 
 function parseGithubRepoUrl(url: string): Repository {
-  try {
-    const { owner, name } = gh(url) ?? {};
-
-    if (!owner || !name) {
-      throw new InvalidGithubUrlError("Missing owner or name");
-    }
-
-    return { owner, name };
-  } catch (e) {
-    if (e instanceof InvalidGithubUrlError) {
-      throw e;
-    }
-
-    const errorMessage = e instanceof Error ? e.message : String(e);
-    throw new ParseGithubUrlError(errorMessage);
-  }
+	try {
+		const { owner, name } = gh(url) ?? {};
+
+		if (!owner || !name) {
+			throw new InvalidGithubUrlError('Missing owner or name');
+		}
+
+		return { owner, name };
+	} catch (e) {
+		if (e instanceof InvalidGithubUrlError) {
+			throw e;
+		}
+
+		const errorMessage = e instanceof Error ? e.message : String(e);
+		throw new ParseGithubUrlError(errorMessage);
+	}
 }
 
 const withPagination = async (
-  paginatedRequest: (page: string) => Promise<AxiosResponse<any[]>>,
+	paginatedRequest: (page: string) => Promise<AxiosResponse<any[]>>,
 ) => {
-  const nextPattern = /(?<=<)([\S]*)(?=>; rel="Next")/i;
-  let nextPage: string | null = "1";
-  let data: any[] = [];
-
-  while (nextPage !== null) {
-    const response = await paginatedRequest(nextPage);
-    data = [...data, ...(response.data ?? [])];
-
-    const linkHeader = response.headers.link;
-
-    if (typeof linkHeader === "string" && linkHeader.includes(`rel=\"next\"`)) {
-      const nextUrl = linkHeader.match(nextPattern)?.[0];
-      nextPage = nextUrl ? new URL(nextUrl).searchParams.get("page") : null;
-    } else {
-      nextPage = null;
-    }
-  }
-
-  return data;
+	const nextPattern = /(?<=<)([\S]*)(?=>; rel="Next")/i;
+	let nextPage: string | null = '1';
+	let data: any[] = [];
+
+	while (nextPage !== null) {
+		const response = await paginatedRequest(nextPage);
+		data = [...data, ...(response.data ?? [])];
+
+		const linkHeader = response.headers.link;
+
+		if (
+			typeof linkHeader === 'string' &&
+			linkHeader.includes(`rel=\"next\"`)
+		) {
+			const nextUrl = linkHeader.match(nextPattern)?.[0];
+			nextPage = nextUrl
+				? new URL(nextUrl).searchParams.get('page')
+				: null;
+		} else {
+			nextPage = null;
+		}
+	}
+
+	return data;
 };
 
 const PER_PAGE = 99;
 
 export class GithubProvider implements SourceControlProvider {
-  private readonly __repo: string | null = null;
-  private readonly __baseUrl: string;
-  private readonly __authHeader: string;
-
-  constructor(oAuthToken: string, repoUrl: string | null) {
-    this.__baseUrl = "https://api.github.com";
-    this.__repo = repoUrl;
-    this.__authHeader = `Bearer ${oAuthToken}`;
-  }
-
-  private get __repoUrl() {
-    const { owner, name } = parseGithubRepoUrl(this.__repo ?? "");
-
-    return `${this.__baseUrl}/repos/${owner}/${name}`;
-  }
-
-  async createIssue(params: NewIssueParams): Promise<Issue> {
-    const res = await axios.post(`${this.__repoUrl}/issues`, params, {
-      headers: {
-        Authorization: this.__authHeader,
-      },
-    });
-
-    return res.data;
-  }
-
-  async createPullRequest(params: CreatePRParams): Promise<PullRequest> {
-    const res = await axios.post(`${this.__repoUrl}/pulls`, params, {
-      headers: {
-        Authorization: this.__authHeader,
-      },
-    });
-
-    return res.data;
-  }
-
-  async getPullRequests(params: ListPRParams): Promise<PullRequest[]> {
-    const queryParams = Object.entries(params).reduce<Record<string, string>>(
-      (acc, [key, value]) => {
-        if (value) {
-          acc[key] = value;
-        }
-
-        return acc;
-      },
-      {},
-    );
-
-    const query = new URLSearchParams(queryParams).toString();
-
-    const res = await axios.get(`${this.__repoUrl}/pulls?${query}`, {
-      headers: {
-        Authorization: this.__authHeader,
-      },
-    });
-
-    return res.data;
-  }
-
-  async getAssignees(): Promise<Assignee[]> {
-    const res = await axios.get(`${this.__repoUrl}/assignees`, {
-      headers: {
-        Authorization: this.__authHeader,
-      },
-    });
-
-    return res.data;
-  }
-
-  private __getUserRepositories = async (
-    page: string,
-  ): Promise<AxiosResponse<GithubRepository[]>> => {
-    return await axios.get<GithubRepository[]>(
-      `https://api.github.com/user/repos?per_page=${PER_PAGE}&page=${page}`,
-      {
-        headers: {
-          Authorization: this.__authHeader,
-        },
-      },
-    );
-  };
-
-  async getUserRepositories(): Promise<GithubRepository[]> {
-    return await withPagination(this.__getUserRepositories);
-  }
-
-  private __getBranches = async (
-    page: string,
-  ): Promise<AxiosResponse<GHBranch[]>> => {
-    return await axios.get(
-      `${this.__repoUrl}/branches?per_page=${PER_PAGE}&page=${page}`,
-      {
-        headers: {
-          Authorization: this.__authHeader,
-        },
-      },
-    );
-  };
-
-  async getBranches(): Promise<string[]> {
-    return await withPagination(this.__getBranches);
-  }
-
-  async getRepoContents(branchName: string): Promise<GithubContent[]> {
-    const res = await axios.get(
-      `${this.__repoUrl}/contents?ref=${branchName}`,
-      {
-        headers: {
-          Authorization: this.__authHeader,
-        },
-      },
-    );
-
-    return res.data;
-  }
+	private readonly __repo: string | null = null;
+	private readonly __baseUrl: string;
+	private readonly __authHeader: string;
+
+	constructor(oAuthToken: string, repoUrl: string | null) {
+		this.__baseUrl = 'https://api.github.com';
+		this.__repo = repoUrl;
+		this.__authHeader = `Bearer ${oAuthToken}`;
+	}
+
+	private get __repoUrl() {
+		const { owner, name } = parseGithubRepoUrl(this.__repo ?? '');
+
+		return `${this.__baseUrl}/repos/${owner}/${name}`;
+	}
+
+	async createIssue(params: NewIssueParams): Promise<Issue> {
+		const response = await fetch(`${this.__repoUrl}/issues`, {
+			method: 'POST',
+			headers: {
+				Authorization: this.__authHeader,
+				'Content-Type': 'application/json',
+			},
+			body: JSON.stringify(params),
+		});
+		if (!response.ok) throw new Error('Network response was not ok');
+		const res = { data: (await response.json()) as Issue };
+
+		return res.data;
+	}
+
+	async createPullRequest(params: CreatePRParams): Promise<PullRequest> {
+		const response = await fetch(`${this.__repoUrl}/pulls`, {
+			method: 'POST',
+			headers: {
+				Authorization: this.__authHeader,
+				'Content-Type': 'application/json',
+			},
+			body: JSON.stringify(params),
+		});
+		if (!response.ok) throw new Error('Network response was not ok');
+		const res = { data: (await response.json()) as PullRequest };
+
+		return res.data;
+	}
+
+	async getPullRequests(params: ListPRParams): Promise<PullRequest[]> {
+		const queryParams = Object.entries(params).reduce<
+			Record<string, string>
+		>((acc, [key, value]) => {
+			if (value) {
+				acc[key] = value;
+			}
+
+			return acc;
+		}, {});
+
+		const query = new URLSearchParams(queryParams).toString();
+
+		const response = await fetch(`${this.__repoUrl}/pulls?${query}`, {
+			headers: { Authorization: this.__authHeader },
+		});
+		if (!response.ok) throw new Error('Network response was not ok');
+		const res = { data: (await response.json()) as PullRequest[] };
+
+		return res.data;
+	}
+
+	async getAssignees(): Promise<Assignee[]> {
+		const response = await fetch(`${this.__repoUrl}/assignees`, {
+			headers: { Authorization: this.__authHeader },
+		});
+		if (!response.ok) throw new Error('Network response was not ok');
+		const res = { data: (await response.json()) as Assignee[] };
+
+		return res.data;
+	}
+
+	private __getUserRepositories = async (
+		page: string,
+	): Promise<AxiosResponse<GithubRepository[]>> => {
+		return await axios.get<GithubRepository[]>(
+			`https://api.github.com/user/repos?per_page=${PER_PAGE}&page=${page}`,
+			{
+				headers: {
+					Authorization: this.__authHeader,
+				},
+			},
+		);
+	};
+
+	async getUserRepositories(): Promise<GithubRepository[]> {
+		return await withPagination(this.__getUserRepositories);
+	}
+
+	private __getBranches = async (
+		page: string,
+	): Promise<AxiosResponse<GHBranch[]>> => {
+		const response = await fetch(
+			`${this.__repoUrl}/branches?per_page=${PER_PAGE}&page=${page}`,
+			{ headers: { Authorization: this.__authHeader } },
+		);
+		if (!response.ok) throw new Error('Network response was not ok');
+		return { data: (await response.json()) as GHBranch[] };
+	};
+
+	async getBranches(): Promise<string[]> {
+		return await withPagination(this.__getBranches);
+	}
+
+	async getRepoContents(branchName: string): Promise<GithubContent[]> {
+		const response = await fetch(
+			`${this.__repoUrl}/contents?ref=${branchName}`,
+			{ headers: { Authorization: this.__authHeader } },
+		);
+		if (!response.ok) throw new Error('Network response was not ok');
+		const res = { data: (await response.json()) as GithubContent[] };
+
+		return res.data;
+	}
 }
diff --git a/apps/backend/src/services/PostHogService.ts b/apps/backend/src/services/PostHogService.ts
index 90970916c..ff3c06d62 100644
--- a/apps/backend/src/services/PostHogService.ts
+++ b/apps/backend/src/services/PostHogService.ts
@@ -1,50 +1,59 @@
-import { buildCodemodSlug } from "@codemod-com/utilities";
-import axios, { isAxiosError } from "axios";
+import { buildCodemodSlug } from '@codemod-com/utilities';
+import axios, { isAxiosError } from 'axios';
 
 export class PostHogCodemodNotFoundError extends Error {}
 
 export class PostHogService {
-  private readonly __authHeader: string;
-  private readonly __projectId: string;
+	private readonly __authHeader: string;
+	private readonly __projectId: string;
 
-  constructor(authKey: string, projectId: string) {
-    this.__authHeader = `Bearer ${authKey}`;
-    this.__projectId = projectId;
-  }
+	constructor(authKey: string, projectId: string) {
+		this.__authHeader = `Bearer ${authKey}`;
+		this.__projectId = projectId;
+	}
 
-  async getCodemodTotalRuns(): Promise<Array<{ slug: string; runs: number }>> {
-    try {
-      const { data } = await axios.post(
-        `https://app.posthog.com/api/projects/${this.__projectId}/query/`,
-        {
-          query: {
-            kind: "HogQLQuery",
-            query:
-              "select properties.codemodName, count(*) from events where event in ('codemod.CLI.codemodExecuted', 'codemod.VSCE.codemodExecuted') group by properties.codemodName limit 500",
-          },
-        },
-        {
-          headers: {
-            Authorization: this.__authHeader,
-          },
-        },
-      );
+	async getCodemodTotalRuns(): Promise<
+		Array<{ slug: string; runs: number }>
+	> {
+		try {
+			const response = await fetch(
+				`https://app.posthog.com/api/projects/${this.__projectId}/query/`,
+				{
+					method: 'POST',
+					headers: {
+						'Content-Type': 'application/json',
+						Authorization: this.__authHeader,
+					},
+					body: JSON.stringify({
+						query: {
+							kind: 'HogQLQuery',
+							query: "select properties.codemodName, count(*) from events where event in ('codemod.CLI.codemodExecuted', 'codemod.VSCE.codemodExecuted') group by properties.codemodName limit 500",
+						},
+					}),
+				},
+			);
+			if (!response.ok) {
+				throw new Error(`HTTP error! status: ${response.status}`);
+			}
+			const data = await response.json();
 
-      const result = data?.results?.map((value: [string, number]) => ({
-        // @TODO add isLocal field to telemetry event, exclude local events from total runs
-        slug: buildCodemodSlug(value[0].replaceAll(" (from user machine)", "")),
-        runs: value[1],
-      }));
+			const result = data?.results?.map((value: [string, number]) => ({
+				// @TODO add isLocal field to telemetry event, exclude local events from total runs
+				slug: buildCodemodSlug(
+					value[0].replaceAll(' (from user machine)', ''),
+				),
+				runs: value[1],
+			}));
 
-      return result;
-    } catch (error) {
-      const errorMessage = isAxiosError<{ message: string }>(error)
-        ? error.response?.data.message
-        : (error as Error).message;
+			return result;
+		} catch (error) {
+			const errorMessage = isAxiosError<{ message: string }>(error)
+				? error.response?.data.message
+				: (error as Error).message;
 
-      throw new PostHogCodemodNotFoundError(
-        `Failed to retrieve events. Reason: ${errorMessage}`,
-      );
-    }
-  }
+			throw new PostHogCodemodNotFoundError(
+				`Failed to retrieve events. Reason: ${errorMessage}`,
+			);
+		}
+	}
 }
diff --git a/apps/cli/src/fileDownloadService.ts b/apps/cli/src/fileDownloadService.ts
index 59d7adf9d..70d885381 100644
--- a/apps/cli/src/fileDownloadService.ts
+++ b/apps/cli/src/fileDownloadService.ts
@@ -43,9 +43,7 @@ export class FileDownloadService implements FileDownloadServiceBlueprint {
       }
     }
 
-    const { data } = await axios.get(url, {
-      responseType: "arraybuffer",
-    });
+    const response = await fetch(url);if (!response.ok) throw new Error('Network response was not ok.');const data = await response.arrayBuffer();
 
     const buffer = Buffer.from(data);
 
@@ -70,9 +68,7 @@ export class FileDownloadService implements FileDownloadServiceBlueprint {
     let response: AxiosResponse;
 
     try {
-      response = await axios.head(url, {
-        timeout: 15000,
-      });
+      response = await fetch(url, { signal: AbortSignal.timeout(15000) })if (!response.ok) throw new Error('Network response was not ok.');;
     } catch (error) {
       if (!isAxiosError(error)) {
         throw error;
diff --git a/apps/modgpt/src/plugins/authPlugin.ts b/apps/modgpt/src/plugins/authPlugin.ts
index f81cd4c98..aa05eb427 100644
--- a/apps/modgpt/src/plugins/authPlugin.ts
+++ b/apps/modgpt/src/plugins/authPlugin.ts
@@ -32,11 +32,7 @@ async function authPlugin(fastify: FastifyInstance, _opts: unknown) {
 
         if (!authHeader) reply.code(401).send({ error: "Unauthorized" });
 
-        await axios.get(`${environment.AUTH_SERVICE_URL}/verifyToken`, {
-          headers: {
-            Authorization: authHeader,
-          },
-        });
+        await const controller = new AbortController();setTimeout(() => controller.abort(), 5000); // Assuming a default timeout of 5000ms as it was not specified in the original axios callconst response = await fetch(`${environment.AUTH_SERVICE_URL}/verifyToken`, {  headers: {    Authorization: authHeader,  },  signal: controller.signal});if (!response.ok) throw new Error('Failed to fetch');const result = { data: await response.json() };;
       } catch (error) {
         console.log(error);
       }
diff --git a/apps/task-manager/src/services/Auth.ts b/apps/task-manager/src/services/Auth.ts
index 1a91a7e79..12020be75 100644
--- a/apps/task-manager/src/services/Auth.ts
+++ b/apps/task-manager/src/services/Auth.ts
@@ -1,47 +1,47 @@
-import axios, { isAxiosError } from "axios";
+import axios, { isAxiosError } from 'axios';
 
 export class AuthError extends Error {}
 
 const USER_ID_REGEX = /^[a-z0-9_]+$/i;
 
 export class AuthService {
-  private readonly __authHeader: string;
-
-  constructor(authKey: string) {
-    if (!authKey) {
-      throw new AuthError("Invalid auth key provided.");
-    }
-    this.__authHeader = `Bearer ${authKey}`;
-  }
-
-  async getAuthToken(userId: string): Promise<string> {
-    try {
-      if (!USER_ID_REGEX.test(userId)) {
-        throw new AuthError("Invalid userId.");
-      }
-
-      const result = await axios.get(
-        `https://api.clerk.dev/v1/users/${userId}/oauth_access_tokens/github`,
-        {
-          headers: {
-            Authorization: this.__authHeader,
-          },
-        },
-      );
-
-      const token = result.data[0]?.token;
-
-      if (!token) {
-        throw new AuthError("Missing OAuth token");
-      }
-
-      return token;
-    } catch (error) {
-      const { message } = error as Error;
-
-      throw new AuthError(
-        `Failed to retrieve OAuth token for GitHub. Reason: ${message}`,
-      );
-    }
-  }
+	private readonly __authHeader: string;
+
+	constructor(authKey: string) {
+		if (!authKey) {
+			throw new AuthError('Invalid auth key provided.');
+		}
+		this.__authHeader = `Bearer ${authKey}`;
+	}
+
+	async getAuthToken(userId: string): Promise<string> {
+		try {
+			if (!USER_ID_REGEX.test(userId)) {
+				throw new AuthError('Invalid userId.');
+			}
+
+			const response = await fetch(
+				`https://api.clerk.dev/v1/users/${userId}/oauth_access_tokens/github`,
+				{ headers: { Authorization: this.__authHeader } },
+			);
+			if (!response.ok) {
+				throw new Error('Failed to fetch data');
+			}
+			const result = { data: await response.json() };
+
+			const token = result.data[0]?.token;
+
+			if (!token) {
+				throw new AuthError('Missing OAuth token');
+			}
+
+			return token;
+		} catch (error) {
+			const { message } = error as Error;
+
+			throw new AuthError(
+				`Failed to retrieve OAuth token for GitHub. Reason: ${message}`,
+			);
+		}
+	}
 }
diff --git a/apps/task-manager/src/util.ts b/apps/task-manager/src/util.ts
index 80fe2df47..1135c7495 100644
--- a/apps/task-manager/src/util.ts
+++ b/apps/task-manager/src/util.ts
@@ -1,7 +1,7 @@
-import axios, { type AxiosRequestConfig, type AxiosResponse } from "axios";
-import gh from "parse-github-url";
+import axios, { type AxiosRequestConfig, type AxiosResponse } from 'axios';
+import gh from 'parse-github-url';
 
-import { parseEnvironment } from "./schemata/env.js";
+import { parseEnvironment } from './schemata/env.js';
 
 export const environment = parseEnvironment(process.env);
 
@@ -10,40 +10,53 @@ class ParseGithubUrlError extends Error {}
 class AxiosRequestError extends Error {}
 
 type Repository = {
-  authorName: string;
-  repoName: string;
+	authorName: string;
+	repoName: string;
 };
 
 export function parseGithubRepoUrl(url: string): Repository {
-  try {
-    const { owner, name } = gh(url) ?? {};
-    if (!owner || !name)
-      throw new InvalidGithubUrlError("Missing owner or name");
-
-    return { authorName: owner, repoName: name };
-  } catch (error) {
-    const errorMessage = error instanceof Error ? error.message : String(error);
-    throw new ParseGithubUrlError(errorMessage);
-  }
+	try {
+		const { owner, name } = gh(url) ?? {};
+		if (!owner || !name)
+			throw new InvalidGithubUrlError('Missing owner or name');
+
+		return { authorName: owner, repoName: name };
+	} catch (error) {
+		const errorMessage =
+			error instanceof Error ? error.message : String(error);
+		throw new ParseGithubUrlError(errorMessage);
+	}
 }
 
 export async function axiosRequest<T>(
-  url: string,
-  method: "get" | "post" | "put" | "delete",
-  data: Record<string, unknown> | null,
-  headers?: Record<string, string>,
+	url: string,
+	method: 'get' | 'post' | 'put' | 'delete',
+	data: Record<string, unknown> | null,
+	headers?: Record<string, string>,
 ): Promise<T> {
-  try {
-    const config: AxiosRequestConfig = {
-      url,
-      method,
-      data,
-      headers,
-    };
-
-    const res: AxiosResponse<T> = await axios(config);
-    return res.data;
-  } catch (error) {
-    throw new AxiosRequestError();
-  }
+	try {
+		const config: AxiosRequestConfig = {
+			url,
+			method,
+			data,
+			headers,
+		};
+
+		const res: AxiosResponse<T> = await fetch(config.url, {
+			method: config.method,
+			headers: config.headers,
+			body: JSON.stringify(config.data),
+			signal: config.timeout
+				? AbortSignal.timeout(config.timeout)
+				: undefined,
+		}).then((response) => {
+			if (!response.ok) {
+				throw new AxiosRequestError();
+			}
+			return response.json().then((data) => ({ data }));
+		});
+		return res.data;
+	} catch (error) {
+		throw new AxiosRequestError();
+	}
 }
diff --git a/apps/vsce/src/components/webview/MainProvider.ts b/apps/vsce/src/components/webview/MainProvider.ts
index 92f30c2c9..fba6eaf35 100644
--- a/apps/vsce/src/components/webview/MainProvider.ts
+++ b/apps/vsce/src/components/webview/MainProvider.ts
@@ -1,522 +1,559 @@
-import axios from "axios";
-import areEqual from "fast-deep-equal";
-import { glob } from "glob";
+import axios from 'axios';
+import areEqual from 'fast-deep-equal';
+import { glob } from 'glob';
 import {
-  type ExtensionContext,
-  Uri,
-  type WebviewView,
-  type WebviewViewProvider,
-  commands,
-  window,
-  workspace,
-} from "vscode";
-import type { Store } from "../../data";
-import { actions } from "../../data/slice";
-import { createIssueResponseCodec } from "../../github/types";
+	type ExtensionContext,
+	Uri,
+	type WebviewView,
+	type WebviewViewProvider,
+	commands,
+	window,
+	workspace,
+} from 'vscode';
+import type { Store } from '../../data';
+import { actions } from '../../data/slice';
+import { createIssueResponseCodec } from '../../github/types';
 import {
-  type CodemodNodeHashDigest,
-  relativeToAbsolutePath,
-  selectCodemodArguments,
-} from "../../selectors/selectCodemodTree";
-import { selectMainWebviewViewProps } from "../../selectors/selectMainWebviewViewProps";
-import { buildGlobPattern, isNeitherNullNorUndefined } from "../../utilities";
-import type { EngineService } from "../engineService";
-import { type MessageBus, MessageKind } from "../messageBus";
-import { WebviewResolver } from "./WebviewResolver";
+	type CodemodNodeHashDigest,
+	relativeToAbsolutePath,
+	selectCodemodArguments,
+} from '../../selectors/selectCodemodTree';
+import { selectMainWebviewViewProps } from '../../selectors/selectMainWebviewViewProps';
+import { buildGlobPattern, isNeitherNullNorUndefined } from '../../utilities';
+import type { EngineService } from '../engineService';
+import { type MessageBus, MessageKind } from '../messageBus';
+import { WebviewResolver } from './WebviewResolver';
 import type {
-  CodemodHash,
-  WebviewMessage,
-  WebviewResponse,
-} from "./webviewEvents";
+	CodemodHash,
+	WebviewMessage,
+	WebviewResponse,
+} from './webviewEvents';
 
 export const validateAccessToken = async (
-  accessToken: string,
+	accessToken: string,
 ): Promise<void> => {
-  try {
-    const response = await axios.post(
-      "https://backend.codemod.com/verifyToken",
-      {},
-      {
-        headers: { Authorization: `Bearer ${accessToken}` },
-        timeout: 5000,
-      },
-    );
-
-    return response.data;
-  } catch (error) {
-    if (!axios.isAxiosError(error)) {
-      console.error(error);
-    }
-  }
+	try {
+		const controller = new AbortController();
+		const timeoutId = setTimeout(() => controller.abort(), 5000);
+		const response = await fetch(
+			'https://backend.codemod.com/verifyToken',
+			{
+				method: 'POST',
+				headers: { Authorization: `Bearer ${accessToken}` },
+				signal: controller.signal,
+			},
+		);
+		clearTimeout(timeoutId);
+		if (!response.ok) throw new Error('Network response was not ok.');
+		const data = await response.json();
+
+		return response.data;
+	} catch (error) {
+		if ((!error) instanceof Error && error.name === 'AbortError') {
+			console.error(error);
+		}
+	}
 };
 
 export const createIssue = async (
-  title: string,
-  body: string,
-  accessToken: string,
-  onSuccess: () => void,
-  onFail: () => Promise<void>,
+	title: string,
+	body: string,
+	accessToken: string,
+	onSuccess: () => void,
+	onFail: () => Promise<void>,
 ): Promise<{ status: number; html_url: string | null }> => {
-  // call API to create Github Issue
-  const codemodRegistryRepoUrl = "https://github.com/codemod-com/codemod";
-
-  const result = await axios.post(
-    "https://backend.codemod.com/sourceControl/github/issues",
-    { title, body, repoUrl: codemodRegistryRepoUrl },
-    { headers: { Authorization: `Bearer ${accessToken}` } },
-  );
-  if (result.status !== 200) {
-    await onFail();
-    return { status: result.status, html_url: null };
-  }
-
-  const { data } = result;
-
-  const validation = createIssueResponseCodec.decode(data);
-
-  if (validation._tag === "Left") {
-    await onFail();
-    window.showErrorMessage("Creating Github issue failed.");
-    return { status: 406, html_url: null };
-  }
-
-  onSuccess();
-
-  const decision = await window.showInformationMessage(
-    "Github issue is successfully created.",
-    "See issue in Github",
-  );
-  const { html_url } = validation.right;
-  if (decision === "See issue in Github") {
-    commands.executeCommand("codemod.redirect", html_url);
-  }
-  return {
-    status: 200,
-    html_url,
-  };
+	// call API to create Github Issue
+	const codemodRegistryRepoUrl = 'https://github.com/codemod-com/codemod';
+
+	const result = await fetch(
+		'https://backend.codemod.com/sourceControl/github/issues',
+		{
+			method: 'POST',
+			headers: { Authorization: `Bearer ${accessToken}` },
+			body: JSON.stringify({
+				title,
+				body,
+				repoUrl: codemodRegistryRepoUrl,
+			}),
+		},
+	);
+	if (!result.ok) throw new Error('Network response was not ok.');
+	const data = await result.json();
+	if (result.status !== 200) {
+		await onFail();
+		return { status: result.status, html_url: null };
+	}
+
+	const { data } = result;
+
+	const validation = createIssueResponseCodec.decode(data);
+
+	if (validation._tag === 'Left') {
+		await onFail();
+		window.showErrorMessage('Creating Github issue failed.');
+		return { status: 406, html_url: null };
+	}
+
+	onSuccess();
+
+	const decision = await window.showInformationMessage(
+		'Github issue is successfully created.',
+		'See issue in Github',
+	);
+	const { html_url } = validation.right;
+	if (decision === 'See issue in Github') {
+		commands.executeCommand('codemod.redirect', html_url);
+	}
+	return {
+		status: 200,
+		html_url,
+	};
 };
 
 export class MainViewProvider implements WebviewViewProvider {
-  private __view: WebviewView | null = null;
-  private __webviewResolver: WebviewResolver;
-  private __executionQueue: ReadonlyArray<CodemodHash> = [];
-  private __directoryPaths: ReadonlyArray<string> | null = null;
-  // true by default to prevent banner blinking on load
-  private __codemodEngineNodeLocated = true;
-
-  constructor(
-    context: ExtensionContext,
-    private readonly __engineService: EngineService,
-    private readonly __messageBus: MessageBus,
-    private readonly __rootUri: Uri | null,
-    private readonly __store: Store,
-  ) {
-    this.__webviewResolver = new WebviewResolver(context.extensionUri);
-
-    this.__messageBus.subscribe(MessageKind.showProgress, (message) => {
-      if (message.codemodHash === null) {
-        return;
-      }
-
-      this.__postMessage({
-        kind: "webview.global.setCodemodExecutionProgress",
-        codemodHash: message.codemodHash,
-        progressKind: message.progressKind,
-        totalFileNumber: message.totalFileNumber,
-        processedFileNumber: message.processedFileNumber,
-      });
-    });
-
-    this.__messageBus.subscribe(MessageKind.codemodSetExecuted, () => {
-      this.__postMessage({
-        kind: "webview.global.codemodExecutionHalted",
-      });
-    });
-
-    this.__messageBus.subscribe(MessageKind.executeCodemodSet, () => {
-      this.__store.dispatch(actions.collapseResultsPanel(false));
-      this.__store.dispatch(actions.collapseChangeExplorerPanel(false));
-    });
-
-    this.__messageBus.subscribe(MessageKind.executionQueueChange, (message) => {
-      this.__executionQueue = message.queuedCodemodHashes;
-      const props = this.__buildProps();
-
-      this.__postMessage({
-        kind: "webview.main.setProps",
-        props: props,
-      });
-    });
-
-    this.__messageBus.subscribe(
-      MessageKind.codemodEngineNodeLocated,
-      ({ codemodEngineNodeLocated }) => {
-        if (this.__codemodEngineNodeLocated === codemodEngineNodeLocated) {
-          return;
-        }
-
-        this.__codemodEngineNodeLocated = codemodEngineNodeLocated;
-
-        const props = this.__buildProps();
-
-        this.__postMessage({
-          kind: "webview.main.setProps",
-          props,
-        });
-      },
-    );
-
-    let prevProps = this.__buildProps();
-
-    this.__store.subscribe(async () => {
-      if (this.__directoryPaths === null) {
-        await this.__getDirectoryPaths();
-      }
-
-      const nextProps = this.__buildProps();
-      if (areEqual(prevProps, nextProps)) {
-        return;
-      }
-
-      prevProps = nextProps;
-
-      this.__postMessage({
-        kind: "webview.main.setProps",
-        props: nextProps,
-      });
-    });
-  }
-
-  public isVisible(): boolean {
-    return this.__view?.visible ?? false;
-  }
-
-  public resolveWebviewView(webviewView: WebviewView): void | Thenable<void> {
-    this.__resolveWebview(webviewView);
-
-    this.__view = webviewView;
-
-    this.__view.webview.onDidReceiveMessage(this.__onDidReceiveMessage);
-
-    this.__messageBus.publish({
-      kind: MessageKind.mainWebviewViewVisibilityChange,
-    });
-
-    this.__view.onDidChangeVisibility(() => {
-      this.__messageBus.publish({
-        kind: MessageKind.mainWebviewViewVisibilityChange,
-      });
-
-      if (this.__view?.visible) {
-        this.__resolveWebview(this.__view);
-      }
-    });
-  }
-
-  private async __getDirectoryPaths() {
-    if (this.__rootUri === null) {
-      return;
-    }
-
-    const globPattern = buildGlobPattern(this.__rootUri, "/**");
-
-    // From `glob` documentation:
-    // (Note: to match only directories, put a / at the end of the pattern.)
-    const directoryPaths = await glob(`${globPattern}/`, {
-      // ignore node_modules and files, match only directories
-      ignore: ["**/node_modules/**"],
-      follow: false,
-      maxDepth: 10,
-    });
-
-    const MAX_NUMBER_OF_DIRECTORIES = 10000;
-
-    this.__directoryPaths = directoryPaths.slice(0, MAX_NUMBER_OF_DIRECTORIES);
-  }
-
-  private __postMessage(message: WebviewMessage) {
-    this.__view?.webview.postMessage(message);
-  }
-
-  private __resolveWebview(webviewView: WebviewView) {
-    this.__webviewResolver.resolveWebview(
-      webviewView.webview,
-      "main",
-      JSON.stringify(this.__buildProps()),
-      "mainWebviewViewProps",
-    );
-  }
-
-  private __buildProps() {
-    return selectMainWebviewViewProps(
-      this.__store.getState(),
-      this.__rootUri,
-      this.__directoryPaths,
-      this.__executionQueue,
-      this.__codemodEngineNodeLocated,
-    );
-  }
-
-  private __onDidReceiveMessage = async (message: WebviewResponse) => {
-    if (message.kind === "webview.command") {
-      commands.executeCommand(
-        message.value.command,
-        ...(message.value.arguments ?? []),
-      );
-    }
-
-    if (message.kind === "webview.campaignManager.setSelectedCaseHash") {
-      this.__store.dispatch(actions.setSelectedCaseHash(message.caseHash));
-    }
-
-    if (message.kind === "webview.global.discardSelected") {
-      commands.executeCommand("codemod.discardJobs", message.caseHashDigest);
-    }
-
-    if (message.kind === "webview.global.showInformationMessage") {
-      window.showInformationMessage(message.value);
-    }
-
-    if (message.kind === "webview.global.applySelected") {
-      commands.executeCommand(
-        "codemod.sourceControl.saveStagedJobsToTheFileSystem",
-        message.caseHashDigest,
-      );
-    }
-
-    if (message.kind === "webview.main.setActiveTabId") {
-      this.__store.dispatch(actions.setActiveTabId(message.activeTabId));
-    }
-
-    if (message.kind === "webview.main.setCodemodDiscoveryPanelGroupSettings") {
-      this.__store.dispatch(
-        actions.setCodemodDiscoveryPanelGroupSettings(
-          message.panelGroupSettings,
-        ),
-      );
-    }
-
-    if (message.kind === "webview.main.setCodemodRunsPanelGroupSettings") {
-      this.__store.dispatch(
-        actions.setCodemodRunsPanelGroupSettings(message.panelGroupSettings),
-      );
-    }
-
-    if (message.kind === "webview.main.setToaster") {
-      this.__store.dispatch(actions.setToaster(message.value));
-    }
-
-    if (message.kind === "webview.global.flipSelectedExplorerNode") {
-      this.__store.dispatch(
-        actions.flipSelectedExplorerNode([
-          message.caseHashDigest,
-          message.explorerNodeHashDigest,
-        ]),
-      );
-    }
-
-    if (message.kind === "webview.global.flipCollapsibleExplorerNode") {
-      this.__store.dispatch(
-        actions.flipCollapsibleExplorerNode([
-          message.caseHashDigest,
-          message.explorerNodeHashDigest,
-        ]),
-      );
-    }
-
-    if (message.kind === "webview.global.focusExplorerNode") {
-      this.__store.dispatch(
-        actions.focusExplorerNode([
-          message.caseHashDigest,
-          message.explorerNodeHashDigest,
-        ]),
-      );
-    }
-
-    if (message.kind === "webview.global.setChangeExplorerSearchPhrase") {
-      this.__store.dispatch(
-        actions.setChangeExplorerSearchPhrase([
-          message.caseHashDigest,
-          message.searchPhrase,
-        ]),
-      );
-    }
-
-    if (message.kind === "webview.codemodList.haltCodemodExecution") {
-      this.__engineService.shutdownEngines();
-    }
-
-    if (message.kind === "webview.codemodList.dryRunCodemod") {
-      if (this.__rootUri === null) {
-        window.showWarningMessage("No active workspace is found.");
-        return;
-      }
-
-      const hashDigest = message.value;
-      this.__store.dispatch(actions.setRecentCodemodHashes(hashDigest));
-
-      const state = this.__store.getState().codemodDiscoveryView;
-      const executionPath =
-        state.executionPaths[hashDigest] ?? this.__rootUri.fsPath;
-
-      if (executionPath === null) {
-        return;
-      }
-
-      const uri = Uri.file(executionPath);
-
-      // if missing some required arguments, open arguments popup
-
-      const argumentsSpecified = selectCodemodArguments(
-        this.__store.getState(),
-        hashDigest as unknown as CodemodNodeHashDigest,
-      ).every(
-        ({ required, value }) =>
-          !required || (isNeitherNullNorUndefined(value) && value !== ""),
-      );
-
-      if (!argumentsSpecified) {
-        this.__store.dispatch(
-          actions.setCodemodArgumentsPopupHashDigest(
-            hashDigest as unknown as CodemodNodeHashDigest,
-          ),
-        );
-        return;
-      }
-
-      commands.executeCommand("codemod.executeCodemod", uri, hashDigest);
-    }
-
-    if (message.kind === "webview.codemodList.updatePathToExecute") {
-      await this.updateExecutionPath(message.value);
-
-      this.__postMessage({
-        kind: "webview.main.setProps",
-        props: this.__buildProps(),
-      });
-    }
-
-    if (message.kind === "webview.global.showWarningMessage") {
-      window.showWarningMessage(message.value);
-    }
-
-    if (message.kind === "webview.global.flipCodemodHashDigest") {
-      this.__store.dispatch(
-        actions.flipCodemodHashDigest(message.codemodNodeHashDigest),
-      );
-    }
-
-    if (message.kind === "webview.global.selectCodemodNodeHashDigest") {
-      this.__store.dispatch(
-        actions.setFocusedCodemodHashDigest(
-          message.selectedCodemodNodeHashDigest,
-        ),
-      );
-    }
-
-    if (message.kind === "webview.global.setCodemodSearchPhrase") {
-      this.__store.dispatch(
-        actions.setCodemodSearchPhrase(message.searchPhrase),
-      );
-    }
-
-    if (message.kind === "webview.global.collapseResultsPanel") {
-      this.__store.dispatch(actions.collapseResultsPanel(message.collapsed));
-    }
-
-    if (message.kind === "webview.global.collapseChangeExplorerPanel") {
-      this.__store.dispatch(
-        actions.collapseChangeExplorerPanel(message.collapsed),
-      );
-    }
-
-    if (message.kind === "webview.global.setCodemodArgumentsPopupHashDigest") {
-      this.__store.dispatch(
-        actions.setCodemodArgumentsPopupHashDigest(message.hashDigest),
-      );
-    }
-
-    if (message.kind === "webview.global.setCodemodArgument") {
-      this.__store.dispatch(
-        actions.setCodemodArgument({
-          hashDigest: message.hashDigest,
-          name: message.name,
-          value: message.value,
-        }),
-      );
-    }
-  };
-
-  public updateExecutionPath = async ({
-    newPath,
-    codemodHash,
-    errorMessage,
-    warningMessage,
-    revertToPrevExecutionIfInvalid,
-    fromVSCodeCommand,
-  }: {
-    newPath: string;
-    codemodHash: CodemodHash;
-    errorMessage: string | null;
-    warningMessage: string | null;
-    revertToPrevExecutionIfInvalid: boolean;
-    fromVSCodeCommand?: boolean;
-  }) => {
-    if (this.__rootUri === null) {
-      window.showWarningMessage("No active workspace is found.");
-      return;
-    }
-
-    const state = this.__store.getState().codemodDiscoveryView;
-    const persistedExecutionPath = state.executionPaths[codemodHash];
-
-    const oldExecutionPath = persistedExecutionPath ?? null;
-    const newPathAbsolute = relativeToAbsolutePath(
-      newPath,
-      this.__rootUri.fsPath,
-    );
-
-    try {
-      await workspace.fs.stat(Uri.file(newPathAbsolute));
-      this.__store.dispatch(
-        actions.setExecutionPath({
-          codemodHash,
-          path: newPathAbsolute,
-        }),
-      );
-
-      if (!fromVSCodeCommand) {
-        window.showInformationMessage(
-          "Successfully updated the execution path.",
-        );
-      }
-    } catch (e) {
-      if (errorMessage !== null) {
-        window.showErrorMessage(errorMessage);
-      }
-      if (warningMessage !== null) {
-        window.showWarningMessage(warningMessage);
-      }
-
-      if (oldExecutionPath === null) {
-        return;
-      }
-
-      if (revertToPrevExecutionIfInvalid) {
-        this.__store.dispatch(
-          actions.setExecutionPath({
-            codemodHash,
-            path: oldExecutionPath,
-          }),
-        );
-      } else {
-        this.__store.dispatch(
-          actions.setExecutionPath({
-            codemodHash,
-            path: oldExecutionPath,
-          }),
-        );
-      }
-    }
-  };
+	private __view: WebviewView | null = null;
+	private __webviewResolver: WebviewResolver;
+	private __executionQueue: ReadonlyArray<CodemodHash> = [];
+	private __directoryPaths: ReadonlyArray<string> | null = null;
+	// true by default to prevent banner blinking on load
+	private __codemodEngineNodeLocated = true;
+
+	constructor(
+		context: ExtensionContext,
+		private readonly __engineService: EngineService,
+		private readonly __messageBus: MessageBus,
+		private readonly __rootUri: Uri | null,
+		private readonly __store: Store,
+	) {
+		this.__webviewResolver = new WebviewResolver(context.extensionUri);
+
+		this.__messageBus.subscribe(MessageKind.showProgress, (message) => {
+			if (message.codemodHash === null) {
+				return;
+			}
+
+			this.__postMessage({
+				kind: 'webview.global.setCodemodExecutionProgress',
+				codemodHash: message.codemodHash,
+				progressKind: message.progressKind,
+				totalFileNumber: message.totalFileNumber,
+				processedFileNumber: message.processedFileNumber,
+			});
+		});
+
+		this.__messageBus.subscribe(MessageKind.codemodSetExecuted, () => {
+			this.__postMessage({
+				kind: 'webview.global.codemodExecutionHalted',
+			});
+		});
+
+		this.__messageBus.subscribe(MessageKind.executeCodemodSet, () => {
+			this.__store.dispatch(actions.collapseResultsPanel(false));
+			this.__store.dispatch(actions.collapseChangeExplorerPanel(false));
+		});
+
+		this.__messageBus.subscribe(
+			MessageKind.executionQueueChange,
+			(message) => {
+				this.__executionQueue = message.queuedCodemodHashes;
+				const props = this.__buildProps();
+
+				this.__postMessage({
+					kind: 'webview.main.setProps',
+					props: props,
+				});
+			},
+		);
+
+		this.__messageBus.subscribe(
+			MessageKind.codemodEngineNodeLocated,
+			({ codemodEngineNodeLocated }) => {
+				if (
+					this.__codemodEngineNodeLocated === codemodEngineNodeLocated
+				) {
+					return;
+				}
+
+				this.__codemodEngineNodeLocated = codemodEngineNodeLocated;
+
+				const props = this.__buildProps();
+
+				this.__postMessage({
+					kind: 'webview.main.setProps',
+					props,
+				});
+			},
+		);
+
+		let prevProps = this.__buildProps();
+
+		this.__store.subscribe(async () => {
+			if (this.__directoryPaths === null) {
+				await this.__getDirectoryPaths();
+			}
+
+			const nextProps = this.__buildProps();
+			if (areEqual(prevProps, nextProps)) {
+				return;
+			}
+
+			prevProps = nextProps;
+
+			this.__postMessage({
+				kind: 'webview.main.setProps',
+				props: nextProps,
+			});
+		});
+	}
+
+	public isVisible(): boolean {
+		return this.__view?.visible ?? false;
+	}
+
+	public resolveWebviewView(webviewView: WebviewView): void | Thenable<void> {
+		this.__resolveWebview(webviewView);
+
+		this.__view = webviewView;
+
+		this.__view.webview.onDidReceiveMessage(this.__onDidReceiveMessage);
+
+		this.__messageBus.publish({
+			kind: MessageKind.mainWebviewViewVisibilityChange,
+		});
+
+		this.__view.onDidChangeVisibility(() => {
+			this.__messageBus.publish({
+				kind: MessageKind.mainWebviewViewVisibilityChange,
+			});
+
+			if (this.__view?.visible) {
+				this.__resolveWebview(this.__view);
+			}
+		});
+	}
+
+	private async __getDirectoryPaths() {
+		if (this.__rootUri === null) {
+			return;
+		}
+
+		const globPattern = buildGlobPattern(this.__rootUri, '/**');
+
+		// From `glob` documentation:
+		// (Note: to match only directories, put a / at the end of the pattern.)
+		const directoryPaths = await glob(`${globPattern}/`, {
+			// ignore node_modules and files, match only directories
+			ignore: ['**/node_modules/**'],
+			follow: false,
+			maxDepth: 10,
+		});
+
+		const MAX_NUMBER_OF_DIRECTORIES = 10000;
+
+		this.__directoryPaths = directoryPaths.slice(
+			0,
+			MAX_NUMBER_OF_DIRECTORIES,
+		);
+	}
+
+	private __postMessage(message: WebviewMessage) {
+		this.__view?.webview.postMessage(message);
+	}
+
+	private __resolveWebview(webviewView: WebviewView) {
+		this.__webviewResolver.resolveWebview(
+			webviewView.webview,
+			'main',
+			JSON.stringify(this.__buildProps()),
+			'mainWebviewViewProps',
+		);
+	}
+
+	private __buildProps() {
+		return selectMainWebviewViewProps(
+			this.__store.getState(),
+			this.__rootUri,
+			this.__directoryPaths,
+			this.__executionQueue,
+			this.__codemodEngineNodeLocated,
+		);
+	}
+
+	private __onDidReceiveMessage = async (message: WebviewResponse) => {
+		if (message.kind === 'webview.command') {
+			commands.executeCommand(
+				message.value.command,
+				...(message.value.arguments ?? []),
+			);
+		}
+
+		if (message.kind === 'webview.campaignManager.setSelectedCaseHash') {
+			this.__store.dispatch(
+				actions.setSelectedCaseHash(message.caseHash),
+			);
+		}
+
+		if (message.kind === 'webview.global.discardSelected') {
+			commands.executeCommand(
+				'codemod.discardJobs',
+				message.caseHashDigest,
+			);
+		}
+
+		if (message.kind === 'webview.global.showInformationMessage') {
+			window.showInformationMessage(message.value);
+		}
+
+		if (message.kind === 'webview.global.applySelected') {
+			commands.executeCommand(
+				'codemod.sourceControl.saveStagedJobsToTheFileSystem',
+				message.caseHashDigest,
+			);
+		}
+
+		if (message.kind === 'webview.main.setActiveTabId') {
+			this.__store.dispatch(actions.setActiveTabId(message.activeTabId));
+		}
+
+		if (
+			message.kind ===
+			'webview.main.setCodemodDiscoveryPanelGroupSettings'
+		) {
+			this.__store.dispatch(
+				actions.setCodemodDiscoveryPanelGroupSettings(
+					message.panelGroupSettings,
+				),
+			);
+		}
+
+		if (message.kind === 'webview.main.setCodemodRunsPanelGroupSettings') {
+			this.__store.dispatch(
+				actions.setCodemodRunsPanelGroupSettings(
+					message.panelGroupSettings,
+				),
+			);
+		}
+
+		if (message.kind === 'webview.main.setToaster') {
+			this.__store.dispatch(actions.setToaster(message.value));
+		}
+
+		if (message.kind === 'webview.global.flipSelectedExplorerNode') {
+			this.__store.dispatch(
+				actions.flipSelectedExplorerNode([
+					message.caseHashDigest,
+					message.explorerNodeHashDigest,
+				]),
+			);
+		}
+
+		if (message.kind === 'webview.global.flipCollapsibleExplorerNode') {
+			this.__store.dispatch(
+				actions.flipCollapsibleExplorerNode([
+					message.caseHashDigest,
+					message.explorerNodeHashDigest,
+				]),
+			);
+		}
+
+		if (message.kind === 'webview.global.focusExplorerNode') {
+			this.__store.dispatch(
+				actions.focusExplorerNode([
+					message.caseHashDigest,
+					message.explorerNodeHashDigest,
+				]),
+			);
+		}
+
+		if (message.kind === 'webview.global.setChangeExplorerSearchPhrase') {
+			this.__store.dispatch(
+				actions.setChangeExplorerSearchPhrase([
+					message.caseHashDigest,
+					message.searchPhrase,
+				]),
+			);
+		}
+
+		if (message.kind === 'webview.codemodList.haltCodemodExecution') {
+			this.__engineService.shutdownEngines();
+		}
+
+		if (message.kind === 'webview.codemodList.dryRunCodemod') {
+			if (this.__rootUri === null) {
+				window.showWarningMessage('No active workspace is found.');
+				return;
+			}
+
+			const hashDigest = message.value;
+			this.__store.dispatch(actions.setRecentCodemodHashes(hashDigest));
+
+			const state = this.__store.getState().codemodDiscoveryView;
+			const executionPath =
+				state.executionPaths[hashDigest] ?? this.__rootUri.fsPath;
+
+			if (executionPath === null) {
+				return;
+			}
+
+			const uri = Uri.file(executionPath);
+
+			// if missing some required arguments, open arguments popup
+
+			const argumentsSpecified = selectCodemodArguments(
+				this.__store.getState(),
+				hashDigest as unknown as CodemodNodeHashDigest,
+			).every(
+				({ required, value }) =>
+					!required ||
+					(isNeitherNullNorUndefined(value) && value !== ''),
+			);
+
+			if (!argumentsSpecified) {
+				this.__store.dispatch(
+					actions.setCodemodArgumentsPopupHashDigest(
+						hashDigest as unknown as CodemodNodeHashDigest,
+					),
+				);
+				return;
+			}
+
+			commands.executeCommand('codemod.executeCodemod', uri, hashDigest);
+		}
+
+		if (message.kind === 'webview.codemodList.updatePathToExecute') {
+			await this.updateExecutionPath(message.value);
+
+			this.__postMessage({
+				kind: 'webview.main.setProps',
+				props: this.__buildProps(),
+			});
+		}
+
+		if (message.kind === 'webview.global.showWarningMessage') {
+			window.showWarningMessage(message.value);
+		}
+
+		if (message.kind === 'webview.global.flipCodemodHashDigest') {
+			this.__store.dispatch(
+				actions.flipCodemodHashDigest(message.codemodNodeHashDigest),
+			);
+		}
+
+		if (message.kind === 'webview.global.selectCodemodNodeHashDigest') {
+			this.__store.dispatch(
+				actions.setFocusedCodemodHashDigest(
+					message.selectedCodemodNodeHashDigest,
+				),
+			);
+		}
+
+		if (message.kind === 'webview.global.setCodemodSearchPhrase') {
+			this.__store.dispatch(
+				actions.setCodemodSearchPhrase(message.searchPhrase),
+			);
+		}
+
+		if (message.kind === 'webview.global.collapseResultsPanel') {
+			this.__store.dispatch(
+				actions.collapseResultsPanel(message.collapsed),
+			);
+		}
+
+		if (message.kind === 'webview.global.collapseChangeExplorerPanel') {
+			this.__store.dispatch(
+				actions.collapseChangeExplorerPanel(message.collapsed),
+			);
+		}
+
+		if (
+			message.kind === 'webview.global.setCodemodArgumentsPopupHashDigest'
+		) {
+			this.__store.dispatch(
+				actions.setCodemodArgumentsPopupHashDigest(message.hashDigest),
+			);
+		}
+
+		if (message.kind === 'webview.global.setCodemodArgument') {
+			this.__store.dispatch(
+				actions.setCodemodArgument({
+					hashDigest: message.hashDigest,
+					name: message.name,
+					value: message.value,
+				}),
+			);
+		}
+	};
+
+	public updateExecutionPath = async ({
+		newPath,
+		codemodHash,
+		errorMessage,
+		warningMessage,
+		revertToPrevExecutionIfInvalid,
+		fromVSCodeCommand,
+	}: {
+		newPath: string;
+		codemodHash: CodemodHash;
+		errorMessage: string | null;
+		warningMessage: string | null;
+		revertToPrevExecutionIfInvalid: boolean;
+		fromVSCodeCommand?: boolean;
+	}) => {
+		if (this.__rootUri === null) {
+			window.showWarningMessage('No active workspace is found.');
+			return;
+		}
+
+		const state = this.__store.getState().codemodDiscoveryView;
+		const persistedExecutionPath = state.executionPaths[codemodHash];
+
+		const oldExecutionPath = persistedExecutionPath ?? null;
+		const newPathAbsolute = relativeToAbsolutePath(
+			newPath,
+			this.__rootUri.fsPath,
+		);
+
+		try {
+			await workspace.fs.stat(Uri.file(newPathAbsolute));
+			this.__store.dispatch(
+				actions.setExecutionPath({
+					codemodHash,
+					path: newPathAbsolute,
+				}),
+			);
+
+			if (!fromVSCodeCommand) {
+				window.showInformationMessage(
+					'Successfully updated the execution path.',
+				);
+			}
+		} catch (e) {
+			if (errorMessage !== null) {
+				window.showErrorMessage(errorMessage);
+			}
+			if (warningMessage !== null) {
+				window.showWarningMessage(warningMessage);
+			}
+
+			if (oldExecutionPath === null) {
+				return;
+			}
+
+			if (revertToPrevExecutionIfInvalid) {
+				this.__store.dispatch(
+					actions.setExecutionPath({
+						codemodHash,
+						path: oldExecutionPath,
+					}),
+				);
+			} else {
+				this.__store.dispatch(
+					actions.setExecutionPath({
+						codemodHash,
+						path: oldExecutionPath,
+					}),
+				);
+			}
+		}
+	};
 }
diff --git a/packages/codemods/axios/fetch/__testfixtures__/fixture1.input.ts b/packages/codemods/axios/fetch/__testfixtures__/fixture1.input.ts
index e6ef09e31..c0ef47581 100644
--- a/packages/codemods/axios/fetch/__testfixtures__/fixture1.input.ts
+++ b/packages/codemods/axios/fetch/__testfixtures__/fixture1.input.ts
@@ -1,3 +1,3 @@
-const { data } = await axios.get(url, {
-    responseType: "arraybuffer",
-});
\ No newline at end of file
+const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
+if (!response.ok) throw new Error('Network response was not ok');
+const data = await response.arrayBuffer();
diff --git a/packages/codemods/axios/fetch/__testfixtures__/fixture2.input.ts b/packages/codemods/axios/fetch/__testfixtures__/fixture2.input.ts
index 0da333077..7b6280635 100644
--- a/packages/codemods/axios/fetch/__testfixtures__/fixture2.input.ts
+++ b/packages/codemods/axios/fetch/__testfixtures__/fixture2.input.ts
@@ -1,15 +1,20 @@
-const { data } = await axios.post(
-    `https://app.posthog.com/api/projects/${this.__projectId}/query/`,
-    {
-        query: {
-        kind: "HogQLQuery",
-        query:
-            "select properties.codemodName, count(*) from events where event in ('codemod.CLI.codemodExecuted', 'codemod.VSCE.codemodExecuted') group by properties.codemodName",
-        },
-    },
-    {
-        headers: {
-        Authorization: this.__authHeader,
-        },
-    },
-);
\ No newline at end of file
+const response = await fetch(
+	`https://app.posthog.com/api/projects/${this.__projectId}/query/`,
+	{
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: this.__authHeader,
+		},
+		body: JSON.stringify({
+			query: {
+				kind: 'HogQLQuery',
+				query: "select properties.codemodName, count(*) from events where event in ('codemod.CLI.codemodExecuted', 'codemod.VSCE.codemodExecuted') group by properties.codemodName",
+			},
+		}),
+	},
+);
+if (!response.ok) {
+	throw new Error('Network response was not ok');
+}
+const { data } = await response.json();
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 06193d25c..334c8a7f9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3584,6 +3584,30 @@ importers:
         specifier: ^1.0.1
         version: 1.5.1(@types/node@20.12.7)(jsdom@23.2.0)(terser@5.30.4)
 
+  packages/codemods/nuxt/4/default-data-error-value:
+    devDependencies:
+      '@codemod-com/utilities':
+        specifier: workspace:*
+        version: link:../../../../utilities
+      '@types/jscodeshift':
+        specifier: ^0.11.10
+        version: 0.11.11
+      '@vitest/coverage-v8':
+        specifier: ^1.0.1
+        version: 1.5.1(vitest@1.5.1(@types/node@20.12.7)(jsdom@23.2.0)(terser@5.30.4))
+      jscodeshift:
+        specifier: ^0.15.1
+        version: 0.15.2(@babel/preset-env@7.24.4)
+      ts-node:
+        specifier: ^10.9.1
+        version: 10.9.2(@types/node@20.12.7)(typescript@5.4.5)
+      typescript:
+        specifier: ^5.2.2
+        version: 5.4.5
+      vitest:
+        specifier: ^1.0.1
+        version: 1.5.1(@types/node@20.12.7)(jsdom@23.2.0)(terser@5.30.4)
+
   packages/codemods/nuxt/4/shallow-data-reactivity:
     devDependencies:
       '@codemod-com/utilities':
@@ -4935,8 +4959,6 @@ importers:
         specifier: ^5.4.5
         version: 5.4.5
 
-  packages/database/generated/client: {}
-
   packages/filemod:
     devDependencies:
       '@types/node':
@@ -25920,7 +25942,7 @@ snapshots:
       eslint: 8.56.0
       eslint-import-resolver-node: 0.3.9
       eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0)
-      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
       eslint-plugin-jsx-a11y: 6.8.0(eslint@8.56.0)
       eslint-plugin-react: 7.34.1(eslint@8.56.0)
       eslint-plugin-react-hooks: 4.6.0(eslint@8.56.0)
@@ -25948,7 +25970,7 @@ snapshots:
       enhanced-resolve: 5.16.0
       eslint: 8.56.0
       eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
-      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
       fast-glob: 3.3.2
       get-tsconfig: 4.7.3
       is-core-module: 2.13.1
@@ -25970,7 +25992,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0):
+  eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0):
     dependencies:
       array-includes: 3.1.8
       array.prototype.findlastindex: 1.2.5

From 81ee30e3a423955fa71af0aa75c1368424a511c5 Mon Sep 17 00:00:00 2001
From: Aleksy Rybicki <alekso.php@gmail.com>
Date: Thu, 27 Jun 2024 11:42:26 +0100
Subject: [PATCH 2/4] Manual changes for axios migration

---
 apps/auth-service/package.json                |    1 -
 apps/backend/package.json                     |    1 -
 apps/backend/src/plugins/authPlugin.ts        |  230 ++--
 apps/backend/src/publishHandler.test.ts       |   40 +-
 apps/backend/src/publishHandler.ts            |   31 +-
 apps/backend/src/services/GithubProvider.ts   |  326 +++--
 apps/backend/src/services/PostHogService.ts   |   94 +-
 apps/backend/src/services/SourceControl.ts    |   34 +-
 apps/cli/package.json                         |    5 +-
 apps/cli/src/apis.ts                          |  143 ++-
 apps/cli/src/buildCodemodOptions.ts           |    6 +-
 apps/cli/src/commands/publish.ts              |    7 +-
 apps/cli/src/commands/run.ts                  |    6 +-
 apps/cli/src/commands/unpublish.ts            |    6 +-
 apps/cli/src/downloadCodemod.ts               |   10 +-
 apps/cli/src/executeMainThread.ts             |   23 +-
 apps/cli/src/fileDownloadService.ts           |   21 +-
 .../(website)/studio/src/api/getCodeDiff.ts   |   10 +-
 .../studio/src/api/populateLoginIntent.ts     |   11 +-
 .../(website)/studio/src/api/sendMessage.ts   |   31 +-
 .../app/(website)/studio/src/hooks/useAPI.ts  |   41 +-
 apps/frontend/package.json                    |    1 -
 apps/frontend/utils/apis/client.ts            |   53 +-
 apps/modgpt/package.json                      |    1 -
 apps/modgpt/src/plugins/authPlugin.ts         |   17 +-
 apps/shared/mocks/gh-run.ts                   |   20 +-
 apps/task-manager/package.json                |    4 +-
 apps/task-manager/src/services/Auth.ts        |   77 +-
 .../src/services/GithubProvider.ts            |   25 +-
 apps/task-manager/src/util.ts                 |   64 +-
 apps/vsce/package.json                        |    4 +-
 apps/vsce/src/axios/index.ts                  |    8 -
 apps/vsce/src/components/downloadService.ts   |   29 +-
 apps/vsce/src/components/engineService.ts     |   13 +-
 .../src/components/webview/MainProvider.ts    | 1059 ++++++++---------
 apps/vsce/src/fetch/index.ts                  |   23 +
 apps/vsce/test/dowloadService.test.ts         |   31 +-
 apps/vsce/tsconfig.json                       |    2 +-
 packages/codemods/axios/fetch/src/index.ts    |    4 +
 packages/runner/package.json                  |    4 +-
 packages/utilities/src/fetch.ts               |   45 +
 packages/utilities/src/index.ts               |    1 +
 packages/utilities/tsconfig.json              |    1 +
 packages/workflow/package.json                |    4 +-
 pnpm-lock.yaml                                |  243 +++-
 45 files changed, 1483 insertions(+), 1327 deletions(-)
 delete mode 100644 apps/vsce/src/axios/index.ts
 create mode 100644 apps/vsce/src/fetch/index.ts
 create mode 100644 packages/utilities/src/fetch.ts

diff --git a/apps/auth-service/package.json b/apps/auth-service/package.json
index ebea35b33..a581c0eb7 100644
--- a/apps/auth-service/package.json
+++ b/apps/auth-service/package.json
@@ -28,7 +28,6 @@
     "@fastify/busboy": "^2.1.1",
     "@fastify/cors": "8.5.0",
     "@fastify/rate-limit": "9.0.1",
-    "axios": "^1.6.8",
     "dotenv": "^16.4.5",
     "fastify": "4.25.1",
     "valibot": "^0.24.1"
diff --git a/apps/backend/package.json b/apps/backend/package.json
index fc78549e7..867de75ce 100644
--- a/apps/backend/package.json
+++ b/apps/backend/package.json
@@ -43,7 +43,6 @@
     "@fastify/rate-limit": "9.0.1",
     "@types/tar": "^6.1.11",
     "ai": "2.2.29",
-    "axios": "^1.6.8",
     "bullmq": "^5.7.5",
     "chatgpt": "5.2.5",
     "chromadb": "1.7.2",
diff --git a/apps/backend/src/plugins/authPlugin.ts b/apps/backend/src/plugins/authPlugin.ts
index 9d0fe3a83..8b3201d37 100644
--- a/apps/backend/src/plugins/authPlugin.ts
+++ b/apps/backend/src/plugins/authPlugin.ts
@@ -1,124 +1,132 @@
-import type { OrganizationMembership, User } from '@codemod-com/utilities';
-import axios from 'axios';
-import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
-import fp from 'fastify-plugin';
-import { environment } from '../util';
+import {
+  type OrganizationMembership,
+  type User,
+  extendedFetch,
+} from "@codemod-com/utilities";
+import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
+import fp from "fastify-plugin";
+import { environment } from "../util";
 
 export interface UserDataPopulatedRequest extends FastifyRequest {
-	user?: User;
-	organizations?: OrganizationMembership[];
-	allowedNamespaces?: string[];
+  user?: User;
+  organizations?: OrganizationMembership[];
+  allowedNamespaces?: string[];
 }
 
 export interface OAuthTokenPopulatedRequest extends FastifyRequest {
-	token?: string;
+  token?: string;
 }
 
-declare module 'fastify' {
-	interface FastifyInstance {
-		authenticate: (
-			request: FastifyRequest,
-			reply: FastifyReply,
-		) => Promise<void>;
-		getUserData: (
-			request: FastifyRequest & UserDataPopulatedRequest,
-			reply: FastifyReply,
-		) => Promise<void>;
-		getOAuthToken: (
-			request: FastifyRequest & OAuthTokenPopulatedRequest,
-			reply: FastifyReply,
-		) => Promise<void>;
-	}
+declare module "fastify" {
+  interface FastifyInstance {
+    authenticate: (
+      request: FastifyRequest,
+      reply: FastifyReply,
+    ) => Promise<void>;
+    getUserData: (
+      request: FastifyRequest & UserDataPopulatedRequest,
+      reply: FastifyReply,
+    ) => Promise<void>;
+    getOAuthToken: (
+      request: FastifyRequest & OAuthTokenPopulatedRequest,
+      reply: FastifyReply,
+    ) => Promise<void>;
+  }
 }
 
 async function authPlugin(fastify: FastifyInstance, _opts: unknown) {
-	fastify.decorate(
-		'authenticate',
-		async (request: FastifyRequest, reply: FastifyReply) => {
-			try {
-				const authHeader = request.headers.authorization;
-
-				if (!authHeader)
-					reply.code(401).send({ error: 'Unauthorized' });
-
-				await fetch(`${environment.AUTH_SERVICE_URL}/verifyToken`, {
-					headers: { Authorization: authHeader },
-				});
-			} catch (error) {
-				console.error(error);
-				reply.code(401).send({ error: 'Unauthorized' });
-			}
-		},
-	);
-
-	fastify.decorate(
-		'getUserData',
-		async (
-			request: FastifyRequest & {
-				user?: User;
-				organizations?: OrganizationMembership[];
-				allowedNamespaces?: string[];
-			},
-			reply: FastifyReply,
-		) => {
-			try {
-				const authHeader = request.headers.authorization;
-
-				if (!authHeader) {
-					request.user = undefined;
-					request.organizations = undefined;
-					request.allowedNamespaces = undefined;
-					return;
-				}
-
-				const response = await fetch(
-					`${environment.AUTH_SERVICE_URL}/userData`,
-					{ headers: { Authorization: authHeader } },
-				);
-				if (!response.ok) throw new Error('Failed to fetch userData');
-				const { data } = { data: await response.json() };
-
-				const { user, organizations, allowedNamespaces } = data;
-
-				request.user = user;
-				request.organizations = organizations;
-				request.allowedNamespaces = allowedNamespaces;
-			} catch (error) {
-				console.error(error);
-				reply.code(401).send({ error: 'Unauthorized' });
-			}
-		},
-	);
-
-	fastify.decorate(
-		'getOAuthToken',
-		async (
-			request: FastifyRequest & {
-				token?: string;
-			},
-			reply: FastifyReply,
-		) => {
-			try {
-				const authHeader = request.headers.authorization;
-
-				if (!authHeader)
-					reply.code(401).send({ error: 'Unauthorized' });
-
-				const response = await fetch(
-					`${environment.AUTH_SERVICE_URL}/oAuthToken`,
-					{ headers: { Authorization: authHeader } },
-				);
-				if (!response.ok) throw new Error('Failed to fetch oAuthToken');
-				const { data } = { data: await response.json() };
-
-				const { token } = data;
-
-				request.token = token;
-			} catch {
-				reply.code(401).send({ error: 'Unauthorized' });
-			}
-		},
-	);
+  fastify.decorate(
+    "authenticate",
+    async (request: FastifyRequest, reply: FastifyReply) => {
+      try {
+        const authHeader = request.headers.authorization;
+
+        if (!authHeader) {
+          reply.code(401).send({ error: "Unauthorized" });
+          return;
+        }
+
+        await extendedFetch(`${environment.AUTH_SERVICE_URL}/verifyToken`, {
+          headers: { Authorization: authHeader },
+        });
+      } catch (error) {
+        console.error(error);
+        reply.code(401).send({ error: "Unauthorized" });
+      }
+    },
+  );
+
+  fastify.decorate(
+    "getUserData",
+    async (
+      request: FastifyRequest & {
+        user?: User;
+        organizations?: OrganizationMembership[];
+        allowedNamespaces?: string[];
+      },
+      reply: FastifyReply,
+    ) => {
+      try {
+        const authHeader = request.headers.authorization;
+
+        if (!authHeader) {
+          request.user = undefined;
+          request.organizations = undefined;
+          request.allowedNamespaces = undefined;
+          return;
+        }
+
+        const response = await extendedFetch(
+          `${environment.AUTH_SERVICE_URL}/userData`,
+          { headers: { Authorization: authHeader } },
+        );
+
+        const { user, organizations, allowedNamespaces } =
+          (await response.json()) as {
+            user?: User;
+            organizations?: OrganizationMembership[];
+            allowedNamespaces?: string[];
+          };
+
+        request.user = user;
+        request.organizations = organizations;
+        request.allowedNamespaces = allowedNamespaces;
+      } catch (error) {
+        console.error(error);
+        reply.code(401).send({ error: "Unauthorized" });
+      }
+    },
+  );
+
+  fastify.decorate(
+    "getOAuthToken",
+    async (
+      request: FastifyRequest & {
+        token?: string;
+      },
+      reply: FastifyReply,
+    ) => {
+      try {
+        const authHeader = request.headers.authorization;
+
+        if (!authHeader) {
+          reply.code(401).send({ error: "Unauthorized" });
+          return;
+        }
+
+        const response = await extendedFetch(
+          `${environment.AUTH_SERVICE_URL}/oAuthToken`,
+          { headers: { Authorization: authHeader } },
+        );
+
+        const { token } = (await response.json()) as { token?: string };
+
+        request.token = token;
+      } catch {
+        reply.code(401).send({ error: "Unauthorized" });
+      }
+    },
+  );
 }
 
 export default fp(authPlugin);
diff --git a/apps/backend/src/publishHandler.test.ts b/apps/backend/src/publishHandler.test.ts
index 054f02807..1dbff7730 100644
--- a/apps/backend/src/publishHandler.test.ts
+++ b/apps/backend/src/publishHandler.test.ts
@@ -20,6 +20,8 @@ const GET_USER_RETURN = {
 
 const MOCK_TIMESTAMP = "timestamp";
 
+const originalFetch = global.fetch;
+
 const mocks = vi.hoisted(() => {
   const S3Client = vi.fn();
   S3Client.prototype.send = vi.fn();
@@ -44,12 +46,16 @@ const mocks = vi.hoisted(() => {
         findUnique: vi.fn(),
       },
     },
-    axios: {
-      get: vi.fn().mockImplementation((url: string, ...args: unknown[]) => ({
-        data: GET_USER_RETURN,
-      })),
-      post: vi.fn().mockImplementation(() => ({})),
-    },
+    fetch: vi.fn().mockImplementation((url, options) => {
+      if (options.method === "GET") {
+        return Promise.resolve({
+          json: () => Promise.resolve(GET_USER_RETURN),
+          ok: true,
+        });
+      }
+
+      return Promise.resolve({ json: () => GET_USER_RETURN, ok: true });
+    }),
     S3Client,
     TarService,
     PutObjectCommand,
@@ -62,10 +68,6 @@ vi.mock("@codemod-com/database", async () => {
   return { ...actual, prisma: mocks.prisma };
 });
 
-vi.mock("axios", async () => {
-  return { default: mocks.axios };
-});
-
 vi.mock("@aws-sdk/client-s3", async () => {
   const actual = await vi.importActual("@aws-sdk/client-s3");
 
@@ -116,7 +118,7 @@ vi.mock("@codemod-com/utilities", async () => {
   };
 });
 
-vi.stubGlobal("fetch", vi.fn());
+vi.stubGlobal("fetch", mocks.fetch);
 
 describe("/publish route", async () => {
   const fastify = await runServer();
@@ -218,8 +220,8 @@ describe("/publish route", async () => {
       requestTimeout: 5000,
     });
 
-    expect(mocks.axios.post).toHaveBeenCalledOnce();
-    expect(mocks.axios.post).toHaveBeenCalledWith(
+    expect(mocks.fetch).toHaveBeenCalledOnce();
+    expect(mocks.fetch).toHaveBeenCalledWith(
       "https://hooks.zapier.com/hooks/catch/18983913/2ybuovt/",
       {
         codemod: {
@@ -605,8 +607,8 @@ describe("/publish route", async () => {
   describe("when publishing via org", async () => {
     it("should go through happy path if user has access to the org", async () => {
       mocks.prisma.codemodVersion.findFirst.mockImplementation(() => null);
-      mocks.axios.get.mockImplementation(() => ({
-        data: { ...GET_USER_RETURN, allowedNamespaces: ["org"] },
+      mocks.fetch.mockImplementation(() => ({
+        json: () => ({ ...GET_USER_RETURN, allowedNamespaces: ["org"] }),
       }));
       mocks.prisma.codemod.upsert.mockImplementation(() => {
         return { createdAt: { getTime: () => MOCK_TIMESTAMP }, id: "id" };
@@ -669,8 +671,12 @@ describe("/publish route", async () => {
 
     it("should fail if user has no access to the org", async () => {
       mocks.prisma.codemodVersion.findFirst.mockImplementation(() => null);
-      mocks.axios.get.mockImplementation(() => ({
-        data: { ...GET_USER_RETURN, organizations: [], allowedNamespaces: [] },
+      mocks.fetch.mockImplementation(() => ({
+        json: () => ({
+          ...GET_USER_RETURN,
+          organizations: [],
+          allowedNamespaces: [],
+        }),
       }));
 
       const codemodRcContents: CodemodConfigInput = {
diff --git a/apps/backend/src/publishHandler.ts b/apps/backend/src/publishHandler.ts
index 1982b81c5..ac7b3224d 100644
--- a/apps/backend/src/publishHandler.ts
+++ b/apps/backend/src/publishHandler.ts
@@ -7,10 +7,10 @@ import {
   TarService,
   buildCodemodSlug,
   codemodNameRegex,
+  extendedFetch,
   isNeitherNullNorUndefined,
   parseCodemodConfig,
 } from "@codemod-com/utilities";
-import axios from "axios";
 import type { RouteHandler } from "fastify";
 import * as semver from "semver";
 import type { UserDataPopulatedRequest } from "./plugins/authPlugin";
@@ -350,7 +350,34 @@ export const publishHandler: RouteHandler<{
 
     if (latestVersion === null) {
       try {
-        await const controller = new AbortController();const signal = controller.signal;setTimeout(() => controller.abort(), 5000);try {  const response = await fetch(    "https://hooks.zapier.com/hooks/catch/18983913/2ybuovt/",    {      method: 'POST',      body: JSON.stringify({        codemod: {          name,          from: codemodRc.applicability?.from?.map((tuple) => tuple.join(" ")),          to: codemodRc.applicability?.to?.map((tuple) => tuple.join(" ")),          engine: codemodRc.engine,          publishedAt: createdAtTimestamp,        },        author: {          username,          name: `${firstName ?? ""} ${lastName ?? ""}`.trim() || null,          email: emailAddresses.find((e) => e.id === primaryEmailAddressId)?.emailAddress ?? null,        },      }),      headers: {        'Content-Type': 'application/json'      },      signal: signal    }  );  if (!response.ok) {    throw new Error('Network response was not ok');  }  const result = { data: await response.json() };} catch (err) {  console.error("Failed calling Zapier hook:", err);};
+        await extendedFetch(
+          "https://hooks.zapier.com/hooks/catch/18983913/2ybuovt/",
+          {
+            method: "POST",
+            body: JSON.stringify({
+              codemod: {
+                name,
+                from: codemodRc.applicability?.from?.map((tuple) =>
+                  tuple.join(" "),
+                ),
+                to: codemodRc.applicability?.to?.map((tuple) =>
+                  tuple.join(" "),
+                ),
+                engine: codemodRc.engine,
+                publishedAt: createdAtTimestamp,
+              },
+              author: {
+                username,
+                name: `${firstName ?? ""} ${lastName ?? ""}`.trim() || null,
+                email:
+                  emailAddresses.find((e) => e.id === primaryEmailAddressId)
+                    ?.emailAddress ?? null,
+              },
+            }),
+            headers: { "Content-Type": "application/json" },
+            signal: AbortSignal.timeout(5000),
+          },
+        );
       } catch (err) {
         console.error("Failed calling Zapier hook:", err);
       }
diff --git a/apps/backend/src/services/GithubProvider.ts b/apps/backend/src/services/GithubProvider.ts
index c886c389c..b33e0ed7d 100644
--- a/apps/backend/src/services/GithubProvider.ts
+++ b/apps/backend/src/services/GithubProvider.ts
@@ -1,195 +1,169 @@
-import axios, { type AxiosResponse } from 'axios';
-import gh from 'parse-github-url';
+import { extendedFetch } from "@codemod-com/utilities";
+import gh from "parse-github-url";
 import type {
-	Assignee,
-	CreatePRParams,
-	GHBranch,
-	GithubContent,
-	GithubRepository,
-	Issue,
-	ListPRParams,
-	NewIssueParams,
-	PullRequest,
-	SourceControlProvider,
-} from './SourceControl.js';
+  Assignee,
+  CreatePRParams,
+  GithubContent,
+  GithubRepository,
+  Issue,
+  ListPRParams,
+  NewIssueParams,
+  PullRequest,
+  SourceControlProvider,
+} from "./SourceControl.js";
 
 type Repository = {
-	owner: string;
-	name: string;
+  owner: string;
+  name: string;
 };
 
 class InvalidGithubUrlError extends Error {}
 class ParseGithubUrlError extends Error {}
 
 function parseGithubRepoUrl(url: string): Repository {
-	try {
-		const { owner, name } = gh(url) ?? {};
-
-		if (!owner || !name) {
-			throw new InvalidGithubUrlError('Missing owner or name');
-		}
-
-		return { owner, name };
-	} catch (e) {
-		if (e instanceof InvalidGithubUrlError) {
-			throw e;
-		}
-
-		const errorMessage = e instanceof Error ? e.message : String(e);
-		throw new ParseGithubUrlError(errorMessage);
-	}
+  try {
+    const { owner, name } = gh(url) ?? {};
+
+    if (!owner || !name) {
+      throw new InvalidGithubUrlError("Missing owner or name");
+    }
+
+    return { owner, name };
+  } catch (e) {
+    if (e instanceof InvalidGithubUrlError) {
+      throw e;
+    }
+
+    const errorMessage = e instanceof Error ? e.message : String(e);
+    throw new ParseGithubUrlError(errorMessage);
+  }
 }
 
 const withPagination = async (
-	paginatedRequest: (page: string) => Promise<AxiosResponse<any[]>>,
+  paginatedRequest: (page: string) => Promise<Response>,
 ) => {
-	const nextPattern = /(?<=<)([\S]*)(?=>; rel="Next")/i;
-	let nextPage: string | null = '1';
-	let data: any[] = [];
-
-	while (nextPage !== null) {
-		const response = await paginatedRequest(nextPage);
-		data = [...data, ...(response.data ?? [])];
-
-		const linkHeader = response.headers.link;
-
-		if (
-			typeof linkHeader === 'string' &&
-			linkHeader.includes(`rel=\"next\"`)
-		) {
-			const nextUrl = linkHeader.match(nextPattern)?.[0];
-			nextPage = nextUrl
-				? new URL(nextUrl).searchParams.get('page')
-				: null;
-		} else {
-			nextPage = null;
-		}
-	}
-
-	return data;
+  const nextPattern = /(?<=<)([\S]*)(?=>; rel="Next")/i;
+  let nextPage: string | null = "1";
+  let data: any[] = [];
+
+  while (nextPage !== null) {
+    const response = await paginatedRequest(nextPage);
+    data = [...data, ...(((await response.json()) as any[]) ?? [])];
+
+    const linkHeader = response.headers.get("link");
+
+    if (typeof linkHeader === "string" && linkHeader.includes(`rel=\"next\"`)) {
+      const nextUrl = linkHeader.match(nextPattern)?.[0];
+      nextPage = nextUrl ? new URL(nextUrl).searchParams.get("page") : null;
+    } else {
+      nextPage = null;
+    }
+  }
+
+  return data;
 };
 
 const PER_PAGE = 99;
 
 export class GithubProvider implements SourceControlProvider {
-	private readonly __repo: string | null = null;
-	private readonly __baseUrl: string;
-	private readonly __authHeader: string;
-
-	constructor(oAuthToken: string, repoUrl: string | null) {
-		this.__baseUrl = 'https://api.github.com';
-		this.__repo = repoUrl;
-		this.__authHeader = `Bearer ${oAuthToken}`;
-	}
-
-	private get __repoUrl() {
-		const { owner, name } = parseGithubRepoUrl(this.__repo ?? '');
-
-		return `${this.__baseUrl}/repos/${owner}/${name}`;
-	}
-
-	async createIssue(params: NewIssueParams): Promise<Issue> {
-		const response = await fetch(`${this.__repoUrl}/issues`, {
-			method: 'POST',
-			headers: {
-				Authorization: this.__authHeader,
-				'Content-Type': 'application/json',
-			},
-			body: JSON.stringify(params),
-		});
-		if (!response.ok) throw new Error('Network response was not ok');
-		const res = { data: (await response.json()) as Issue };
-
-		return res.data;
-	}
-
-	async createPullRequest(params: CreatePRParams): Promise<PullRequest> {
-		const response = await fetch(`${this.__repoUrl}/pulls`, {
-			method: 'POST',
-			headers: {
-				Authorization: this.__authHeader,
-				'Content-Type': 'application/json',
-			},
-			body: JSON.stringify(params),
-		});
-		if (!response.ok) throw new Error('Network response was not ok');
-		const res = { data: (await response.json()) as PullRequest };
-
-		return res.data;
-	}
-
-	async getPullRequests(params: ListPRParams): Promise<PullRequest[]> {
-		const queryParams = Object.entries(params).reduce<
-			Record<string, string>
-		>((acc, [key, value]) => {
-			if (value) {
-				acc[key] = value;
-			}
-
-			return acc;
-		}, {});
-
-		const query = new URLSearchParams(queryParams).toString();
-
-		const response = await fetch(`${this.__repoUrl}/pulls?${query}`, {
-			headers: { Authorization: this.__authHeader },
-		});
-		if (!response.ok) throw new Error('Network response was not ok');
-		const res = { data: (await response.json()) as PullRequest[] };
-
-		return res.data;
-	}
-
-	async getAssignees(): Promise<Assignee[]> {
-		const response = await fetch(`${this.__repoUrl}/assignees`, {
-			headers: { Authorization: this.__authHeader },
-		});
-		if (!response.ok) throw new Error('Network response was not ok');
-		const res = { data: (await response.json()) as Assignee[] };
-
-		return res.data;
-	}
-
-	private __getUserRepositories = async (
-		page: string,
-	): Promise<AxiosResponse<GithubRepository[]>> => {
-		return await axios.get<GithubRepository[]>(
-			`https://api.github.com/user/repos?per_page=${PER_PAGE}&page=${page}`,
-			{
-				headers: {
-					Authorization: this.__authHeader,
-				},
-			},
-		);
-	};
-
-	async getUserRepositories(): Promise<GithubRepository[]> {
-		return await withPagination(this.__getUserRepositories);
-	}
-
-	private __getBranches = async (
-		page: string,
-	): Promise<AxiosResponse<GHBranch[]>> => {
-		const response = await fetch(
-			`${this.__repoUrl}/branches?per_page=${PER_PAGE}&page=${page}`,
-			{ headers: { Authorization: this.__authHeader } },
-		);
-		if (!response.ok) throw new Error('Network response was not ok');
-		return { data: (await response.json()) as GHBranch[] };
-	};
-
-	async getBranches(): Promise<string[]> {
-		return await withPagination(this.__getBranches);
-	}
-
-	async getRepoContents(branchName: string): Promise<GithubContent[]> {
-		const response = await fetch(
-			`${this.__repoUrl}/contents?ref=${branchName}`,
-			{ headers: { Authorization: this.__authHeader } },
-		);
-		if (!response.ok) throw new Error('Network response was not ok');
-		const res = { data: (await response.json()) as GithubContent[] };
-
-		return res.data;
-	}
+  private readonly __repo: string | null = null;
+  private readonly __baseUrl: string;
+  private readonly __authHeader: string;
+
+  constructor(oAuthToken: string, repoUrl: string | null) {
+    this.__baseUrl = "https://api.github.com";
+    this.__repo = repoUrl;
+    this.__authHeader = `Bearer ${oAuthToken}`;
+  }
+
+  private get __repoUrl() {
+    const { owner, name } = parseGithubRepoUrl(this.__repo ?? "");
+
+    return `${this.__baseUrl}/repos/${owner}/${name}`;
+  }
+
+  async createIssue(params: NewIssueParams) {
+    const response = await extendedFetch(`${this.__repoUrl}/issues`, {
+      method: "POST",
+      headers: {
+        Authorization: this.__authHeader,
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify(params),
+    });
+
+    return (await response.json()) as Issue;
+  }
+
+  async createPullRequest(params: CreatePRParams) {
+    const response = await extendedFetch(`${this.__repoUrl}/pulls`, {
+      method: "POST",
+      headers: {
+        Authorization: this.__authHeader,
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify(params),
+    });
+
+    return (await response.json()) as PullRequest;
+  }
+
+  async getPullRequests(params: ListPRParams) {
+    const queryParams = Object.entries(params).reduce<Record<string, string>>(
+      (acc, [key, value]) => {
+        if (value) {
+          acc[key] = value;
+        }
+
+        return acc;
+      },
+      {},
+    );
+
+    const query = new URLSearchParams(queryParams).toString();
+
+    const response = await extendedFetch(`${this.__repoUrl}/pulls?${query}`, {
+      headers: { Authorization: this.__authHeader },
+    });
+
+    return (await response.json()) as PullRequest[];
+  }
+
+  async getAssignees() {
+    const response = await extendedFetch(`${this.__repoUrl}/assignees`, {
+      headers: { Authorization: this.__authHeader },
+    });
+
+    return (await response.json()) as Assignee[];
+  }
+
+  private __getUserRepositories = (page: string) =>
+    extendedFetch(
+      `https://api.github.com/user/repos?per_page=${PER_PAGE}&page=${page}`,
+      { headers: { Authorization: this.__authHeader } },
+    );
+
+  async getUserRepositories(): Promise<GithubRepository[]> {
+    return await withPagination(this.__getUserRepositories);
+  }
+
+  private __getBranches = (page: string) =>
+    extendedFetch(
+      `${this.__repoUrl}/branches?per_page=${PER_PAGE}&page=${page}`,
+      {
+        headers: { Authorization: this.__authHeader },
+      },
+    );
+
+  async getBranches(): Promise<string[]> {
+    return await withPagination(this.__getBranches);
+  }
+
+  async getRepoContents(branchName: string) {
+    const response = await extendedFetch(
+      `${this.__repoUrl}/contents?ref=${branchName}`,
+      { headers: { Authorization: this.__authHeader } },
+    );
+    return (await response.json()) as GithubContent[];
+  }
 }
diff --git a/apps/backend/src/services/PostHogService.ts b/apps/backend/src/services/PostHogService.ts
index ff3c06d62..4c4f609fa 100644
--- a/apps/backend/src/services/PostHogService.ts
+++ b/apps/backend/src/services/PostHogService.ts
@@ -1,59 +1,55 @@
-import { buildCodemodSlug } from '@codemod-com/utilities';
-import axios, { isAxiosError } from 'axios';
+import {
+  buildCodemodSlug,
+  extendedFetch,
+  isFetchError,
+} from "@codemod-com/utilities";
 
 export class PostHogCodemodNotFoundError extends Error {}
 
 export class PostHogService {
-	private readonly __authHeader: string;
-	private readonly __projectId: string;
+  private readonly __authHeader: string;
+  private readonly __projectId: string;
 
-	constructor(authKey: string, projectId: string) {
-		this.__authHeader = `Bearer ${authKey}`;
-		this.__projectId = projectId;
-	}
+  constructor(authKey: string, projectId: string) {
+    this.__authHeader = `Bearer ${authKey}`;
+    this.__projectId = projectId;
+  }
 
-	async getCodemodTotalRuns(): Promise<
-		Array<{ slug: string; runs: number }>
-	> {
-		try {
-			const response = await fetch(
-				`https://app.posthog.com/api/projects/${this.__projectId}/query/`,
-				{
-					method: 'POST',
-					headers: {
-						'Content-Type': 'application/json',
-						Authorization: this.__authHeader,
-					},
-					body: JSON.stringify({
-						query: {
-							kind: 'HogQLQuery',
-							query: "select properties.codemodName, count(*) from events where event in ('codemod.CLI.codemodExecuted', 'codemod.VSCE.codemodExecuted') group by properties.codemodName limit 500",
-						},
-					}),
-				},
-			);
-			if (!response.ok) {
-				throw new Error(`HTTP error! status: ${response.status}`);
-			}
-			const data = await response.json();
+  async getCodemodTotalRuns(): Promise<Array<{ slug: string; runs: number }>> {
+    try {
+      const response = await extendedFetch(
+        `https://app.posthog.com/api/projects/${this.__projectId}/query/`,
+        {
+          method: "POST",
+          headers: { Authorization: this.__authHeader },
+          body: JSON.stringify({
+            query: {
+              kind: "HogQLQuery",
+              query:
+                "select properties.codemodName, count(*) from events where event in ('codemod.CLI.codemodExecuted', 'codemod.VSCE.codemodExecuted') group by properties.codemodName limit 500",
+            },
+          }),
+        },
+      );
+      const { data } = (await response.json()) as {
+        data: { results: Array<[string, number]> };
+      };
 
-			const result = data?.results?.map((value: [string, number]) => ({
-				// @TODO add isLocal field to telemetry event, exclude local events from total runs
-				slug: buildCodemodSlug(
-					value[0].replaceAll(' (from user machine)', ''),
-				),
-				runs: value[1],
-			}));
+      const result = data?.results?.map((value: [string, number]) => ({
+        // @TODO add isLocal field to telemetry event, exclude local events from total runs
+        slug: buildCodemodSlug(value[0].replaceAll(" (from user machine)", "")),
+        runs: value[1],
+      }));
 
-			return result;
-		} catch (error) {
-			const errorMessage = isAxiosError<{ message: string }>(error)
-				? error.response?.data.message
-				: (error as Error).message;
+      return result;
+    } catch (error) {
+      const errorMessage = isFetchError(error)
+        ? ((await error.response?.json()) as { message: string }).message
+        : (error as Error).message;
 
-			throw new PostHogCodemodNotFoundError(
-				`Failed to retrieve events. Reason: ${errorMessage}`,
-			);
-		}
-	}
+      throw new PostHogCodemodNotFoundError(
+        `Failed to retrieve events. Reason: ${errorMessage}`,
+      );
+    }
+  }
 }
diff --git a/apps/backend/src/services/SourceControl.ts b/apps/backend/src/services/SourceControl.ts
index ce65f87a7..2c5c504eb 100644
--- a/apps/backend/src/services/SourceControl.ts
+++ b/apps/backend/src/services/SourceControl.ts
@@ -1,4 +1,4 @@
-import { isAxiosError } from "axios";
+import { isFetchError } from "@codemod-com/utilities";
 
 export type NewIssueParams = Readonly<{
   body: string;
@@ -93,13 +93,17 @@ export interface SourceControlProvider {
 
 // biome-ignore lint/complexity/noStaticOnlyClass: reason?
 export class SourceControlError extends Error {
-  static parse(e: unknown) {
-    const message =
-      isAxiosError(e) && e.response?.data.message
-        ? e.response?.data.message
-        : e instanceof Error
-          ? e.message
-          : String(e);
+  static async parse(e: unknown) {
+    let message: any;
+    if (isFetchError(e)) {
+      const response = (await e.response?.json()) as { message?: string };
+      if (response.message) {
+        message = response.message;
+      }
+    }
+    if (!message) {
+      message = e instanceof Error ? e.message : String(e);
+    }
     return new SourceControlError(message);
   }
 }
@@ -112,7 +116,7 @@ export class SourceControl {
     try {
       return await provider.createIssue(params);
     } catch (e) {
-      throw SourceControlError.parse(e);
+      throw await SourceControlError.parse(e);
     }
   }
 
@@ -123,7 +127,7 @@ export class SourceControl {
     try {
       return await provider.createPullRequest(params);
     } catch (e) {
-      throw SourceControlError.parse(e);
+      throw await SourceControlError.parse(e);
     }
   }
 
@@ -134,7 +138,7 @@ export class SourceControl {
     try {
       return await provider.getPullRequests(params);
     } catch (e) {
-      throw SourceControlError.parse(e);
+      throw await SourceControlError.parse(e);
     }
   }
 
@@ -142,7 +146,7 @@ export class SourceControl {
     try {
       return await provider.getAssignees();
     } catch (e) {
-      throw SourceControlError.parse(e);
+      throw await SourceControlError.parse(e);
     }
   }
 
@@ -152,7 +156,7 @@ export class SourceControl {
     try {
       return await provider.getUserRepositories();
     } catch (e) {
-      throw SourceControlError.parse(e);
+      throw await SourceControlError.parse(e);
     }
   }
 
@@ -160,7 +164,7 @@ export class SourceControl {
     try {
       return await provider.getBranches();
     } catch (e) {
-      throw SourceControlError.parse(e);
+      throw await SourceControlError.parse(e);
     }
   }
 
@@ -171,7 +175,7 @@ export class SourceControl {
     try {
       return await provider.getRepoContents(branchName);
     } catch (e) {
-      throw SourceControlError.parse(e);
+      throw await SourceControlError.parse(e);
     }
   }
 }
diff --git a/apps/cli/package.json b/apps/cli/package.json
index 15deea8b0..676b7ea6b 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -43,7 +43,6 @@
     "@types/unzipper": "^0.10.9",
     "@types/yargs": "^17.0.13",
     "@vitest/coverage-v8": "^1.0.1",
-    "axios": "^1.6.8",
     "columnify": "^1.6.0",
     "cosmiconfig": "^8.3.6",
     "exponential-backoff": "^3.1.1",
@@ -82,8 +81,8 @@
     "access": "public"
   },
   "dependencies": {
-    "@ast-grep/cli": "^0.22.3",
-    "@ast-grep/napi": "^0.22.3",
+    "@ast-grep/cli": "^0.24.0",
+    "@ast-grep/napi": "^0.24.0",
     "esbuild": "^0.17.14",
     "keytar": "^7.9.0",
     "prettier": "^3.2.5"
diff --git a/apps/cli/src/apis.ts b/apps/cli/src/apis.ts
index 8e6b5f6dd..73642ba64 100644
--- a/apps/cli/src/apis.ts
+++ b/apps/cli/src/apis.ts
@@ -1,52 +1,50 @@
-import type {
-  CodemodDownloadLinkResponse,
-  CodemodListResponse,
-  GetScopedTokenResponse,
-  GetUserDataResponse,
-  VerifyTokenResponse,
+import {
+  type CodemodDownloadLinkResponse,
+  type CodemodListResponse,
+  type GetScopedTokenResponse,
+  type GetUserDataResponse,
+  type VerifyTokenResponse,
+  extendedFetch,
 } from "@codemod-com/utilities";
-import Axios, { type RawAxiosRequestHeaders } from "axios";
 import type FormData from "form-data";
 
-export const getCLIAccessToken = async (
-  accessToken: string,
-): Promise<GetScopedTokenResponse> => {
+export const getCLIAccessToken = async (accessToken: string) => {
   const url = new URL(`${process.env.AUTH_BACKEND_URL}/appToken`);
 
-  const res = await Axios.get<GetScopedTokenResponse>(url.toString(), {
+  const response = await extendedFetch(url.toString(), {
+    method: "GET",
     headers: { Authorization: `Bearer ${accessToken}` },
-    timeout: 10000,
+    signal: AbortSignal.timeout(10000),
   });
 
-  return res.data;
+  return (await response.json()) as GetScopedTokenResponse;
 };
 
-export const validateCLIToken = async (
-  accessToken: string,
-): Promise<VerifyTokenResponse> => {
-  const res = await Axios.get<VerifyTokenResponse>(
+export const validateCLIToken = async (accessToken: string) => {
+  const response = await extendedFetch(
     `${process.env.AUTH_BACKEND_URL}/verifyToken`,
     {
+      method: "GET",
       headers: { Authorization: `Bearer ${accessToken}` },
-      timeout: 5000,
+      signal: AbortSignal.timeout(10000),
     },
   );
 
-  return res.data;
+  return (await response.json()) as VerifyTokenResponse;
 };
 
-export const getUserData = async (
-  accessToken: string,
-): Promise<GetUserDataResponse | null> => {
+export const getUserData = async (accessToken: string) => {
   try {
-    const { data } = await Axios.get<GetUserDataResponse | object>(
+    const response = await extendedFetch(
       `${process.env.AUTH_BACKEND_URL}/userData`,
       {
         headers: { Authorization: `Bearer ${accessToken}` },
-        timeout: 5000,
+        signal: AbortSignal.timeout(5000),
       },
     );
 
+    const data = (await response.json()) as GetUserDataResponse;
+
     if (!("user" in data)) {
       return null;
     }
@@ -57,31 +55,25 @@ export const getUserData = async (
   }
 };
 
-export const publish = async (
-  accessToken: string,
-  formData: FormData,
-): Promise<void> => {
-  await Axios.post(`${process.env.BACKEND_URL}/publish`, formData, {
+export const publish = async (accessToken: string, formData: FormData) => {
+  await extendedFetch(`${process.env.BACKEND_URL}/publish`, {
+    method: "POST",
+    body: formData as any,
     headers: {
       Authorization: `Bearer ${accessToken}`,
       "Content-Type": "multipart/form-data",
     },
-    timeout: 10000,
+    signal: AbortSignal.timeout(10000),
   });
 };
 
-export const unpublish = async (
-  accessToken: string,
-  name: string,
-): Promise<void> => {
-  await Axios.post(
-    `${process.env.BACKEND_URL}/unpublish`,
-    { name },
-    {
-      headers: { Authorization: `Bearer ${accessToken}` },
-      timeout: 10000,
-    },
-  );
+export const unpublish = async (accessToken: string, name: string) => {
+  await extendedFetch(`${process.env.BACKEND_URL}/unpublish`, {
+    method: "POST",
+    body: JSON.stringify({ name }),
+    headers: { Authorization: `Bearer ${accessToken}` },
+    signal: AbortSignal.timeout(10000),
+  });
 };
 
 // @TODO
@@ -96,34 +88,34 @@ export const revokeCLIToken = async (accessToken: string): Promise<void> => {
 export const getCodemodDownloadURI = async (
   name: string,
   accessToken?: string,
-): Promise<CodemodDownloadLinkResponse> => {
+) => {
   const url = new URL(`${process.env.BACKEND_URL}/codemods/downloadLink`);
   if (name) {
     url.searchParams.set("name", name);
   }
 
-  const headers: RawAxiosRequestHeaders = {};
+  const headers = new Headers();
   if (accessToken) {
-    headers.Authorization = `Bearer ${accessToken}`;
+    headers.set("Authorization", `Bearer ${accessToken}`);
   }
 
-  const res = await Axios.get<CodemodDownloadLinkResponse>(url.toString(), {
+  const response = await extendedFetch(url.toString(), {
     headers,
-    timeout: 10000,
+    signal: AbortSignal.timeout(10000),
   });
 
-  return res.data;
+  return (await response.json()) as CodemodDownloadLinkResponse;
 };
 
 export const getCodemodList = async (options?: {
   accessToken?: string;
   search?: string | null;
-}): Promise<CodemodListResponse> => {
+}) => {
   const { accessToken, search } = options ?? {};
 
-  const headers: RawAxiosRequestHeaders = {};
+  const headers = new Headers();
   if (accessToken) {
-    headers.Authorization = `Bearer ${accessToken}`;
+    headers.set("Authorization", `Bearer ${accessToken}`);
   }
 
   const url = new URL(`${process.env.BACKEND_URL}/codemods/list`);
@@ -131,40 +123,40 @@ export const getCodemodList = async (options?: {
     url.searchParams.set("search", search);
   }
 
-  const res = await Axios.get<CodemodListResponse>(url.toString(), {
+  const response = await extendedFetch(url.toString(), {
     headers,
-    timeout: 10000,
+    signal: AbortSignal.timeout(10000),
   });
 
-  return res.data;
+  return (await response.json()) as CodemodListResponse;
 };
 
 type UserLoginIntentResponse = {
   id: string;
   iv: string;
 };
-export const generateUserLoginIntent =
-  async (): Promise<UserLoginIntentResponse> => {
-    const res = await Axios.post<UserLoginIntentResponse>(
-      `${process.env.AUTH_BACKEND_URL}/intents`,
-      {},
-    );
+export const generateUserLoginIntent = async () => {
+  const response = await extendedFetch(
+    `${process.env.AUTH_BACKEND_URL}/intents`,
+    {
+      method: "POST",
+      body: JSON.stringify({}),
+    },
+  );
 
-    return res.data;
-  };
+  return (await response.json()) as UserLoginIntentResponse;
+};
 
 type ConfirmUserLoggedInResponse = {
   token: string;
 };
-export const confirmUserLoggedIn = async (
-  sessionId: string,
-  iv: string,
-): Promise<string> => {
-  const res = await Axios.get<ConfirmUserLoggedInResponse>(
+export const confirmUserLoggedIn = async (sessionId: string, iv: string) => {
+  const response = await extendedFetch(
     `${process.env.AUTH_BACKEND_URL}/intents/${sessionId}?iv=${iv}`,
   );
 
-  return res.data.token;
+  const data = (await response.json()) as ConfirmUserLoggedInResponse;
+  return data.token;
 };
 
 type CreateCodeDiffResponse = {
@@ -174,15 +166,14 @@ type CreateCodeDiffResponse = {
 export const createCodeDiff = async (body: {
   beforeSnippet: string;
   afterSnippet: string;
-}): Promise<CreateCodeDiffResponse> => {
-  const res = await Axios.post<CreateCodeDiffResponse>(
-    `${process.env.BACKEND_URL}/diffs`,
-    {
+}) => {
+  const response = await extendedFetch(`${process.env.BACKEND_URL}/diffs`, {
+    method: "POST",
+    body: JSON.stringify({
       before: body.beforeSnippet,
       after: body.afterSnippet,
       source: "cli",
-    },
-  );
-
-  return res.data;
+    }),
+  });
+  return (await response.json()) as CreateCodeDiffResponse;
 };
diff --git a/apps/cli/src/buildCodemodOptions.ts b/apps/cli/src/buildCodemodOptions.ts
index c94eb334b..5e141941b 100644
--- a/apps/cli/src/buildCodemodOptions.ts
+++ b/apps/cli/src/buildCodemodOptions.ts
@@ -6,13 +6,13 @@ import type { Codemod, CodemodSettings } from "@codemod-com/runner";
 import {
   type AllEngines,
   type CodemodConfig,
+  FetchError,
   type FileSystem,
   type TarService,
   allEnginesSchema,
   doubleQuotify,
   parseCodemodConfig,
 } from "@codemod-com/utilities";
-import { AxiosError } from "axios";
 import unzipper from "unzipper";
 import { object, parse } from "valibot";
 import type { CodemodDownloaderBlueprint } from "./downloadCodemod.js";
@@ -184,10 +184,10 @@ export const buildSourcedCodemodOptions = async (
           return await codemodDownloader.download(subCodemodName, true);
         } catch (error) {
           spinner.fail();
-          if (error instanceof AxiosError) {
+          if (error instanceof FetchError) {
             if (
               error.response?.status === 400 &&
-              error.response.data.error === "Codemod not found"
+              (await error.response.json()).error === "Codemod not found"
             ) {
               throw new Error(
                 `Error locating one of the recipe codemods: ${chalk.bold(
diff --git a/apps/cli/src/commands/publish.ts b/apps/cli/src/commands/publish.ts
index 5cd5b966c..85abc7ac0 100644
--- a/apps/cli/src/commands/publish.ts
+++ b/apps/cli/src/commands/publish.ts
@@ -2,12 +2,11 @@ import * as fs from "node:fs";
 import { basename, dirname, join, resolve } from "node:path";
 import { type PrinterBlueprint, chalk } from "@codemod-com/printer";
 import {
+  FetchError,
   codemodNameRegex,
   doubleQuotify,
-  execPromise,
   parseCodemodConfig,
 } from "@codemod-com/utilities";
-import { AxiosError } from "axios";
 import FormData from "form-data";
 import { glob } from "glob";
 import inquirer from "inquirer";
@@ -263,7 +262,9 @@ export const handlePublishCliCommand = async (options: {
   } catch (error) {
     publishSpinner.fail();
     const message =
-      error instanceof AxiosError ? error.response?.data.error : String(error);
+      error instanceof FetchError
+        ? ((await error.response?.json()) as any).error
+        : String(error);
     const errorMessage = `${chalk.bold(
       `Could not publish the "${codemodRc.name}" codemod`,
     )}:\n${message}`;
diff --git a/apps/cli/src/commands/run.ts b/apps/cli/src/commands/run.ts
index 2441f4ba9..59220ca35 100644
--- a/apps/cli/src/commands/run.ts
+++ b/apps/cli/src/commands/run.ts
@@ -14,13 +14,13 @@ import {
 } from "@codemod-com/runner";
 import type { TelemetrySender } from "@codemod-com/telemetry";
 import {
+  FetchError,
   TarService,
   doubleQuotify,
   execPromise,
   parseCodemodConfig,
   sleep,
 } from "@codemod-com/utilities";
-import { AxiosError } from "axios";
 import inquirer from "inquirer";
 import type { TelemetryEvent } from "../analytics/telemetry.js";
 import { buildSourcedCodemodOptions } from "../buildCodemodOptions.js";
@@ -159,10 +159,10 @@ export const handleRunCliCommand = async (options: {
     try {
       codemod = await codemodDownloader.download(codemodSettings.name);
     } catch (error) {
-      if (error instanceof AxiosError) {
+      if (error instanceof FetchError) {
         if (
           error.response?.status === 400 &&
-          error.response.data.error === "Codemod not found"
+          (await error.response.json()).error === "Codemod not found"
         ) {
           printer.printConsoleMessage(
             "error",
diff --git a/apps/cli/src/commands/unpublish.ts b/apps/cli/src/commands/unpublish.ts
index 3f466de36..c2bdf8984 100644
--- a/apps/cli/src/commands/unpublish.ts
+++ b/apps/cli/src/commands/unpublish.ts
@@ -1,10 +1,10 @@
 import { type PrinterBlueprint, chalk } from "@codemod-com/printer";
 import {
+  FetchError,
   doubleQuotify,
   extractLibNameAndVersion,
   isNeitherNullNorUndefined,
 } from "@codemod-com/utilities";
-import { AxiosError } from "axios";
 import { unpublish } from "../apis.js";
 import { getCurrentUserData } from "../utils.js";
 
@@ -49,7 +49,9 @@ export const handleUnpublishCliCommand = async (options: {
   } catch (error) {
     spinner.fail();
     const message =
-      error instanceof AxiosError ? error.response?.data.error : String(error);
+      error instanceof FetchError
+        ? ((await error.response?.json()) as any).error
+        : String(error);
     const errorMessage = `${chalk.bold(
       `Could not unpublish the "${name}" codemod`,
     )}:\n${message}`;
diff --git a/apps/cli/src/downloadCodemod.ts b/apps/cli/src/downloadCodemod.ts
index c6840ee34..3690f664b 100644
--- a/apps/cli/src/downloadCodemod.ts
+++ b/apps/cli/src/downloadCodemod.ts
@@ -3,14 +3,16 @@ import { mkdir, readFile } from "node:fs/promises";
 import { join } from "node:path";
 import { type PrinterBlueprint, chalk } from "@codemod-com/printer";
 import type { Codemod } from "@codemod-com/runner";
-import type { CodemodDownloadLinkResponse } from "@codemod-com/utilities";
+import type {
+  CodemodDownloadLinkResponse,
+  FetchError,
+} from "@codemod-com/utilities";
 import {
   type CodemodConfig,
   doubleQuotify,
   parseCodemodConfig,
 } from "@codemod-com/utilities";
 import type { TarService } from "@codemod-com/utilities";
-import type { AxiosError } from "axios";
 import inquirer from "inquirer";
 import semver from "semver";
 import { getCodemodDownloadURI } from "./apis.js";
@@ -68,7 +70,7 @@ export class CodemodDownloader implements CodemodDownloaderBlueprint {
     } catch (err) {
       spinner?.fail();
       throw new Error(
-        (err as AxiosError<{ error: string }>).response?.data?.error ??
+        ((await (err as FetchError).response?.json()) as any).error ??
           "Error getting download link for codemod",
       );
     }
@@ -86,7 +88,7 @@ export class CodemodDownloader implements CodemodDownloaderBlueprint {
     } catch (err) {
       spinner?.fail();
       throw new Error(
-        (err as AxiosError<{ error: string }>).response?.data?.error ??
+        ((await (err as FetchError).response?.json()) as any).error ??
           "Error downloading codemod from the registry",
       );
     }
diff --git a/apps/cli/src/executeMainThread.ts b/apps/cli/src/executeMainThread.ts
index 360975521..caee54e5b 100644
--- a/apps/cli/src/executeMainThread.ts
+++ b/apps/cli/src/executeMainThread.ts
@@ -4,8 +4,11 @@ import {
   PostHogSender,
   type TelemetrySender,
 } from "@codemod-com/telemetry";
-import { doubleQuotify, execPromise } from "@codemod-com/utilities";
-import Axios from "axios";
+import {
+  addGlobalHook,
+  doubleQuotify,
+  execPromise,
+} from "@codemod-com/utilities";
 import semver from "semver";
 import yargs from "yargs";
 import { hideBin } from "yargs/helpers";
@@ -68,10 +71,20 @@ const initializeDependencies = async (argv: {
   const clientIdentifier =
     typeof argv.clientIdentifier === "string" ? argv.clientIdentifier : "CLI";
 
-  Axios.interceptors.request.use((config) => {
-    config.headers["X-Client-Identifier"] = clientIdentifier;
+  addGlobalHook((options) => {
+    options.headers = options.headers ?? {};
+
+    if (options.headers) {
+      if (options.headers instanceof Headers) {
+        options.headers.set("X-Client-Identifier", clientIdentifier);
+      } else if (Array.isArray(options.headers)) {
+        options.headers.push(["X-Client-Identifier", clientIdentifier]);
+      } else {
+        options.headers["X-Client-Identśifier"] = clientIdentifier;
+      }
+    }
 
-    return config;
+    return options;
   });
 
   const printer = new Printer(argv.json);
diff --git a/apps/cli/src/fileDownloadService.ts b/apps/cli/src/fileDownloadService.ts
index 70d885381..8a4116b80 100644
--- a/apps/cli/src/fileDownloadService.ts
+++ b/apps/cli/src/fileDownloadService.ts
@@ -1,6 +1,9 @@
 import type { PrinterBlueprint } from "@codemod-com/printer";
-import type { FileSystem } from "@codemod-com/utilities";
-import axios, { isAxiosError, type AxiosResponse } from "axios";
+import {
+  type FileSystem,
+  extendedFetch,
+  isFetchError,
+} from "@codemod-com/utilities";
 
 export type FileDownloadServiceBlueprint = Readonly<{
   cacheEnabled: boolean;
@@ -43,7 +46,8 @@ export class FileDownloadService implements FileDownloadServiceBlueprint {
       }
     }
 
-    const response = await fetch(url);if (!response.ok) throw new Error('Network response was not ok.');const data = await response.arrayBuffer();
+    const response = await extendedFetch(url);
+    const data = await response.arrayBuffer();
 
     const buffer = Buffer.from(data);
 
@@ -65,12 +69,14 @@ export class FileDownloadService implements FileDownloadServiceBlueprint {
   private async __getRemoteFileLastModified(
     url: string,
   ): Promise<number | null> {
-    let response: AxiosResponse;
+    let response: Response;
 
     try {
-      response = await fetch(url, { signal: AbortSignal.timeout(15000) })if (!response.ok) throw new Error('Network response was not ok.');;
+      response = await extendedFetch(url, {
+        signal: AbortSignal.timeout(15000),
+      });
     } catch (error) {
-      if (!isAxiosError(error)) {
+      if (!isFetchError(error)) {
         throw error;
       }
 
@@ -79,11 +85,10 @@ export class FileDownloadService implements FileDownloadServiceBlueprint {
       if (status === 403) {
         return null;
       }
-
       return null;
     }
 
-    const lastModified = response.headers["last-modified"];
+    const lastModified = response.headers.get("last-modified");
 
     return lastModified ? Date.parse(lastModified) : null;
   }
diff --git a/apps/frontend/app/(website)/studio/src/api/getCodeDiff.ts b/apps/frontend/app/(website)/studio/src/api/getCodeDiff.ts
index c8a955fde..b512b9549 100644
--- a/apps/frontend/app/(website)/studio/src/api/getCodeDiff.ts
+++ b/apps/frontend/app/(website)/studio/src/api/getCodeDiff.ts
@@ -12,11 +12,13 @@ export const getCodeDiff = async (body: {
   const { diffId, iv } = body;
 
   try {
-    const res = await apiClient.get<GetCodeDiffResponse>(
-      `diffs/${diffId}?iv=${iv}`,
-    );
+    const response = await apiClient(`diffs/${diffId}?iv=${iv}`, {
+      headers: {
+        "Content-Type": "application/json",
+      },
+    });
 
-    return res.data;
+    return (await response.json()) as GetCodeDiffResponse;
   } catch (e) {
     console.error(e);
     return null;
diff --git a/apps/frontend/app/(website)/studio/src/api/populateLoginIntent.ts b/apps/frontend/app/(website)/studio/src/api/populateLoginIntent.ts
index f5d7d5ed4..42b762e61 100644
--- a/apps/frontend/app/(website)/studio/src/api/populateLoginIntent.ts
+++ b/apps/frontend/app/(website)/studio/src/api/populateLoginIntent.ts
@@ -1,6 +1,6 @@
 import { authApiClient } from "@/utils/apis/client";
+import type { FetchError } from "@codemod-com/utilities";
 import { isNeitherNullNorUndefined } from "@studio/utils/isNeitherNullNorUndefined";
-import type { AxiosError } from "axios";
 import { POPULATE_LOGIN_INTENT } from "../constants";
 import { Either } from "../utils/Either";
 
@@ -38,7 +38,12 @@ export const populateLoginIntent = async ({
 
     return Either.right(accessToken);
   } catch (e) {
-    const err = e as AxiosError<{ message?: string }>;
-    return Either.left(new Error(err.response?.data.message ?? err.message));
+    const err = e as FetchError;
+    return Either.left(
+      new Error(
+        ((await err.response?.json()) as { message?: string }).message ??
+          err.message,
+      ),
+    );
   }
 };
diff --git a/apps/frontend/app/(website)/studio/src/api/sendMessage.ts b/apps/frontend/app/(website)/studio/src/api/sendMessage.ts
index 569a15801..05a76aa6a 100644
--- a/apps/frontend/app/(website)/studio/src/api/sendMessage.ts
+++ b/apps/frontend/app/(website)/studio/src/api/sendMessage.ts
@@ -1,5 +1,5 @@
 import { apiClient } from "@/utils/apis/client";
-import type { AxiosError } from "axios";
+import type { FetchError } from "@codemod-com/utilities";
 import { SEND_MESSAGE } from "../constants";
 import { Either } from "../utils/Either";
 
@@ -22,23 +22,26 @@ const sendMessage = async ({
   token,
 }: SendMessageRequest): Promise<Either<Error, SendMessageResponse>> => {
   try {
-    const res = await apiClient.post<SendMessageResponse>(
-      SEND_MESSAGE,
-      {
+    const response = await apiClient(SEND_MESSAGE, {
+      method: "POST",
+      headers: {
+        Authorization: `Bearer ${token}`,
+      },
+      body: JSON.stringify({
         message,
         parentMessageId,
-      },
-      {
-        headers: {
-          Authorization: `Bearer ${token}`,
-        },
-      },
-    );
+      }),
+    });
 
-    return Either.right(res.data);
+    return Either.right((await response.json()) as SendMessageResponse);
   } catch (e) {
-    const err = e as AxiosError<{ message?: string }>;
-    return Either.left(new Error(err.response?.data.message ?? err.message));
+    const err = e as FetchError;
+    return Either.left(
+      new Error(
+        ((await err.response?.json()) as { message?: string }).message ??
+          err.message,
+      ),
+    );
   }
 };
 
diff --git a/apps/frontend/app/(website)/studio/src/hooks/useAPI.ts b/apps/frontend/app/(website)/studio/src/hooks/useAPI.ts
index 445751d09..8a5feee3d 100644
--- a/apps/frontend/app/(website)/studio/src/hooks/useAPI.ts
+++ b/apps/frontend/app/(website)/studio/src/hooks/useAPI.ts
@@ -2,14 +2,13 @@ import { apiClient } from "@/utils/apis/client";
 import { useAuth } from "@clerk/nextjs";
 import { mockedEndpoints } from "@shared/mocks";
 import { isServer } from "@studio/config";
-import type { AxiosResponse } from "axios";
 
 const shouldUseMocks =
   !isServer &&
   process.env.NODE_ENV === "development" &&
   Boolean(localStorage?.getItem("useMocks"));
 const mockified = (
-  verb: "put" | "get" | "post",
+  verb: "PUT" | "GET" | "POST",
   endpoint: string | ((x: any) => string),
   ...rest: any[]
 ) => {
@@ -20,8 +19,7 @@ const mockified = (
     const response = mockedEndpoints[path][verb](...rest);
     return new Promise((r) => setTimeout(() => r(response), 1000));
   }
-  // @ts-ignore
-  return apiClient[verb](...rest);
+  return apiClient(path, { method: verb, ...rest });
 };
 
 export const useAPI = <T>(endpoint: string) => {
@@ -39,21 +37,32 @@ export const useAPI = <T>(endpoint: string) => {
   return {
     get: async <U = T>() =>
       await (shouldUseMocks
-        ? (mockified("get", endpoint, await getHeaders()) as Promise<
-            AxiosResponse<U>
-          >)
-        : apiClient.get<U>(endpoint, await getHeaders())),
+        ? (mockified("GET", endpoint, await getHeaders()) as Promise<U>)
+        : ((
+            await apiClient(endpoint, {
+              method: "GET",
+              ...(await getHeaders()),
+            })
+          ).json() as Promise<U>)),
     put: async <U = T>(body: U) =>
       await (shouldUseMocks
-        ? (mockified("put", endpoint, body, await getHeaders()) as Promise<
-            AxiosResponse<U>
-          >)
-        : apiClient.put<U>(endpoint, body, await getHeaders())),
+        ? (mockified("PUT", endpoint, body, await getHeaders()) as Promise<U>)
+        : ((
+            await apiClient(endpoint, {
+              method: "PUT",
+              body: JSON.stringify(body),
+              ...(await getHeaders()),
+            })
+          ).json() as Promise<U>)),
     post: async <U, K = T>(body: U) =>
       await (shouldUseMocks
-        ? (mockified("post", endpoint, body, await getHeaders()) as Promise<
-            AxiosResponse<K>
-          >)
-        : apiClient.post<K>(endpoint, body, await getHeaders())),
+        ? (mockified("POST", endpoint, body, await getHeaders()) as Promise<K>)
+        : ((
+            await apiClient(endpoint, {
+              method: "POST",
+              body: JSON.stringify(body),
+              ...(await getHeaders()),
+            })
+          ).json() as Promise<K>)),
   };
 };
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index 04f078624..e34d642b9 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -71,7 +71,6 @@
     "ai": "^2.1.32",
     "ast-node-builder": "^4.2.1",
     "ast-types": "^0.14.2",
-    "axios": "^1.6.8",
     "change-case": "^5.2.0",
     "chart.js": "^4.4.2",
     "class-variance-authority": "^0.7.0",
diff --git a/apps/frontend/utils/apis/client.ts b/apps/frontend/utils/apis/client.ts
index 62c9e6e7a..9353ef8f8 100644
--- a/apps/frontend/utils/apis/client.ts
+++ b/apps/frontend/utils/apis/client.ts
@@ -1,37 +1,32 @@
 import { env } from "@/env";
-import axios, { type AxiosError } from "axios";
+import { extendedFetch, isFetchError } from "@codemod-com/utilities";
 import toast from "react-hot-toast";
 
-const apiClient = axios.create({
-  baseURL: env.NEXT_PUBLIC_API_URL,
-  timeout: 60000,
-});
+const createClient =
+  (baseUrl: string) => async (path: string, options?: RequestInit) => {
+    try {
+      return await extendedFetch(`${baseUrl}${path}`, {
+        signal: AbortSignal.timeout(60000),
+        ...options,
+      });
+    } catch (error) {
+      if (isFetchError(error) && error.response?.status) {
+        toast.error(
+          ((await error.response.json()) as { message?: string })?.message ??
+            "Network Error",
+          {
+            position: "top-center",
+          },
+        );
+      }
+      throw error;
+    }
+  };
+
+const apiClient = createClient(env.NEXT_PUBLIC_API_URL);
 
 // mostly for local dev, in prod they should be on the same domain.
 // later we need to figure out how to do this in a better way
-const authApiClient = axios.create({
-  baseURL: env.NEXT_PUBLIC_AUTH_API_URL,
-  timeout: 60000,
-});
-
-const errorHandler = (error: AxiosError<{ message?: string }>) => {
-  if (error.response?.status) {
-    toast.error(error.response?.data.message ?? "Network Error", {
-      position: "top-center",
-    });
-  }
-
-  return Promise.reject({ ...error });
-};
-
-apiClient.interceptors.response.use(
-  (response) => response,
-  (error) => errorHandler(error),
-);
-
-authApiClient.interceptors.response.use(
-  (response) => response,
-  (error) => errorHandler(error),
-);
+const authApiClient = createClient(env.NEXT_PUBLIC_AUTH_API_URL);
 
 export { apiClient, authApiClient };
diff --git a/apps/modgpt/package.json b/apps/modgpt/package.json
index 97876ca9c..ebe689b9c 100644
--- a/apps/modgpt/package.json
+++ b/apps/modgpt/package.json
@@ -14,7 +14,6 @@
     "@fastify/multipart": "^8.1.0",
     "@fastify/rate-limit": "9.0.1",
     "ai": "2.2.29",
-    "axios": "^1.6.8",
     "chatgpt": "5.2.5",
     "dotenv": "^16.4.5",
     "fastify": "4.25.1",
diff --git a/apps/modgpt/src/plugins/authPlugin.ts b/apps/modgpt/src/plugins/authPlugin.ts
index aa05eb427..cd6d17529 100644
--- a/apps/modgpt/src/plugins/authPlugin.ts
+++ b/apps/modgpt/src/plugins/authPlugin.ts
@@ -1,5 +1,8 @@
-import type { OrganizationMembership, User } from "@codemod-com/utilities";
-import axios from "axios";
+import {
+  type OrganizationMembership,
+  type User,
+  extendedFetch,
+} from "@codemod-com/utilities";
 import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
 import fp from "fastify-plugin";
 import { environment } from "../dev-utils/configs";
@@ -30,9 +33,15 @@ async function authPlugin(fastify: FastifyInstance, _opts: unknown) {
       try {
         const authHeader = request.headers.authorization;
 
-        if (!authHeader) reply.code(401).send({ error: "Unauthorized" });
+        if (!authHeader) {
+          reply.code(401).send({ error: "Unauthorized" });
+          return;
+        }
 
-        await const controller = new AbortController();setTimeout(() => controller.abort(), 5000); // Assuming a default timeout of 5000ms as it was not specified in the original axios callconst response = await fetch(`${environment.AUTH_SERVICE_URL}/verifyToken`, {  headers: {    Authorization: authHeader,  },  signal: controller.signal});if (!response.ok) throw new Error('Failed to fetch');const result = { data: await response.json() };;
+        await extendedFetch(`${environment.AUTH_SERVICE_URL}/verifyToken`, {
+          headers: { Authorization: authHeader },
+          signal: AbortSignal.timeout(5000),
+        });
       } catch (error) {
         console.log(error);
       }
diff --git a/apps/shared/mocks/gh-run.ts b/apps/shared/mocks/gh-run.ts
index df0c4ca7e..f99973176 100644
--- a/apps/shared/mocks/gh-run.ts
+++ b/apps/shared/mocks/gh-run.ts
@@ -105,33 +105,27 @@ export const mockGHBranches: GHBranch[] = [
 ];
 export const mockedGhRunEndpoints = {
   [GH_REPO_LIST]: {
-    get: (): { data: GithubRepository[] } => ({ data: mockGithubRepositories }),
+    GET: (): GithubRepository[] => mockGithubRepositories,
   },
   [GH_BRANCH_LIST]: {
-    post: ({ repoUrl }: { repoUrl: string }): { data: GHBranch[] } => {
+    POST: ({ repoUrl }: { repoUrl: string }): GHBranch[] => {
       isSuccess = repoUrl === "success";
-      return {
-        data: mockGHBranches,
-      };
+      return mockGHBranches;
     },
   },
   [RUN_CODEMOD]: {
-    post: (): { data: CodemodRunStatus } => ({
-      data: { codemodRunId: "1", success: true },
-    }),
+    POST: (): CodemodRunStatus => ({ codemodRunId: "1", success: true }),
   },
   [GET_EXECUTION_STATUS("1")]: {
-    get: (): { data: GetExecutionStatusResponse } => {
+    GET: (): GetExecutionStatusResponse => {
       const index = executionResultsIndex;
       executionResultsIndex =
         executionResultsIndex === getExecutionResults().length
           ? 0
           : executionResultsIndex + 1;
       return {
-        data: {
-          result: getExecutionResults()[index] || null,
-          success: true,
-        },
+        result: getExecutionResults()[index] || null,
+        success: true,
       };
     },
   },
diff --git a/apps/task-manager/package.json b/apps/task-manager/package.json
index a2bb5e2ac..c99fc44ff 100644
--- a/apps/task-manager/package.json
+++ b/apps/task-manager/package.json
@@ -8,14 +8,14 @@
   "author": "Codemod inc.",
   "private": true,
   "dependencies": {
-    "axios": "^1.6.8",
     "bullmq": "^5.7.2",
     "dotenv": "^16.4.5",
     "ioredis": "^5.4.1",
     "parse-github-url": "1.0.2",
     "simple-git": "^3.24.0",
     "uuid": "^10.0.0",
-    "valibot": "^0.24.1"
+    "valibot": "^0.24.1",
+    "@codemod-com/utilities": "workspace:*"
   },
   "type": "module",
   "devDependencies": {
diff --git a/apps/task-manager/src/services/Auth.ts b/apps/task-manager/src/services/Auth.ts
index 12020be75..e0f6a2432 100644
--- a/apps/task-manager/src/services/Auth.ts
+++ b/apps/task-manager/src/services/Auth.ts
@@ -1,47 +1,44 @@
-import axios, { isAxiosError } from 'axios';
+import { extendedFetch } from "@codemod-com/utilities";
 
 export class AuthError extends Error {}
 
 const USER_ID_REGEX = /^[a-z0-9_]+$/i;
 
 export class AuthService {
-	private readonly __authHeader: string;
-
-	constructor(authKey: string) {
-		if (!authKey) {
-			throw new AuthError('Invalid auth key provided.');
-		}
-		this.__authHeader = `Bearer ${authKey}`;
-	}
-
-	async getAuthToken(userId: string): Promise<string> {
-		try {
-			if (!USER_ID_REGEX.test(userId)) {
-				throw new AuthError('Invalid userId.');
-			}
-
-			const response = await fetch(
-				`https://api.clerk.dev/v1/users/${userId}/oauth_access_tokens/github`,
-				{ headers: { Authorization: this.__authHeader } },
-			);
-			if (!response.ok) {
-				throw new Error('Failed to fetch data');
-			}
-			const result = { data: await response.json() };
-
-			const token = result.data[0]?.token;
-
-			if (!token) {
-				throw new AuthError('Missing OAuth token');
-			}
-
-			return token;
-		} catch (error) {
-			const { message } = error as Error;
-
-			throw new AuthError(
-				`Failed to retrieve OAuth token for GitHub. Reason: ${message}`,
-			);
-		}
-	}
+  private readonly __authHeader: string;
+
+  constructor(authKey: string) {
+    if (!authKey) {
+      throw new AuthError("Invalid auth key provided.");
+    }
+    this.__authHeader = `Bearer ${authKey}`;
+  }
+
+  async getAuthToken(userId: string): Promise<string> {
+    try {
+      if (!USER_ID_REGEX.test(userId)) {
+        throw new AuthError("Invalid userId.");
+      }
+
+      const response = await extendedFetch(
+        `https://api.clerk.dev/v1/users/${userId}/oauth_access_tokens/github`,
+        { headers: { Authorization: this.__authHeader } },
+      );
+      const result = { data: (await response.json()) as { token: string }[] };
+
+      const token = result.data[0]?.token;
+
+      if (!token) {
+        throw new AuthError("Missing OAuth token");
+      }
+
+      return token;
+    } catch (error) {
+      const { message } = error as Error;
+
+      throw new AuthError(
+        `Failed to retrieve OAuth token for GitHub. Reason: ${message}`,
+      );
+    }
+  }
 }
diff --git a/apps/task-manager/src/services/GithubProvider.ts b/apps/task-manager/src/services/GithubProvider.ts
index 2255db769..b2d1ae9cd 100644
--- a/apps/task-manager/src/services/GithubProvider.ts
+++ b/apps/task-manager/src/services/GithubProvider.ts
@@ -1,6 +1,7 @@
+import { extendedFetch } from "@codemod-com/utilities";
 import { type SimpleGit, simpleGit } from "simple-git";
 import type { CodemodMetadata } from "../jobs/runCodemod";
-import { axiosRequest, parseGithubRepoUrl } from "../util";
+import { parseGithubRepoUrl } from "../util";
 
 const BASE_URL = "https://api.github.com";
 
@@ -116,20 +117,22 @@ export class GithubProviderService {
       const title = `[${codemodName}]: Codemod changes for ${authorName}/${repoName}.`;
       const body = `Changes applied with ${codemodName} codemod.`;
 
-      const pullRequestResponse = await axiosRequest<PullRequestResponse>(
-        url,
-        "post",
-        {
+      const response = await extendedFetch(url, {
+        method: "POST",
+        headers: {
+          Authorization: `Bearer ${this.__codemodMetadata.token}`,
+          Accept: "application/vnd.github+json",
+        },
+        body: JSON.stringify({
           head: this.__currentBranch,
           base: branch ?? this.__base,
           title,
           body,
-        },
-        {
-          Authorization: `Bearer ${this.__codemodMetadata.token}`,
-          Accept: "application/vnd.github+json",
-        },
-      );
+        }),
+      });
+
+      const pullRequestResponse =
+        (await response.json()) as PullRequestResponse;
 
       return pullRequestResponse.html_url;
     } catch (error) {
diff --git a/apps/task-manager/src/util.ts b/apps/task-manager/src/util.ts
index 1135c7495..66dc0437e 100644
--- a/apps/task-manager/src/util.ts
+++ b/apps/task-manager/src/util.ts
@@ -1,62 +1,26 @@
-import axios, { type AxiosRequestConfig, type AxiosResponse } from 'axios';
-import gh from 'parse-github-url';
+import gh from "parse-github-url";
 
-import { parseEnvironment } from './schemata/env.js';
+import { parseEnvironment } from "./schemata/env.js";
 
 export const environment = parseEnvironment(process.env);
 
 class InvalidGithubUrlError extends Error {}
 class ParseGithubUrlError extends Error {}
-class AxiosRequestError extends Error {}
 
 type Repository = {
-	authorName: string;
-	repoName: string;
+  authorName: string;
+  repoName: string;
 };
 
 export function parseGithubRepoUrl(url: string): Repository {
-	try {
-		const { owner, name } = gh(url) ?? {};
-		if (!owner || !name)
-			throw new InvalidGithubUrlError('Missing owner or name');
-
-		return { authorName: owner, repoName: name };
-	} catch (error) {
-		const errorMessage =
-			error instanceof Error ? error.message : String(error);
-		throw new ParseGithubUrlError(errorMessage);
-	}
-}
-
-export async function axiosRequest<T>(
-	url: string,
-	method: 'get' | 'post' | 'put' | 'delete',
-	data: Record<string, unknown> | null,
-	headers?: Record<string, string>,
-): Promise<T> {
-	try {
-		const config: AxiosRequestConfig = {
-			url,
-			method,
-			data,
-			headers,
-		};
-
-		const res: AxiosResponse<T> = await fetch(config.url, {
-			method: config.method,
-			headers: config.headers,
-			body: JSON.stringify(config.data),
-			signal: config.timeout
-				? AbortSignal.timeout(config.timeout)
-				: undefined,
-		}).then((response) => {
-			if (!response.ok) {
-				throw new AxiosRequestError();
-			}
-			return response.json().then((data) => ({ data }));
-		});
-		return res.data;
-	} catch (error) {
-		throw new AxiosRequestError();
-	}
+  try {
+    const { owner, name } = gh(url) ?? {};
+    if (!owner || !name)
+      throw new InvalidGithubUrlError("Missing owner or name");
+
+    return { authorName: owner, repoName: name };
+  } catch (error) {
+    const errorMessage = error instanceof Error ? error.message : String(error);
+    throw new ParseGithubUrlError(errorMessage);
+  }
 }
diff --git a/apps/vsce/package.json b/apps/vsce/package.json
index b4be81fa1..f69f0a706 100644
--- a/apps/vsce/package.json
+++ b/apps/vsce/package.json
@@ -223,8 +223,6 @@
     "@codemod-com/telemetry": "workspace:*",
     "@reduxjs/toolkit": "^1.9.5",
     "@vscode/vsce": "^2.22.0",
-    "axios": "^1.6.8",
-    "axios-retry": "^4.0.0",
     "diff": "^5.1.0",
     "fast-deep-equal": "^3.1.3",
     "fp-ts": "^2.15.0",
@@ -235,7 +233,7 @@
     "io-ts-types": "^0.5.19",
     "monocle-ts": "^2.3.13",
     "newtype-ts": "^0.3.5",
-    "nock": "^13.5.1",
+    "nock": "beta",
     "redux-persist": "^6.0.0",
     "semver": "^7.3.8",
     "ts-morph": "^19.0.0",
diff --git a/apps/vsce/src/axios/index.ts b/apps/vsce/src/axios/index.ts
deleted file mode 100644
index c0d737bd5..000000000
--- a/apps/vsce/src/axios/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import axios from "axios";
-import axiosRetry from "axios-retry";
-
-const retryingClient = axios.create();
-axiosRetry(retryingClient);
-
-export const DEFAULT_RETRY_COUNT = 3;
-export { retryingClient };
diff --git a/apps/vsce/src/components/downloadService.ts b/apps/vsce/src/components/downloadService.ts
index 70fb205fe..0c60459a5 100644
--- a/apps/vsce/src/components/downloadService.ts
+++ b/apps/vsce/src/components/downloadService.ts
@@ -1,7 +1,7 @@
 import type { Mode } from "node:fs";
-import { type AxiosResponse, isAxiosError } from "axios";
+import { isFetchError } from "@codemod-com/utilities";
 import type { FileSystem, Uri } from "vscode";
-import { DEFAULT_RETRY_COUNT, retryingClient } from "../axios";
+import { DEFAULT_RETRY_COUNT, retryingClient } from "../fetch";
 import type { FileSystemUtilities } from "./fileSystemUtilities";
 
 export class RequestError extends Error {}
@@ -27,21 +27,20 @@ export class DownloadService {
     const localModificationTime =
       await this.#fileSystemUtilities.getModificationTime(uri);
 
-    let response: AxiosResponse | undefined;
+    let response: Response | undefined;
 
     try {
-      response = await retryingClient.head(url, {
-        timeout: 15000,
-        "axios-retry": {
-          retries: DEFAULT_RETRY_COUNT,
-        },
+      response = await retryingClient(url, {
+        method: "HEAD",
+        retries: DEFAULT_RETRY_COUNT,
+        signal: AbortSignal.timeout(15000),
       });
     } catch (error) {
       if (localModificationTime > 0) {
         return false;
       }
 
-      if (!isAxiosError(error)) {
+      if (!isFetchError(error)) {
         throw error;
       }
 
@@ -56,7 +55,7 @@ export class DownloadService {
       throw new RequestError(`Could not make a request to ${url}`);
     }
 
-    const lastModified = response?.headers["last-modified"] ?? null;
+    const lastModified = response?.headers.get("last-modified") ?? null;
     const remoteModificationTime = lastModified
       ? Date.parse(lastModified)
       : localModificationTime;
@@ -75,13 +74,11 @@ export class DownloadService {
     uri: Uri,
     chmod: Mode | null,
   ): Promise<void> {
-    const response = await retryingClient.get(url, {
-      responseType: "arraybuffer",
-      "axios-retry": {
-        retries: DEFAULT_RETRY_COUNT,
-      },
+    const response = await retryingClient(url, {
+      retries: DEFAULT_RETRY_COUNT,
+      signal: AbortSignal.timeout(15000),
     });
-    const content = new Uint8Array(response.data);
+    const content = new Uint8Array(await response.arrayBuffer());
 
     await this.#fileSystem.writeFile(uri, content);
 
diff --git a/apps/vsce/src/components/engineService.ts b/apps/vsce/src/components/engineService.ts
index e1035cd75..c6bbb77b1 100644
--- a/apps/vsce/src/components/engineService.ts
+++ b/apps/vsce/src/components/engineService.ts
@@ -9,7 +9,7 @@ import { createHash } from "node:crypto";
 import { existsSync } from "node:fs";
 import { join } from "node:path";
 import * as readline from "node:readline";
-import axios from "axios";
+import { extendedFetch } from "@codemod-com/utilities";
 import * as E from "fp-ts/Either";
 import { type FileSystem, Uri, commands, window } from "vscode";
 import type { Case } from "../cases/types";
@@ -80,14 +80,17 @@ const CODEMOD_ENGINE_NODE_COMMAND = "codemod";
 const CODEMOD_ENGINE_NODE_POLLING_INTERVAL = 1250;
 const CODEMOD_ENGINE_NODE_POLLING_ITERATIONS_LIMIT = 200;
 
-export const getCodemodList = async (): Promise<CodemodListResponse> => {
+export const getCodemodList = async () => {
   const url = new URL("https://backend.codemod.com/codemods/list");
 
-  const res = await axios.get<CodemodListResponse>(url.toString(), {
-    timeout: 10000,
+  const response = await extendedFetch(url.toString(), {
+    headers: {
+      "Content-Type": "application/json",
+    },
+    signal: AbortSignal.timeout(10000),
   });
 
-  return res.data;
+  return (await response.json()) as CodemodListResponse;
 };
 
 const buildCodemodEntry = (
diff --git a/apps/vsce/src/components/webview/MainProvider.ts b/apps/vsce/src/components/webview/MainProvider.ts
index fba6eaf35..f79c4d973 100644
--- a/apps/vsce/src/components/webview/MainProvider.ts
+++ b/apps/vsce/src/components/webview/MainProvider.ts
@@ -1,559 +1,528 @@
-import axios from 'axios';
-import areEqual from 'fast-deep-equal';
-import { glob } from 'glob';
+import { extendedFetch, isFetchError } from "@codemod-com/utilities";
+import areEqual from "fast-deep-equal";
+import { glob } from "glob";
 import {
-	type ExtensionContext,
-	Uri,
-	type WebviewView,
-	type WebviewViewProvider,
-	commands,
-	window,
-	workspace,
-} from 'vscode';
-import type { Store } from '../../data';
-import { actions } from '../../data/slice';
-import { createIssueResponseCodec } from '../../github/types';
+  type ExtensionContext,
+  Uri,
+  type WebviewView,
+  type WebviewViewProvider,
+  commands,
+  window,
+  workspace,
+} from "vscode";
+import type { Store } from "../../data";
+import { actions } from "../../data/slice";
+import { createIssueResponseCodec } from "../../github/types";
 import {
-	type CodemodNodeHashDigest,
-	relativeToAbsolutePath,
-	selectCodemodArguments,
-} from '../../selectors/selectCodemodTree';
-import { selectMainWebviewViewProps } from '../../selectors/selectMainWebviewViewProps';
-import { buildGlobPattern, isNeitherNullNorUndefined } from '../../utilities';
-import type { EngineService } from '../engineService';
-import { type MessageBus, MessageKind } from '../messageBus';
-import { WebviewResolver } from './WebviewResolver';
+  type CodemodNodeHashDigest,
+  relativeToAbsolutePath,
+  selectCodemodArguments,
+} from "../../selectors/selectCodemodTree";
+import { selectMainWebviewViewProps } from "../../selectors/selectMainWebviewViewProps";
+import { buildGlobPattern, isNeitherNullNorUndefined } from "../../utilities";
+import type { EngineService } from "../engineService";
+import { type MessageBus, MessageKind } from "../messageBus";
+import { WebviewResolver } from "./WebviewResolver";
 import type {
-	CodemodHash,
-	WebviewMessage,
-	WebviewResponse,
-} from './webviewEvents';
+  CodemodHash,
+  WebviewMessage,
+  WebviewResponse,
+} from "./webviewEvents";
 
 export const validateAccessToken = async (
-	accessToken: string,
+  accessToken: string,
 ): Promise<void> => {
-	try {
-		const controller = new AbortController();
-		const timeoutId = setTimeout(() => controller.abort(), 5000);
-		const response = await fetch(
-			'https://backend.codemod.com/verifyToken',
-			{
-				method: 'POST',
-				headers: { Authorization: `Bearer ${accessToken}` },
-				signal: controller.signal,
-			},
-		);
-		clearTimeout(timeoutId);
-		if (!response.ok) throw new Error('Network response was not ok.');
-		const data = await response.json();
-
-		return response.data;
-	} catch (error) {
-		if ((!error) instanceof Error && error.name === 'AbortError') {
-			console.error(error);
-		}
-	}
+  try {
+    const response = await extendedFetch(
+      "https://backend.codemod.com/verifyToken",
+      {
+        method: "POST",
+        headers: { Authorization: `Bearer ${accessToken}` },
+        signal: AbortSignal.timeout(5000),
+      },
+    );
+    return await response.json();
+  } catch (error) {
+    if (!isFetchError(error)) {
+      console.error(error);
+    }
+  }
 };
 
 export const createIssue = async (
-	title: string,
-	body: string,
-	accessToken: string,
-	onSuccess: () => void,
-	onFail: () => Promise<void>,
+  title: string,
+  body: string,
+  accessToken: string,
+  onSuccess: () => void,
+  onFail: () => Promise<void>,
 ): Promise<{ status: number; html_url: string | null }> => {
-	// call API to create Github Issue
-	const codemodRegistryRepoUrl = 'https://github.com/codemod-com/codemod';
-
-	const result = await fetch(
-		'https://backend.codemod.com/sourceControl/github/issues',
-		{
-			method: 'POST',
-			headers: { Authorization: `Bearer ${accessToken}` },
-			body: JSON.stringify({
-				title,
-				body,
-				repoUrl: codemodRegistryRepoUrl,
-			}),
-		},
-	);
-	if (!result.ok) throw new Error('Network response was not ok.');
-	const data = await result.json();
-	if (result.status !== 200) {
-		await onFail();
-		return { status: result.status, html_url: null };
-	}
-
-	const { data } = result;
-
-	const validation = createIssueResponseCodec.decode(data);
-
-	if (validation._tag === 'Left') {
-		await onFail();
-		window.showErrorMessage('Creating Github issue failed.');
-		return { status: 406, html_url: null };
-	}
-
-	onSuccess();
-
-	const decision = await window.showInformationMessage(
-		'Github issue is successfully created.',
-		'See issue in Github',
-	);
-	const { html_url } = validation.right;
-	if (decision === 'See issue in Github') {
-		commands.executeCommand('codemod.redirect', html_url);
-	}
-	return {
-		status: 200,
-		html_url,
-	};
+  // call API to create Github Issue
+  const codemodRegistryRepoUrl = "https://github.com/codemod-com/codemod";
+
+  const response = await extendedFetch(
+    "https://backend.codemod.com/sourceControl/github/issues",
+    {
+      method: "POST",
+      headers: { Authorization: `Bearer ${accessToken}` },
+      body: JSON.stringify({
+        title,
+        body,
+        repoUrl: codemodRegistryRepoUrl,
+      }),
+    },
+  );
+  if (response.status !== 200) {
+    await onFail();
+    return { status: response.status, html_url: null };
+  }
+
+  const data = (await response.json()) as any;
+
+  const validation = createIssueResponseCodec.decode(data);
+
+  if (validation._tag === "Left") {
+    await onFail();
+    window.showErrorMessage("Creating Github issue failed.");
+    return { status: 406, html_url: null };
+  }
+
+  onSuccess();
+
+  const decision = await window.showInformationMessage(
+    "Github issue is successfully created.",
+    "See issue in Github",
+  );
+  const { html_url } = validation.right;
+  if (decision === "See issue in Github") {
+    commands.executeCommand("codemod.redirect", html_url);
+  }
+  return {
+    status: 200,
+    html_url,
+  };
 };
 
 export class MainViewProvider implements WebviewViewProvider {
-	private __view: WebviewView | null = null;
-	private __webviewResolver: WebviewResolver;
-	private __executionQueue: ReadonlyArray<CodemodHash> = [];
-	private __directoryPaths: ReadonlyArray<string> | null = null;
-	// true by default to prevent banner blinking on load
-	private __codemodEngineNodeLocated = true;
-
-	constructor(
-		context: ExtensionContext,
-		private readonly __engineService: EngineService,
-		private readonly __messageBus: MessageBus,
-		private readonly __rootUri: Uri | null,
-		private readonly __store: Store,
-	) {
-		this.__webviewResolver = new WebviewResolver(context.extensionUri);
-
-		this.__messageBus.subscribe(MessageKind.showProgress, (message) => {
-			if (message.codemodHash === null) {
-				return;
-			}
-
-			this.__postMessage({
-				kind: 'webview.global.setCodemodExecutionProgress',
-				codemodHash: message.codemodHash,
-				progressKind: message.progressKind,
-				totalFileNumber: message.totalFileNumber,
-				processedFileNumber: message.processedFileNumber,
-			});
-		});
-
-		this.__messageBus.subscribe(MessageKind.codemodSetExecuted, () => {
-			this.__postMessage({
-				kind: 'webview.global.codemodExecutionHalted',
-			});
-		});
-
-		this.__messageBus.subscribe(MessageKind.executeCodemodSet, () => {
-			this.__store.dispatch(actions.collapseResultsPanel(false));
-			this.__store.dispatch(actions.collapseChangeExplorerPanel(false));
-		});
-
-		this.__messageBus.subscribe(
-			MessageKind.executionQueueChange,
-			(message) => {
-				this.__executionQueue = message.queuedCodemodHashes;
-				const props = this.__buildProps();
-
-				this.__postMessage({
-					kind: 'webview.main.setProps',
-					props: props,
-				});
-			},
-		);
-
-		this.__messageBus.subscribe(
-			MessageKind.codemodEngineNodeLocated,
-			({ codemodEngineNodeLocated }) => {
-				if (
-					this.__codemodEngineNodeLocated === codemodEngineNodeLocated
-				) {
-					return;
-				}
-
-				this.__codemodEngineNodeLocated = codemodEngineNodeLocated;
-
-				const props = this.__buildProps();
-
-				this.__postMessage({
-					kind: 'webview.main.setProps',
-					props,
-				});
-			},
-		);
-
-		let prevProps = this.__buildProps();
-
-		this.__store.subscribe(async () => {
-			if (this.__directoryPaths === null) {
-				await this.__getDirectoryPaths();
-			}
-
-			const nextProps = this.__buildProps();
-			if (areEqual(prevProps, nextProps)) {
-				return;
-			}
-
-			prevProps = nextProps;
-
-			this.__postMessage({
-				kind: 'webview.main.setProps',
-				props: nextProps,
-			});
-		});
-	}
-
-	public isVisible(): boolean {
-		return this.__view?.visible ?? false;
-	}
-
-	public resolveWebviewView(webviewView: WebviewView): void | Thenable<void> {
-		this.__resolveWebview(webviewView);
-
-		this.__view = webviewView;
-
-		this.__view.webview.onDidReceiveMessage(this.__onDidReceiveMessage);
-
-		this.__messageBus.publish({
-			kind: MessageKind.mainWebviewViewVisibilityChange,
-		});
-
-		this.__view.onDidChangeVisibility(() => {
-			this.__messageBus.publish({
-				kind: MessageKind.mainWebviewViewVisibilityChange,
-			});
-
-			if (this.__view?.visible) {
-				this.__resolveWebview(this.__view);
-			}
-		});
-	}
-
-	private async __getDirectoryPaths() {
-		if (this.__rootUri === null) {
-			return;
-		}
-
-		const globPattern = buildGlobPattern(this.__rootUri, '/**');
-
-		// From `glob` documentation:
-		// (Note: to match only directories, put a / at the end of the pattern.)
-		const directoryPaths = await glob(`${globPattern}/`, {
-			// ignore node_modules and files, match only directories
-			ignore: ['**/node_modules/**'],
-			follow: false,
-			maxDepth: 10,
-		});
-
-		const MAX_NUMBER_OF_DIRECTORIES = 10000;
-
-		this.__directoryPaths = directoryPaths.slice(
-			0,
-			MAX_NUMBER_OF_DIRECTORIES,
-		);
-	}
-
-	private __postMessage(message: WebviewMessage) {
-		this.__view?.webview.postMessage(message);
-	}
-
-	private __resolveWebview(webviewView: WebviewView) {
-		this.__webviewResolver.resolveWebview(
-			webviewView.webview,
-			'main',
-			JSON.stringify(this.__buildProps()),
-			'mainWebviewViewProps',
-		);
-	}
-
-	private __buildProps() {
-		return selectMainWebviewViewProps(
-			this.__store.getState(),
-			this.__rootUri,
-			this.__directoryPaths,
-			this.__executionQueue,
-			this.__codemodEngineNodeLocated,
-		);
-	}
-
-	private __onDidReceiveMessage = async (message: WebviewResponse) => {
-		if (message.kind === 'webview.command') {
-			commands.executeCommand(
-				message.value.command,
-				...(message.value.arguments ?? []),
-			);
-		}
-
-		if (message.kind === 'webview.campaignManager.setSelectedCaseHash') {
-			this.__store.dispatch(
-				actions.setSelectedCaseHash(message.caseHash),
-			);
-		}
-
-		if (message.kind === 'webview.global.discardSelected') {
-			commands.executeCommand(
-				'codemod.discardJobs',
-				message.caseHashDigest,
-			);
-		}
-
-		if (message.kind === 'webview.global.showInformationMessage') {
-			window.showInformationMessage(message.value);
-		}
-
-		if (message.kind === 'webview.global.applySelected') {
-			commands.executeCommand(
-				'codemod.sourceControl.saveStagedJobsToTheFileSystem',
-				message.caseHashDigest,
-			);
-		}
-
-		if (message.kind === 'webview.main.setActiveTabId') {
-			this.__store.dispatch(actions.setActiveTabId(message.activeTabId));
-		}
-
-		if (
-			message.kind ===
-			'webview.main.setCodemodDiscoveryPanelGroupSettings'
-		) {
-			this.__store.dispatch(
-				actions.setCodemodDiscoveryPanelGroupSettings(
-					message.panelGroupSettings,
-				),
-			);
-		}
-
-		if (message.kind === 'webview.main.setCodemodRunsPanelGroupSettings') {
-			this.__store.dispatch(
-				actions.setCodemodRunsPanelGroupSettings(
-					message.panelGroupSettings,
-				),
-			);
-		}
-
-		if (message.kind === 'webview.main.setToaster') {
-			this.__store.dispatch(actions.setToaster(message.value));
-		}
-
-		if (message.kind === 'webview.global.flipSelectedExplorerNode') {
-			this.__store.dispatch(
-				actions.flipSelectedExplorerNode([
-					message.caseHashDigest,
-					message.explorerNodeHashDigest,
-				]),
-			);
-		}
-
-		if (message.kind === 'webview.global.flipCollapsibleExplorerNode') {
-			this.__store.dispatch(
-				actions.flipCollapsibleExplorerNode([
-					message.caseHashDigest,
-					message.explorerNodeHashDigest,
-				]),
-			);
-		}
-
-		if (message.kind === 'webview.global.focusExplorerNode') {
-			this.__store.dispatch(
-				actions.focusExplorerNode([
-					message.caseHashDigest,
-					message.explorerNodeHashDigest,
-				]),
-			);
-		}
-
-		if (message.kind === 'webview.global.setChangeExplorerSearchPhrase') {
-			this.__store.dispatch(
-				actions.setChangeExplorerSearchPhrase([
-					message.caseHashDigest,
-					message.searchPhrase,
-				]),
-			);
-		}
-
-		if (message.kind === 'webview.codemodList.haltCodemodExecution') {
-			this.__engineService.shutdownEngines();
-		}
-
-		if (message.kind === 'webview.codemodList.dryRunCodemod') {
-			if (this.__rootUri === null) {
-				window.showWarningMessage('No active workspace is found.');
-				return;
-			}
-
-			const hashDigest = message.value;
-			this.__store.dispatch(actions.setRecentCodemodHashes(hashDigest));
-
-			const state = this.__store.getState().codemodDiscoveryView;
-			const executionPath =
-				state.executionPaths[hashDigest] ?? this.__rootUri.fsPath;
-
-			if (executionPath === null) {
-				return;
-			}
-
-			const uri = Uri.file(executionPath);
-
-			// if missing some required arguments, open arguments popup
-
-			const argumentsSpecified = selectCodemodArguments(
-				this.__store.getState(),
-				hashDigest as unknown as CodemodNodeHashDigest,
-			).every(
-				({ required, value }) =>
-					!required ||
-					(isNeitherNullNorUndefined(value) && value !== ''),
-			);
-
-			if (!argumentsSpecified) {
-				this.__store.dispatch(
-					actions.setCodemodArgumentsPopupHashDigest(
-						hashDigest as unknown as CodemodNodeHashDigest,
-					),
-				);
-				return;
-			}
-
-			commands.executeCommand('codemod.executeCodemod', uri, hashDigest);
-		}
-
-		if (message.kind === 'webview.codemodList.updatePathToExecute') {
-			await this.updateExecutionPath(message.value);
-
-			this.__postMessage({
-				kind: 'webview.main.setProps',
-				props: this.__buildProps(),
-			});
-		}
-
-		if (message.kind === 'webview.global.showWarningMessage') {
-			window.showWarningMessage(message.value);
-		}
-
-		if (message.kind === 'webview.global.flipCodemodHashDigest') {
-			this.__store.dispatch(
-				actions.flipCodemodHashDigest(message.codemodNodeHashDigest),
-			);
-		}
-
-		if (message.kind === 'webview.global.selectCodemodNodeHashDigest') {
-			this.__store.dispatch(
-				actions.setFocusedCodemodHashDigest(
-					message.selectedCodemodNodeHashDigest,
-				),
-			);
-		}
-
-		if (message.kind === 'webview.global.setCodemodSearchPhrase') {
-			this.__store.dispatch(
-				actions.setCodemodSearchPhrase(message.searchPhrase),
-			);
-		}
-
-		if (message.kind === 'webview.global.collapseResultsPanel') {
-			this.__store.dispatch(
-				actions.collapseResultsPanel(message.collapsed),
-			);
-		}
-
-		if (message.kind === 'webview.global.collapseChangeExplorerPanel') {
-			this.__store.dispatch(
-				actions.collapseChangeExplorerPanel(message.collapsed),
-			);
-		}
-
-		if (
-			message.kind === 'webview.global.setCodemodArgumentsPopupHashDigest'
-		) {
-			this.__store.dispatch(
-				actions.setCodemodArgumentsPopupHashDigest(message.hashDigest),
-			);
-		}
-
-		if (message.kind === 'webview.global.setCodemodArgument') {
-			this.__store.dispatch(
-				actions.setCodemodArgument({
-					hashDigest: message.hashDigest,
-					name: message.name,
-					value: message.value,
-				}),
-			);
-		}
-	};
-
-	public updateExecutionPath = async ({
-		newPath,
-		codemodHash,
-		errorMessage,
-		warningMessage,
-		revertToPrevExecutionIfInvalid,
-		fromVSCodeCommand,
-	}: {
-		newPath: string;
-		codemodHash: CodemodHash;
-		errorMessage: string | null;
-		warningMessage: string | null;
-		revertToPrevExecutionIfInvalid: boolean;
-		fromVSCodeCommand?: boolean;
-	}) => {
-		if (this.__rootUri === null) {
-			window.showWarningMessage('No active workspace is found.');
-			return;
-		}
-
-		const state = this.__store.getState().codemodDiscoveryView;
-		const persistedExecutionPath = state.executionPaths[codemodHash];
-
-		const oldExecutionPath = persistedExecutionPath ?? null;
-		const newPathAbsolute = relativeToAbsolutePath(
-			newPath,
-			this.__rootUri.fsPath,
-		);
-
-		try {
-			await workspace.fs.stat(Uri.file(newPathAbsolute));
-			this.__store.dispatch(
-				actions.setExecutionPath({
-					codemodHash,
-					path: newPathAbsolute,
-				}),
-			);
-
-			if (!fromVSCodeCommand) {
-				window.showInformationMessage(
-					'Successfully updated the execution path.',
-				);
-			}
-		} catch (e) {
-			if (errorMessage !== null) {
-				window.showErrorMessage(errorMessage);
-			}
-			if (warningMessage !== null) {
-				window.showWarningMessage(warningMessage);
-			}
-
-			if (oldExecutionPath === null) {
-				return;
-			}
-
-			if (revertToPrevExecutionIfInvalid) {
-				this.__store.dispatch(
-					actions.setExecutionPath({
-						codemodHash,
-						path: oldExecutionPath,
-					}),
-				);
-			} else {
-				this.__store.dispatch(
-					actions.setExecutionPath({
-						codemodHash,
-						path: oldExecutionPath,
-					}),
-				);
-			}
-		}
-	};
+  private __view: WebviewView | null = null;
+  private __webviewResolver: WebviewResolver;
+  private __executionQueue: ReadonlyArray<CodemodHash> = [];
+  private __directoryPaths: ReadonlyArray<string> | null = null;
+  // true by default to prevent banner blinking on load
+  private __codemodEngineNodeLocated = true;
+
+  constructor(
+    context: ExtensionContext,
+    private readonly __engineService: EngineService,
+    private readonly __messageBus: MessageBus,
+    private readonly __rootUri: Uri | null,
+    private readonly __store: Store,
+  ) {
+    this.__webviewResolver = new WebviewResolver(context.extensionUri);
+
+    this.__messageBus.subscribe(MessageKind.showProgress, (message) => {
+      if (message.codemodHash === null) {
+        return;
+      }
+
+      this.__postMessage({
+        kind: "webview.global.setCodemodExecutionProgress",
+        codemodHash: message.codemodHash,
+        progressKind: message.progressKind,
+        totalFileNumber: message.totalFileNumber,
+        processedFileNumber: message.processedFileNumber,
+      });
+    });
+
+    this.__messageBus.subscribe(MessageKind.codemodSetExecuted, () => {
+      this.__postMessage({
+        kind: "webview.global.codemodExecutionHalted",
+      });
+    });
+
+    this.__messageBus.subscribe(MessageKind.executeCodemodSet, () => {
+      this.__store.dispatch(actions.collapseResultsPanel(false));
+      this.__store.dispatch(actions.collapseChangeExplorerPanel(false));
+    });
+
+    this.__messageBus.subscribe(MessageKind.executionQueueChange, (message) => {
+      this.__executionQueue = message.queuedCodemodHashes;
+      const props = this.__buildProps();
+
+      this.__postMessage({
+        kind: "webview.main.setProps",
+        props: props,
+      });
+    });
+
+    this.__messageBus.subscribe(
+      MessageKind.codemodEngineNodeLocated,
+      ({ codemodEngineNodeLocated }) => {
+        if (this.__codemodEngineNodeLocated === codemodEngineNodeLocated) {
+          return;
+        }
+
+        this.__codemodEngineNodeLocated = codemodEngineNodeLocated;
+
+        const props = this.__buildProps();
+
+        this.__postMessage({
+          kind: "webview.main.setProps",
+          props,
+        });
+      },
+    );
+
+    let prevProps = this.__buildProps();
+
+    this.__store.subscribe(async () => {
+      if (this.__directoryPaths === null) {
+        await this.__getDirectoryPaths();
+      }
+
+      const nextProps = this.__buildProps();
+      if (areEqual(prevProps, nextProps)) {
+        return;
+      }
+
+      prevProps = nextProps;
+
+      this.__postMessage({
+        kind: "webview.main.setProps",
+        props: nextProps,
+      });
+    });
+  }
+
+  public isVisible(): boolean {
+    return this.__view?.visible ?? false;
+  }
+
+  public resolveWebviewView(webviewView: WebviewView): void | Thenable<void> {
+    this.__resolveWebview(webviewView);
+
+    this.__view = webviewView;
+
+    this.__view.webview.onDidReceiveMessage(this.__onDidReceiveMessage);
+
+    this.__messageBus.publish({
+      kind: MessageKind.mainWebviewViewVisibilityChange,
+    });
+
+    this.__view.onDidChangeVisibility(() => {
+      this.__messageBus.publish({
+        kind: MessageKind.mainWebviewViewVisibilityChange,
+      });
+
+      if (this.__view?.visible) {
+        this.__resolveWebview(this.__view);
+      }
+    });
+  }
+
+  private async __getDirectoryPaths() {
+    if (this.__rootUri === null) {
+      return;
+    }
+
+    const globPattern = buildGlobPattern(this.__rootUri, "/**");
+
+    // From `glob` documentation:
+    // (Note: to match only directories, put a / at the end of the pattern.)
+    const directoryPaths = await glob(`${globPattern}/`, {
+      // ignore node_modules and files, match only directories
+      ignore: ["**/node_modules/**"],
+      follow: false,
+      maxDepth: 10,
+    });
+
+    const MAX_NUMBER_OF_DIRECTORIES = 10000;
+
+    this.__directoryPaths = directoryPaths.slice(0, MAX_NUMBER_OF_DIRECTORIES);
+  }
+
+  private __postMessage(message: WebviewMessage) {
+    this.__view?.webview.postMessage(message);
+  }
+
+  private __resolveWebview(webviewView: WebviewView) {
+    this.__webviewResolver.resolveWebview(
+      webviewView.webview,
+      "main",
+      JSON.stringify(this.__buildProps()),
+      "mainWebviewViewProps",
+    );
+  }
+
+  private __buildProps() {
+    return selectMainWebviewViewProps(
+      this.__store.getState(),
+      this.__rootUri,
+      this.__directoryPaths,
+      this.__executionQueue,
+      this.__codemodEngineNodeLocated,
+    );
+  }
+
+  private __onDidReceiveMessage = async (message: WebviewResponse) => {
+    if (message.kind === "webview.command") {
+      commands.executeCommand(
+        message.value.command,
+        ...(message.value.arguments ?? []),
+      );
+    }
+
+    if (message.kind === "webview.campaignManager.setSelectedCaseHash") {
+      this.__store.dispatch(actions.setSelectedCaseHash(message.caseHash));
+    }
+
+    if (message.kind === "webview.global.discardSelected") {
+      commands.executeCommand("codemod.discardJobs", message.caseHashDigest);
+    }
+
+    if (message.kind === "webview.global.showInformationMessage") {
+      window.showInformationMessage(message.value);
+    }
+
+    if (message.kind === "webview.global.applySelected") {
+      commands.executeCommand(
+        "codemod.sourceControl.saveStagedJobsToTheFileSystem",
+        message.caseHashDigest,
+      );
+    }
+
+    if (message.kind === "webview.main.setActiveTabId") {
+      this.__store.dispatch(actions.setActiveTabId(message.activeTabId));
+    }
+
+    if (message.kind === "webview.main.setCodemodDiscoveryPanelGroupSettings") {
+      this.__store.dispatch(
+        actions.setCodemodDiscoveryPanelGroupSettings(
+          message.panelGroupSettings,
+        ),
+      );
+    }
+
+    if (message.kind === "webview.main.setCodemodRunsPanelGroupSettings") {
+      this.__store.dispatch(
+        actions.setCodemodRunsPanelGroupSettings(message.panelGroupSettings),
+      );
+    }
+
+    if (message.kind === "webview.main.setToaster") {
+      this.__store.dispatch(actions.setToaster(message.value));
+    }
+
+    if (message.kind === "webview.global.flipSelectedExplorerNode") {
+      this.__store.dispatch(
+        actions.flipSelectedExplorerNode([
+          message.caseHashDigest,
+          message.explorerNodeHashDigest,
+        ]),
+      );
+    }
+
+    if (message.kind === "webview.global.flipCollapsibleExplorerNode") {
+      this.__store.dispatch(
+        actions.flipCollapsibleExplorerNode([
+          message.caseHashDigest,
+          message.explorerNodeHashDigest,
+        ]),
+      );
+    }
+
+    if (message.kind === "webview.global.focusExplorerNode") {
+      this.__store.dispatch(
+        actions.focusExplorerNode([
+          message.caseHashDigest,
+          message.explorerNodeHashDigest,
+        ]),
+      );
+    }
+
+    if (message.kind === "webview.global.setChangeExplorerSearchPhrase") {
+      this.__store.dispatch(
+        actions.setChangeExplorerSearchPhrase([
+          message.caseHashDigest,
+          message.searchPhrase,
+        ]),
+      );
+    }
+
+    if (message.kind === "webview.codemodList.haltCodemodExecution") {
+      this.__engineService.shutdownEngines();
+    }
+
+    if (message.kind === "webview.codemodList.dryRunCodemod") {
+      if (this.__rootUri === null) {
+        window.showWarningMessage("No active workspace is found.");
+        return;
+      }
+
+      const hashDigest = message.value;
+      this.__store.dispatch(actions.setRecentCodemodHashes(hashDigest));
+
+      const state = this.__store.getState().codemodDiscoveryView;
+      const executionPath =
+        state.executionPaths[hashDigest] ?? this.__rootUri.fsPath;
+
+      if (executionPath === null) {
+        return;
+      }
+
+      const uri = Uri.file(executionPath);
+
+      // if missing some required arguments, open arguments popup
+
+      const argumentsSpecified = selectCodemodArguments(
+        this.__store.getState(),
+        hashDigest as unknown as CodemodNodeHashDigest,
+      ).every(
+        ({ required, value }) =>
+          !required || (isNeitherNullNorUndefined(value) && value !== ""),
+      );
+
+      if (!argumentsSpecified) {
+        this.__store.dispatch(
+          actions.setCodemodArgumentsPopupHashDigest(
+            hashDigest as unknown as CodemodNodeHashDigest,
+          ),
+        );
+        return;
+      }
+
+      commands.executeCommand("codemod.executeCodemod", uri, hashDigest);
+    }
+
+    if (message.kind === "webview.codemodList.updatePathToExecute") {
+      await this.updateExecutionPath(message.value);
+
+      this.__postMessage({
+        kind: "webview.main.setProps",
+        props: this.__buildProps(),
+      });
+    }
+
+    if (message.kind === "webview.global.showWarningMessage") {
+      window.showWarningMessage(message.value);
+    }
+
+    if (message.kind === "webview.global.flipCodemodHashDigest") {
+      this.__store.dispatch(
+        actions.flipCodemodHashDigest(message.codemodNodeHashDigest),
+      );
+    }
+
+    if (message.kind === "webview.global.selectCodemodNodeHashDigest") {
+      this.__store.dispatch(
+        actions.setFocusedCodemodHashDigest(
+          message.selectedCodemodNodeHashDigest,
+        ),
+      );
+    }
+
+    if (message.kind === "webview.global.setCodemodSearchPhrase") {
+      this.__store.dispatch(
+        actions.setCodemodSearchPhrase(message.searchPhrase),
+      );
+    }
+
+    if (message.kind === "webview.global.collapseResultsPanel") {
+      this.__store.dispatch(actions.collapseResultsPanel(message.collapsed));
+    }
+
+    if (message.kind === "webview.global.collapseChangeExplorerPanel") {
+      this.__store.dispatch(
+        actions.collapseChangeExplorerPanel(message.collapsed),
+      );
+    }
+
+    if (message.kind === "webview.global.setCodemodArgumentsPopupHashDigest") {
+      this.__store.dispatch(
+        actions.setCodemodArgumentsPopupHashDigest(message.hashDigest),
+      );
+    }
+
+    if (message.kind === "webview.global.setCodemodArgument") {
+      this.__store.dispatch(
+        actions.setCodemodArgument({
+          hashDigest: message.hashDigest,
+          name: message.name,
+          value: message.value,
+        }),
+      );
+    }
+  };
+
+  public updateExecutionPath = async ({
+    newPath,
+    codemodHash,
+    errorMessage,
+    warningMessage,
+    revertToPrevExecutionIfInvalid,
+    fromVSCodeCommand,
+  }: {
+    newPath: string;
+    codemodHash: CodemodHash;
+    errorMessage: string | null;
+    warningMessage: string | null;
+    revertToPrevExecutionIfInvalid: boolean;
+    fromVSCodeCommand?: boolean;
+  }) => {
+    if (this.__rootUri === null) {
+      window.showWarningMessage("No active workspace is found.");
+      return;
+    }
+
+    const state = this.__store.getState().codemodDiscoveryView;
+    const persistedExecutionPath = state.executionPaths[codemodHash];
+
+    const oldExecutionPath = persistedExecutionPath ?? null;
+    const newPathAbsolute = relativeToAbsolutePath(
+      newPath,
+      this.__rootUri.fsPath,
+    );
+
+    try {
+      await workspace.fs.stat(Uri.file(newPathAbsolute));
+      this.__store.dispatch(
+        actions.setExecutionPath({
+          codemodHash,
+          path: newPathAbsolute,
+        }),
+      );
+
+      if (!fromVSCodeCommand) {
+        window.showInformationMessage(
+          "Successfully updated the execution path.",
+        );
+      }
+    } catch (e) {
+      if (errorMessage !== null) {
+        window.showErrorMessage(errorMessage);
+      }
+      if (warningMessage !== null) {
+        window.showWarningMessage(warningMessage);
+      }
+
+      if (oldExecutionPath === null) {
+        return;
+      }
+
+      if (revertToPrevExecutionIfInvalid) {
+        this.__store.dispatch(
+          actions.setExecutionPath({
+            codemodHash,
+            path: oldExecutionPath,
+          }),
+        );
+      } else {
+        this.__store.dispatch(
+          actions.setExecutionPath({
+            codemodHash,
+            path: oldExecutionPath,
+          }),
+        );
+      }
+    }
+  };
 }
diff --git a/apps/vsce/src/fetch/index.ts b/apps/vsce/src/fetch/index.ts
new file mode 100644
index 000000000..4c6cff743
--- /dev/null
+++ b/apps/vsce/src/fetch/index.ts
@@ -0,0 +1,23 @@
+import { FetchError, extendedFetch } from "@codemod-com/utilities";
+
+export const retryingClient = async (
+  url: string,
+  options?: RequestInit & { retries?: number },
+) => {
+  let retryCount = options?.retries ?? DEFAULT_RETRY_COUNT;
+  while (retryCount > 0) {
+    try {
+      const response = await extendedFetch(url, options);
+      return response;
+    } catch (err) {
+      retryCount -= 1;
+      if (retryCount === 0) {
+        throw err;
+      }
+    }
+  }
+
+  throw new FetchError("Failed to fetch");
+};
+
+export const DEFAULT_RETRY_COUNT = 5;
diff --git a/apps/vsce/test/dowloadService.test.ts b/apps/vsce/test/dowloadService.test.ts
index a66a6ab11..9f74ff70c 100644
--- a/apps/vsce/test/dowloadService.test.ts
+++ b/apps/vsce/test/dowloadService.test.ts
@@ -1,8 +1,7 @@
-import { AxiosError, type AxiosInstance } from "axios";
+import { FetchError } from "@codemod-com/utilities";
 import nock from "nock";
 import { afterEach, describe, expect, test, vi } from "vitest";
 import type { FileSystem } from "vscode";
-import { retryingClient as axiosInstance } from "../src/axios";
 import { DownloadService } from "../src/components/downloadService";
 
 const mockedFileSystemUtilities = {
@@ -21,7 +20,7 @@ const downloadService = new DownloadService(
   mockedFileSystemUtilities,
 );
 
-const NETWORK_ERROR = new AxiosError("Some connection error");
+const NETWORK_ERROR = new FetchError("Some connection error");
 NETWORK_ERROR.code = "ECONNRESET";
 
 // 3 failed responses, then good response
@@ -39,28 +38,16 @@ const responses = [
   () => nock("https://test.com").get("/test").reply(200, "Test"),
 ];
 
-const setupResponses = (
-  client: AxiosInstance,
-  responses: Array<() => void>,
-) => {
-  const configureResponse = () => {
+const originalFetch = global.fetch;
+global.fetch = vi
+  .fn()
+  .mockImplementation((url: string, options?: RequestInit) => {
     const response = responses.shift();
     if (response) {
       response();
     }
-  };
-
-  client.interceptors.request.use(
-    (config) => {
-      configureResponse();
-      return config;
-    },
-    (error) => {
-      configureResponse();
-      return Promise.reject(error);
-    },
-  );
-};
+    return originalFetch(url, options);
+  });
 
 describe("DownloadService", () => {
   afterEach(() => {
@@ -69,8 +56,6 @@ describe("DownloadService", () => {
   });
 
   test("Should retry 3 times if request fails", async () => {
-    setupResponses(axiosInstance, responses);
-
     await downloadService.downloadFileIfNeeded(
       "https://test.com/test",
       // @ts-expect-error passing a string instead of URI, because URI cannot be imported from vscode
diff --git a/apps/vsce/tsconfig.json b/apps/vsce/tsconfig.json
index db7635554..740ec38b1 100644
--- a/apps/vsce/tsconfig.json
+++ b/apps/vsce/tsconfig.json
@@ -1,5 +1,5 @@
 {
   "include": ["./src/**/*.ts", "./src/types/**/*.d.ts", "./test/**/*.ts"],
   "extends": "@codemod-com/tsconfig/extension.json",
-  "compilerOptions": { "lib": ["ES2021"] }
+  "compilerOptions": { "lib": ["ES2021", "DOM"] }
 }
diff --git a/packages/codemods/axios/fetch/src/index.ts b/packages/codemods/axios/fetch/src/index.ts
index 9e5c42958..1428c58a5 100644
--- a/packages/codemods/axios/fetch/src/index.ts
+++ b/packages/codemods/axios/fetch/src/index.ts
@@ -25,6 +25,10 @@ export async function workflow({ files }: Api) {
     { pattern: "axios.$_($$$).$_($$$)" }, // axios.get(...).then(...)
     { pattern: "axios.$_($$$).$_($$$).$_($$$)" }, // axios.get(...).then(...).catch(...)
     { pattern: "axios.$_($$$).$_($$$).$_($$$).$_($$$)" }, // axios.get(...).then(...).catch(...).finally(...)
+    { pattern: "axios.$_<$_>($$$)" }, // axios.get(...)
+    { pattern: "axios.$_<$_>($$$).$_($$$)" }, // axios.get(...).then(...)
+    { pattern: "axios.$_<$_>($$$).$_($$$).$_($$$)" }, // axios.get(...).then(...).catch(...)
+    { pattern: "axios.$_<$_>($$$).$_($$$).$_($$$).$_($$$)" }, // axios.get(...).then(...).catch(...).finally(...)
   ];
 
   const extendAxiosPatterns = (extend: (pattern: string) => string) =>
diff --git a/packages/runner/package.json b/packages/runner/package.json
index 4cd76e07b..f5eff32cf 100644
--- a/packages/runner/package.json
+++ b/packages/runner/package.json
@@ -32,8 +32,8 @@
     "vitest": "^1.0.1"
   },
   "dependencies": {
-    "@ast-grep/cli": "^0.22.3",
-    "@ast-grep/napi": "^0.22.3",
+    "@ast-grep/cli": "^0.24.0",
+    "@ast-grep/napi": "^0.24.0",
     "@babel/core": "^7.24.4",
     "@babel/parser": "^7.24.4",
     "@babel/preset-env": "^7.24.1",
diff --git a/packages/utilities/src/fetch.ts b/packages/utilities/src/fetch.ts
new file mode 100644
index 000000000..1f340fd55
--- /dev/null
+++ b/packages/utilities/src/fetch.ts
@@ -0,0 +1,45 @@
+const globalHooks: ((options: RequestInit) => RequestInit)[] = [];
+
+export class FetchError extends Error {
+  public code?: string;
+
+  constructor(
+    message: string,
+    public response?: Response,
+  ) {
+    super(message);
+  }
+}
+
+export function isFetchError(error: unknown): error is FetchError {
+  return error instanceof FetchError;
+}
+
+export const addGlobalHook = (hook: (options: RequestInit) => RequestInit) => {
+  globalHooks.push(hook);
+};
+
+export const extendedFetch = async (
+  url: string,
+  initialOptions: RequestInit = {},
+) => {
+  let options = initialOptions;
+  try {
+    for (const hook of globalHooks) {
+      options = hook(options);
+    }
+    const response = await fetch(url, options);
+
+    if (!response.ok) {
+      throw new FetchError("Failed to fetch", response);
+    }
+
+    return response;
+  } catch (e) {
+    if (isFetchError(e)) {
+      throw e;
+    }
+
+    throw new FetchError("Failed to fetch");
+  }
+};
diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts
index d97a6f3ed..d37dcaa22 100644
--- a/packages/utilities/src/index.ts
+++ b/packages/utilities/src/index.ts
@@ -107,3 +107,4 @@ export { CaseReadingService } from "./services/case/caseReadingService.js";
 export { CaseWritingService } from "./services/case/caseWritingService.js";
 export { FileWatcher } from "./services/case/fileWatcher.js";
 export { TarService } from "./services/tar.js";
+export * from "./fetch.js";
diff --git a/packages/utilities/tsconfig.json b/packages/utilities/tsconfig.json
index 19d757df2..ec62541a4 100644
--- a/packages/utilities/tsconfig.json
+++ b/packages/utilities/tsconfig.json
@@ -4,6 +4,7 @@
   "compilerOptions": {
     "module": "NodeNext",
     "moduleResolution": "nodenext",
+    "lib": ["ES2021", "DOM"],
     "types": ["node"],
     "baseUrl": ".",
     "target": "es2021",
diff --git a/packages/workflow/package.json b/packages/workflow/package.json
index a07373d57..d205e2e47 100644
--- a/packages/workflow/package.json
+++ b/packages/workflow/package.json
@@ -30,8 +30,8 @@
     "directory": "packages/workflow"
   },
   "dependencies": {
-    "@ast-grep/cli": "^0.22.3",
-    "@ast-grep/napi": "^0.22.3",
+    "@ast-grep/cli": "^0.24.0",
+    "@ast-grep/napi": "^0.24.0",
     "@sindresorhus/slugify": "^2.2.1",
     "colors-cli": "^1.0.33",
     "filenamify": "^6.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 334c8a7f9..743dfd8e9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -75,9 +75,6 @@ importers:
       '@fastify/rate-limit':
         specifier: 9.0.1
         version: 9.0.1
-      axios:
-        specifier: ^1.6.8
-        version: 1.6.8
       dotenv:
         specifier: ^16.4.5
         version: 16.4.5
@@ -148,9 +145,6 @@ importers:
       ai:
         specifier: 2.2.29
         version: 2.2.29(react@18.2.0)(solid-js@1.8.17)(svelte@4.2.15)(vue@3.4.25(typescript@5.3.3))
-      axios:
-        specifier: ^1.6.8
-        version: 1.6.8
       bullmq:
         specifier: ^5.7.5
         version: 5.7.5
@@ -267,11 +261,11 @@ importers:
   apps/cli:
     dependencies:
       '@ast-grep/cli':
-        specifier: ^0.22.3
-        version: 0.22.4
+        specifier: ^0.24.0
+        version: 0.24.0
       '@ast-grep/napi':
-        specifier: ^0.22.3
-        version: 0.22.4
+        specifier: ^0.24.0
+        version: 0.24.0
       esbuild:
         specifier: ^0.17.14
         version: 0.17.19
@@ -321,9 +315,6 @@ importers:
       '@vitest/coverage-v8':
         specifier: ^1.0.1
         version: 1.5.1(vitest@1.5.1(@types/node@18.11.9)(jsdom@23.2.0)(terser@5.30.4))
-      axios:
-        specifier: ^1.6.8
-        version: 1.6.8
       columnify:
         specifier: ^1.6.0
         version: 1.6.0
@@ -513,9 +504,6 @@ importers:
       ast-types:
         specifier: ^0.14.2
         version: 0.14.2
-      axios:
-        specifier: ^1.6.8
-        version: 1.6.8
       change-case:
         specifier: ^5.2.0
         version: 5.4.4
@@ -838,9 +826,6 @@ importers:
       ai:
         specifier: 2.2.29
         version: 2.2.29(react@18.2.0)(solid-js@1.8.17)(svelte@4.2.15)(vue@3.4.25(typescript@4.9.5))
-      axios:
-        specifier: ^1.6.8
-        version: 1.6.8
       chatgpt:
         specifier: 5.2.5
         version: 5.2.5
@@ -884,9 +869,9 @@ importers:
 
   apps/task-manager:
     dependencies:
-      axios:
-        specifier: ^1.6.8
-        version: 1.6.8
+      '@codemod-com/utilities':
+        specifier: workspace:*
+        version: link:../../packages/utilities
       bullmq:
         specifier: ^5.7.2
         version: 5.7.5
@@ -942,12 +927,6 @@ importers:
       '@vscode/vsce':
         specifier: ^2.22.0
         version: 2.26.0
-      axios:
-        specifier: ^1.6.8
-        version: 1.6.8
-      axios-retry:
-        specifier: ^4.0.0
-        version: 4.1.0(axios@1.6.8)
       diff:
         specifier: ^5.1.0
         version: 5.2.0
@@ -979,8 +958,8 @@ importers:
         specifier: ^0.3.5
         version: 0.3.5(fp-ts@2.16.5)(monocle-ts@2.3.13(fp-ts@2.16.5))
       nock:
-        specifier: ^13.5.1
-        version: 13.5.4
+        specifier: beta
+        version: 14.0.0-beta.7
       redux-persist:
         specifier: ^6.0.0
         version: 6.0.0(react@18.2.0)(redux@4.2.1)
@@ -5032,11 +5011,11 @@ importers:
   packages/runner:
     dependencies:
       '@ast-grep/cli':
-        specifier: ^0.22.3
-        version: 0.22.4
+        specifier: ^0.24.0
+        version: 0.24.0
       '@ast-grep/napi':
-        specifier: ^0.22.3
-        version: 0.22.4
+        specifier: ^0.24.0
+        version: 0.24.0
       '@babel/core':
         specifier: ^7.24.4
         version: 7.24.4
@@ -5253,11 +5232,11 @@ importers:
   packages/workflow:
     dependencies:
       '@ast-grep/cli':
-        specifier: ^0.22.3
-        version: 0.22.4
+        specifier: ^0.24.0
+        version: 0.24.0
       '@ast-grep/napi':
-        specifier: ^0.22.3
-        version: 0.22.4
+        specifier: ^0.24.0
+        version: 0.24.0
       '@sindresorhus/slugify':
         specifier: ^2.2.1
         version: 2.2.1
@@ -5352,99 +5331,198 @@ packages:
     cpu: [arm64]
     os: [darwin]
 
+  '@ast-grep/cli-darwin-arm64@0.24.0':
+    resolution: {integrity: sha512-8YkcaYNzy970snexNS2+8O+Uk8wrmgUELFdoVcJMIdzeTppbzKbAypJkTrOxosCIOoO6Bq0vp8hvmZj04wXhlQ==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+
   '@ast-grep/cli-darwin-x64@0.22.4':
     resolution: {integrity: sha512-gKUznVB4WgNrKCSco1J1cJ7g9eeMTktFGFDVynH9DUECB/ZFSyIJ82SIkvfMBYpO5ToR0tK2p9O8Ze4biWkI5Q==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [darwin]
 
+  '@ast-grep/cli-darwin-x64@0.24.0':
+    resolution: {integrity: sha512-U7S2uZQmT4B79cVfvwd785yFj7ZF02hjhBhAuVQq81LPI8mE8NH/phmjoSoL1u1Olf3BxqUcBLFdM4zoLvryiw==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+
   '@ast-grep/cli-linux-arm64-gnu@0.22.4':
     resolution: {integrity: sha512-C6s+Ztx+IqKarvnykm/IfIsKW8VUA4g5EpJrFKul8C+qrwJm+35FRVDr4pKZ3R9ioB0P6G5XxMA04H1bYdTGjQ==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
 
+  '@ast-grep/cli-linux-arm64-gnu@0.24.0':
+    resolution: {integrity: sha512-NVn83DrcbBhrKwO3yNDdEPv05lr3TDq0pfDg8eEBfJQnZnvXliEYxJwyyzZ53867rLeqaPo4jvPHcIJKdKe0rw==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+
   '@ast-grep/cli-linux-x64-gnu@0.22.4':
     resolution: {integrity: sha512-ZcFvXr+yFHqXULdb9tNr8kjQwx+rA35UCq753sQxJ94VJ+jPvMLCzLOooFaEWOjZKiV3RNHjwQFIOd/O92yHcA==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
+  '@ast-grep/cli-linux-x64-gnu@0.24.0':
+    resolution: {integrity: sha512-OMVq58p2BsMVBoh/cdSDmmUiIA+78FAyZXYWZ8mS1BudU6Ba+15ZVqeId90Q4U9X+yEqIHyaz+X1CcLiygnQxg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
   '@ast-grep/cli-win32-arm64-msvc@0.22.4':
     resolution: {integrity: sha512-uLlfPP/cNy9DFdQHLdc61xwBBqRK3PIk066kxJYH27BE1FleHZMiQEO9o4of6dgstYDeoIN5EMgSpinYYdeMCA==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [win32]
 
+  '@ast-grep/cli-win32-arm64-msvc@0.24.0':
+    resolution: {integrity: sha512-02CE3PfvwEsKvTITBP+J3Dm87MCeZl+t0Q4vBhQhcMBQt7N7D9DPY5tm0mVcgioY0Fm1yGn2z7IG56Dd6Pmnmw==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [win32]
+
   '@ast-grep/cli-win32-ia32-msvc@0.22.4':
     resolution: {integrity: sha512-0Ct+4iUf1yj2gtSC6dF0u4lszgak8mGs7aHpVhXGsygO3A29vJPX8P4zCP9AjKDJFUtxHb5sJG0HCoc/4XbUmA==}
     engines: {node: '>= 10'}
     cpu: [ia32]
     os: [win32]
 
+  '@ast-grep/cli-win32-ia32-msvc@0.24.0':
+    resolution: {integrity: sha512-JB0dFaPTf8JSuKsID4vV/FYGx9kDTxwvzSeNYXebmoEgiS7catiRWBmB4LSuJMvdd5RCSQskDRu733Qsyd4CoA==}
+    engines: {node: '>= 10'}
+    cpu: [ia32]
+    os: [win32]
+
   '@ast-grep/cli-win32-x64-msvc@0.22.4':
     resolution: {integrity: sha512-VHADVsljesHLB3MTPCE4a6UYZHkwzZSTbavRG8X4xdXWpaQMf4h7FyF5P/JkXB3zSJWkYs6oTtVSy78pcJ4IAQ==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [win32]
 
+  '@ast-grep/cli-win32-x64-msvc@0.24.0':
+    resolution: {integrity: sha512-73LDMKe/O+wg1dAVww104UsAHo7u4GBcC+qzHODqGGP1j3QtESyDYRrZfiJAHhkTRSjitVZrxJ7SZzRmbaJn4g==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [win32]
+
   '@ast-grep/cli@0.22.4':
     resolution: {integrity: sha512-SsV8gP8gdPjsb2ov9T0uZ+pmBllLXpsSgBuQpO8MjYHNJBoCzpK/lmrjFj+Fs9NeJvvRuZJrhu1GfhpX+bumpw==}
     engines: {node: '>= 12.0.0'}
     hasBin: true
 
+  '@ast-grep/cli@0.24.0':
+    resolution: {integrity: sha512-sK1/Ozd2HWPOqft+nZGNamO6hCBpdHbmZtiatdsO1Yhz3VRt7m9LSiKNZ7dUfFw9PXn0LRqyZk9SoHq222KcHw==}
+    engines: {node: '>= 12.0.0'}
+    hasBin: true
+
   '@ast-grep/napi-darwin-arm64@0.22.4':
     resolution: {integrity: sha512-q0F3tIqQh+zxNUTFr56vkxwh+f88rlnWyJYA1EjrtkQ6j4Yw/KgdMW3WfIxVEdglAJ3dcRg2w4uOXHTwnmfQIg==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [darwin]
 
+  '@ast-grep/napi-darwin-arm64@0.24.0':
+    resolution: {integrity: sha512-5cj+N60E9019uHsMTcN9vKA/fghNiqN77Sd0XKBg35hDJMc54R5n+kpZ8t3YvP5sN2Rhtf9UbIhznXhB8DPSug==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+
   '@ast-grep/napi-darwin-x64@0.22.4':
     resolution: {integrity: sha512-Ci5IQdlXua+XXQ7qjv5210Hm9lU2w5d7heYS09sOpB/Wnd1KhdZVbSSOnx9pnVbp4CDv3ADQy7FM6HBJGxoyXg==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [darwin]
 
+  '@ast-grep/napi-darwin-x64@0.24.0':
+    resolution: {integrity: sha512-AaZAFkjnQAZ9LxK6uOXRlJVZAQriO37+wyjPjLC7KSuxzcSUBtrV2mGNkHRfNO6GcVCLbNZkk6qOhFNrRVOIxw==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+
   '@ast-grep/napi-linux-arm64-gnu@0.22.4':
     resolution: {integrity: sha512-5U6iOrbIOY/MpGcQBJSS418kLZtqkTMY51EH0H+aRh1MB5mKgcpyUuIJnFxI22uvjI/ZUkgp7Mh+S+Y+OgvD/g==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
 
+  '@ast-grep/napi-linux-arm64-gnu@0.24.0':
+    resolution: {integrity: sha512-/CEVtWpeHZrFeoc5GBVZ0E70FULls1EKHze8r7stVTSkOhIsrFVOImxKlt98tXTM/zfS9BijVz41bHnTif2GUw==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+
   '@ast-grep/napi-linux-x64-gnu@0.22.4':
     resolution: {integrity: sha512-xG3OnyCQ4WTj1z2ir8dBZPUe0IvAexEcNwc2s81wxz1g2MB0TUt94VQF25wXtmgCm9v/3lNA/422nkALus88pA==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
+  '@ast-grep/napi-linux-x64-gnu@0.24.0':
+    resolution: {integrity: sha512-CyNMKz2yPb65uEIGwmBcBexweHdT7cyqYFVahTx4rZs+tHpnGVEKHwffQ1Kob+HCeeGZBUupDNXZqpWjp8q6Fw==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
   '@ast-grep/napi-linux-x64-musl@0.22.4':
     resolution: {integrity: sha512-f+/cUaRjxpZdqefe91ud+YYOLeIl6MwISXh7ao1ywA+7/qtlm1ds/dMHZtNvcl3cw9ujmI+enp8wIOd4nUakhw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
+  '@ast-grep/napi-linux-x64-musl@0.24.0':
+    resolution: {integrity: sha512-H3F7QG45Y9uXb3Kebe7RHDvFvbArNcBNG4qEpxq9T5eAyBWzbunMGC1oAZo6BgBJtO0Yhl3QyC2OvnuAB+4GWw==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
   '@ast-grep/napi-win32-arm64-msvc@0.22.4':
     resolution: {integrity: sha512-+ch/Z3/FJ67mbv2A1T8layLoH2NF/Lxv2Z9u4bdqQHqz/dXZTkYMbry7yIcRccbRsYlWbfVO8iOGDF+RdeiTAw==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [win32]
 
+  '@ast-grep/napi-win32-arm64-msvc@0.24.0':
+    resolution: {integrity: sha512-ZX0JQk7uZV+yaXVhn/mFSSf55JglYq0uAiXB53aAQCOyiKyaewl+tK4sPlcF1BpSh7D+SpP9GNIU4MAE8zB1aA==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [win32]
+
   '@ast-grep/napi-win32-ia32-msvc@0.22.4':
     resolution: {integrity: sha512-q1ElrjsPfrhItHnwolC16oTDjsh1tX2xirVVIAwmbK4+Wini3/6HDRpxOOIHr9QmkfGioabEJATZl7uQJiziUQ==}
     engines: {node: '>= 10'}
     cpu: [ia32]
     os: [win32]
 
+  '@ast-grep/napi-win32-ia32-msvc@0.24.0':
+    resolution: {integrity: sha512-rCOEY27Wu3K/lqHXy1Wg7HoScJuHvsRdmlmfSw19HSv4vR2MQVQx/wd0P3WKzqS8LeDr333kQKbJO57dgDYg5Q==}
+    engines: {node: '>= 10'}
+    cpu: [ia32]
+    os: [win32]
+
   '@ast-grep/napi-win32-x64-msvc@0.22.4':
     resolution: {integrity: sha512-Xk/0ZWA422+9osSCkdOTgHnub2UtuUNHMgPtsGDLslHzp0PhozXi2NV6st5OBEgOkwIeSyxoTtLTVZ56+lL1rw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [win32]
 
+  '@ast-grep/napi-win32-x64-msvc@0.24.0':
+    resolution: {integrity: sha512-049I7DvvC8IFpezOtda0Dj7C5V+Erl1Jhcsq14mUnUEe++nMldicss7D0CXZHVQ9UjPJ5zpxnqYxx05caMMDzA==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [win32]
+
   '@ast-grep/napi@0.22.4':
     resolution: {integrity: sha512-2kBrpVLGivW316WDb25ulau2nWYlyp+N+jLsVdoO3F2Ik80y6uilAbV80gw9SZwlnqRJjzADAYqkXs/lVsc+AA==}
     engines: {node: '>= 10'}
 
+  '@ast-grep/napi@0.24.0':
+    resolution: {integrity: sha512-pldAub9mf12u/xJRNMj0x88y9uH2DpVHWqXfp+MoY1NT1j9jdA/QrXHHwddyeerfCoHZK1j5RDA23LaJAZloGQ==}
+    engines: {node: '>= 10'}
+
   '@aws-crypto/crc32@3.0.0':
     resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==}
 
@@ -10912,11 +10990,6 @@ packages:
     resolution: {integrity: sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==}
     engines: {node: '>=4'}
 
-  axios-retry@4.1.0:
-    resolution: {integrity: sha512-svdth4H00yhlsjBbjfLQ/sMLkXqeLxhiFC1nE1JtkN/CIssGxqk0UwTEdrVjwA2gr3yJkAulwvDSIm4z4HyPvg==}
-    peerDependencies:
-      axios: 0.x || 1.x
-
   axios@1.6.8:
     resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==}
 
@@ -14667,9 +14740,9 @@ packages:
   no-case@3.0.4:
     resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
 
-  nock@13.5.4:
-    resolution: {integrity: sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==}
-    engines: {node: '>= 10.13'}
+  nock@14.0.0-beta.7:
+    resolution: {integrity: sha512-+EQMm5W9K8YnBE2Ceg4hnJynaCZmvK8ZlFXQ2fxGwtkOkBUq8GpQLTks2m1jpvse9XDxMDDOHgOWpiznFuh0bA==}
+    engines: {node: '>= 18'}
 
   node-abi@3.62.0:
     resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==}
@@ -18070,24 +18143,45 @@ snapshots:
   '@ast-grep/cli-darwin-arm64@0.22.4':
     optional: true
 
+  '@ast-grep/cli-darwin-arm64@0.24.0':
+    optional: true
+
   '@ast-grep/cli-darwin-x64@0.22.4':
     optional: true
 
+  '@ast-grep/cli-darwin-x64@0.24.0':
+    optional: true
+
   '@ast-grep/cli-linux-arm64-gnu@0.22.4':
     optional: true
 
+  '@ast-grep/cli-linux-arm64-gnu@0.24.0':
+    optional: true
+
   '@ast-grep/cli-linux-x64-gnu@0.22.4':
     optional: true
 
+  '@ast-grep/cli-linux-x64-gnu@0.24.0':
+    optional: true
+
   '@ast-grep/cli-win32-arm64-msvc@0.22.4':
     optional: true
 
+  '@ast-grep/cli-win32-arm64-msvc@0.24.0':
+    optional: true
+
   '@ast-grep/cli-win32-ia32-msvc@0.22.4':
     optional: true
 
+  '@ast-grep/cli-win32-ia32-msvc@0.24.0':
+    optional: true
+
   '@ast-grep/cli-win32-x64-msvc@0.22.4':
     optional: true
 
+  '@ast-grep/cli-win32-x64-msvc@0.24.0':
+    optional: true
+
   '@ast-grep/cli@0.22.4':
     dependencies:
       detect-libc: 2.0.3
@@ -18100,30 +18194,66 @@ snapshots:
       '@ast-grep/cli-win32-ia32-msvc': 0.22.4
       '@ast-grep/cli-win32-x64-msvc': 0.22.4
 
+  '@ast-grep/cli@0.24.0':
+    dependencies:
+      detect-libc: 2.0.3
+    optionalDependencies:
+      '@ast-grep/cli-darwin-arm64': 0.24.0
+      '@ast-grep/cli-darwin-x64': 0.24.0
+      '@ast-grep/cli-linux-arm64-gnu': 0.24.0
+      '@ast-grep/cli-linux-x64-gnu': 0.24.0
+      '@ast-grep/cli-win32-arm64-msvc': 0.24.0
+      '@ast-grep/cli-win32-ia32-msvc': 0.24.0
+      '@ast-grep/cli-win32-x64-msvc': 0.24.0
+
   '@ast-grep/napi-darwin-arm64@0.22.4':
     optional: true
 
+  '@ast-grep/napi-darwin-arm64@0.24.0':
+    optional: true
+
   '@ast-grep/napi-darwin-x64@0.22.4':
     optional: true
 
+  '@ast-grep/napi-darwin-x64@0.24.0':
+    optional: true
+
   '@ast-grep/napi-linux-arm64-gnu@0.22.4':
     optional: true
 
+  '@ast-grep/napi-linux-arm64-gnu@0.24.0':
+    optional: true
+
   '@ast-grep/napi-linux-x64-gnu@0.22.4':
     optional: true
 
+  '@ast-grep/napi-linux-x64-gnu@0.24.0':
+    optional: true
+
   '@ast-grep/napi-linux-x64-musl@0.22.4':
     optional: true
 
+  '@ast-grep/napi-linux-x64-musl@0.24.0':
+    optional: true
+
   '@ast-grep/napi-win32-arm64-msvc@0.22.4':
     optional: true
 
+  '@ast-grep/napi-win32-arm64-msvc@0.24.0':
+    optional: true
+
   '@ast-grep/napi-win32-ia32-msvc@0.22.4':
     optional: true
 
+  '@ast-grep/napi-win32-ia32-msvc@0.24.0':
+    optional: true
+
   '@ast-grep/napi-win32-x64-msvc@0.22.4':
     optional: true
 
+  '@ast-grep/napi-win32-x64-msvc@0.24.0':
+    optional: true
+
   '@ast-grep/napi@0.22.4':
     optionalDependencies:
       '@ast-grep/napi-darwin-arm64': 0.22.4
@@ -18135,6 +18265,17 @@ snapshots:
       '@ast-grep/napi-win32-ia32-msvc': 0.22.4
       '@ast-grep/napi-win32-x64-msvc': 0.22.4
 
+  '@ast-grep/napi@0.24.0':
+    optionalDependencies:
+      '@ast-grep/napi-darwin-arm64': 0.24.0
+      '@ast-grep/napi-darwin-x64': 0.24.0
+      '@ast-grep/napi-linux-arm64-gnu': 0.24.0
+      '@ast-grep/napi-linux-x64-gnu': 0.24.0
+      '@ast-grep/napi-linux-x64-musl': 0.24.0
+      '@ast-grep/napi-win32-arm64-msvc': 0.24.0
+      '@ast-grep/napi-win32-ia32-msvc': 0.24.0
+      '@ast-grep/napi-win32-x64-msvc': 0.24.0
+
   '@aws-crypto/crc32@3.0.0':
     dependencies:
       '@aws-crypto/util': 3.0.0
@@ -24354,11 +24495,6 @@ snapshots:
 
   axe-core@4.7.0: {}
 
-  axios-retry@4.1.0(axios@1.6.8):
-    dependencies:
-      axios: 1.6.8
-      is-retry-allowed: 2.2.0
-
   axios@1.6.8:
     dependencies:
       follow-redirects: 1.15.6(debug@3.2.7)
@@ -28965,13 +29101,10 @@ snapshots:
       lower-case: 2.0.2
       tslib: 2.4.1
 
-  nock@13.5.4:
+  nock@14.0.0-beta.7:
     dependencies:
-      debug: 4.3.4(supports-color@5.5.0)
       json-stringify-safe: 5.0.1
       propagate: 2.0.1
-    transitivePeerDependencies:
-      - supports-color
 
   node-abi@3.62.0:
     dependencies:

From 3d427ec3b04614ad13bdd518f099ef9a035e5d0c Mon Sep 17 00:00:00 2001
From: Aleksy Rybicki <alekso.php@gmail.com>
Date: Thu, 27 Jun 2024 11:47:26 +0100
Subject: [PATCH 3/4] fixed dependencies

---
 apps/modgpt/package.json | 3 ++-
 pnpm-lock.yaml           | 5 +++++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/apps/modgpt/package.json b/apps/modgpt/package.json
index ebe689b9c..97823de42 100644
--- a/apps/modgpt/package.json
+++ b/apps/modgpt/package.json
@@ -21,7 +21,8 @@
     "replicate": "0.25.2",
     "ts-node": "^10.9.2",
     "ts-node-dev": "^2.0.0",
-    "valibot": "^0.24.1"
+    "valibot": "^0.24.1",
+    "@codemod-com/utilities": "workspace:*"
   },
   "devDependencies": {
     "@types/node": "20.10.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 743dfd8e9..1aec12998 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -814,6 +814,9 @@ importers:
 
   apps/modgpt:
     dependencies:
+      '@codemod-com/utilities':
+        specifier: workspace:*
+        version: link:../../packages/utilities
       '@fastify/cors':
         specifier: 8.5.0
         version: 8.5.0
@@ -4938,6 +4941,8 @@ importers:
         specifier: ^5.4.5
         version: 5.4.5
 
+  packages/database/generated/client: {}
+
   packages/filemod:
     devDependencies:
       '@types/node':

From 32b556b73954508eac3eb9c3455ea37881d64bd6 Mon Sep 17 00:00:00 2001
From: Aleksy Rybicki <alekso.php@gmail.com>
Date: Thu, 27 Jun 2024 12:03:27 +0100
Subject: [PATCH 4/4] fixed tests

---
 apps/backend/src/publishHandler.test.ts | 48 ++++++++++++++-----------
 1 file changed, 28 insertions(+), 20 deletions(-)

diff --git a/apps/backend/src/publishHandler.test.ts b/apps/backend/src/publishHandler.test.ts
index 1dbff7730..72be34731 100644
--- a/apps/backend/src/publishHandler.test.ts
+++ b/apps/backend/src/publishHandler.test.ts
@@ -20,8 +20,6 @@ const GET_USER_RETURN = {
 
 const MOCK_TIMESTAMP = "timestamp";
 
-const originalFetch = global.fetch;
-
 const mocks = vi.hoisted(() => {
   const S3Client = vi.fn();
   S3Client.prototype.send = vi.fn();
@@ -47,7 +45,7 @@ const mocks = vi.hoisted(() => {
       },
     },
     fetch: vi.fn().mockImplementation((url, options) => {
-      if (options.method === "GET") {
+      if (options.method === "GET" || !options.method) {
         return Promise.resolve({
           json: () => Promise.resolve(GET_USER_RETURN),
           ok: true,
@@ -220,26 +218,34 @@ describe("/publish route", async () => {
       requestTimeout: 5000,
     });
 
-    expect(mocks.fetch).toHaveBeenCalledOnce();
-    expect(mocks.fetch).toHaveBeenCalledWith(
+    expect(mocks.fetch).toHaveBeenCalledTimes(3);
+    expect(mocks.fetch).toHaveBeenNthCalledWith(
+      2,
       "https://hooks.zapier.com/hooks/catch/18983913/2ybuovt/",
       {
-        codemod: {
-          name: codemodRcContents.name,
-          from: codemodRcContents.applicability?.from?.map((tuple) =>
-            tuple.join(" "),
-          ),
-          to: codemodRcContents.applicability?.to?.map((tuple) =>
-            tuple.join(" "),
-          ),
-          engine: codemodRcContents.engine,
-          publishedAt: MOCK_TIMESTAMP,
-        },
-        author: {
-          username: GET_USER_RETURN.user.username,
-          name: `${GET_USER_RETURN.user.firstName} ${GET_USER_RETURN.user.lastName}`,
-          email: GET_USER_RETURN.user.emailAddresses[0]?.emailAddress,
+        body: JSON.stringify({
+          codemod: {
+            name: codemodRcContents.name,
+            from: codemodRcContents.applicability?.from?.map((tuple) =>
+              tuple.join(" "),
+            ),
+            to: codemodRcContents.applicability?.to?.map((tuple) =>
+              tuple.join(" "),
+            ),
+            engine: codemodRcContents.engine,
+            publishedAt: MOCK_TIMESTAMP,
+          },
+          author: {
+            username: GET_USER_RETURN.user.username,
+            name: `${GET_USER_RETURN.user.firstName} ${GET_USER_RETURN.user.lastName}`,
+            email: GET_USER_RETURN.user.emailAddresses[0]?.emailAddress,
+          },
+        }),
+        headers: {
+          "Content-Type": "application/json",
         },
+        method: "POST",
+        signal: expect.any(AbortSignal),
       },
     );
 
@@ -609,6 +615,7 @@ describe("/publish route", async () => {
       mocks.prisma.codemodVersion.findFirst.mockImplementation(() => null);
       mocks.fetch.mockImplementation(() => ({
         json: () => ({ ...GET_USER_RETURN, allowedNamespaces: ["org"] }),
+        ok: true,
       }));
       mocks.prisma.codemod.upsert.mockImplementation(() => {
         return { createdAt: { getTime: () => MOCK_TIMESTAMP }, id: "id" };
@@ -677,6 +684,7 @@ describe("/publish route", async () => {
           organizations: [],
           allowedNamespaces: [],
         }),
+        ok: true,
       }));
 
       const codemodRcContents: CodemodConfigInput = {