diff --git a/.env.example b/.env.example
index 1622203..c832b28 100644
--- a/.env.example
+++ b/.env.example
@@ -19,6 +19,7 @@ JWT_SECRET=change-this-to-a-secure-random-string-in-production
# SKIP_FORKS=false
# PRIVATE_REPOSITORIES=false
# MIRROR_ISSUES=false
+# MIRROR_WIKI=false
# MIRROR_STARRED=false
# MIRROR_ORGANIZATIONS=false
# PRESERVE_ORG_STRUCTURE=false
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index bd9a1da..4b820b2 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -63,6 +63,7 @@ services:
- SKIP_FORKS=${SKIP_FORKS:-false}
- PRIVATE_REPOSITORIES=${PRIVATE_REPOSITORIES:-false}
- MIRROR_ISSUES=${MIRROR_ISSUES:-false}
+ - MIRROR_WIKI=${MIRROR_WIKI:-false}
- MIRROR_STARRED=${MIRROR_STARRED:-false}
- MIRROR_ORGANIZATIONS=${MIRROR_ORGANIZATIONS:-false}
- PRESERVE_ORG_STRUCTURE=${PRESERVE_ORG_STRUCTURE:-false}
diff --git a/docker-compose.yml b/docker-compose.yml
index 24237f4..014f21b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -30,6 +30,7 @@ services:
- SKIP_FORKS=${SKIP_FORKS:-false}
- PRIVATE_REPOSITORIES=${PRIVATE_REPOSITORIES:-false}
- MIRROR_ISSUES=${MIRROR_ISSUES:-false}
+ - MIRROR_WIKI=${MIRROR_WIKI:-false}
- MIRROR_STARRED=${MIRROR_STARRED:-false}
- MIRROR_ORGANIZATIONS=${MIRROR_ORGANIZATIONS:-false}
- PRESERVE_ORG_STRUCTURE=${PRESERVE_ORG_STRUCTURE:-false}
diff --git a/src/components/config/ConfigTabs.tsx b/src/components/config/ConfigTabs.tsx
index 7c2051b..07069e5 100644
--- a/src/components/config/ConfigTabs.tsx
+++ b/src/components/config/ConfigTabs.tsx
@@ -35,6 +35,7 @@ export function ConfigTabs() {
skipForks: false,
privateRepositories: false,
mirrorIssues: false,
+ mirrorWiki: false,
mirrorStarred: false,
preserveOrgStructure: false,
skipStarredIssues: false,
diff --git a/src/components/config/GitHubConfigForm.tsx b/src/components/config/GitHubConfigForm.tsx
index 3ef3474..6356205 100644
--- a/src/components/config/GitHubConfigForm.tsx
+++ b/src/components/config/GitHubConfigForm.tsx
@@ -240,6 +240,30 @@ export function GitHubConfigForm({ config, setConfig, onAutoSave, isAutoSaving }
+
+
+ handleChange({
+ target: {
+ name: "mirrorWiki",
+ type: "checkbox",
+ checked: Boolean(checked),
+ value: "",
+ },
+ } as React.ChangeEvent)
+ }
+ />
+
+
+
=> {
// First check if we have a recorded mirroredLocation and if the repo exists there
- if (repository.mirroredLocation && repository.mirroredLocation.trim() !== "") {
- const [mirroredOwner] = repository.mirroredLocation.split('/');
+ if (
+ repository.mirroredLocation &&
+ repository.mirroredLocation.trim() !== ""
+ ) {
+ const [mirroredOwner] = repository.mirroredLocation.split("/");
if (mirroredOwner) {
const mirroredPresent = await isRepoPresentInGitea({
config,
@@ -90,7 +93,9 @@ export const checkRepoLocation = async ({
});
if (mirroredPresent) {
- console.log(`Repository found at recorded mirrored location: ${repository.mirroredLocation}`);
+ console.log(
+ `Repository found at recorded mirrored location: ${repository.mirroredLocation}`
+ );
return { present: true, actualOwner: mirroredOwner };
}
}
@@ -162,7 +167,9 @@ export const mirrorGithubRepoToGitea = async ({
status: "mirrored",
});
- console.log(`Repository ${repository.name} database status updated to mirrored`);
+ console.log(
+ `Repository ${repository.name} database status updated to mirrored`
+ );
return;
}
@@ -205,16 +212,28 @@ export const mirrorGithubRepoToGitea = async ({
const apiUrl = `${config.giteaConfig.url}/api/v1/repos/migrate`;
- const response = await httpPost(apiUrl, {
- clone_addr: cloneAddress,
- repo_name: repository.name,
- mirror: true,
- private: repository.isPrivate,
- repo_owner: config.giteaConfig.username,
- description: "",
- service: "git",
- }, {
- "Authorization": `token ${config.giteaConfig.token}`,
+ const response = await httpPost(
+ apiUrl,
+ {
+ clone_addr: cloneAddress,
+ repo_name: repository.name,
+ mirror: true,
+ wiki: config.githubConfig.mirrorWiki || false, // will mirror wiki if it exists
+ private: repository.isPrivate,
+ repo_owner: config.giteaConfig.username,
+ description: "",
+ service: "git",
+ },
+ {
+ Authorization: `token ${config.giteaConfig.token}`,
+ }
+ );
+
+ //mirror releases
+ await mirrorGitHubReleasesToGitea({
+ config,
+ octokit,
+ repository,
});
// clone issues
@@ -317,16 +336,22 @@ export async function getOrCreateGiteaOrg({
}
);
- console.log(`Get org response status: ${orgRes.status} for org: ${orgName}`);
+ console.log(
+ `Get org response status: ${orgRes.status} for org: ${orgName}`
+ );
if (orgRes.ok) {
// Check if response is actually JSON
const contentType = orgRes.headers.get("content-type");
if (!contentType || !contentType.includes("application/json")) {
- console.warn(`Expected JSON response but got content-type: ${contentType}`);
+ console.warn(
+ `Expected JSON response but got content-type: ${contentType}`
+ );
const responseText = await orgRes.text();
console.warn(`Response body: ${responseText}`);
- throw new Error(`Invalid response format from Gitea API. Expected JSON but got: ${contentType}`);
+ throw new Error(
+ `Invalid response format from Gitea API. Expected JSON but got: ${contentType}`
+ );
}
// Clone the response to handle potential JSON parsing errors
@@ -334,14 +359,22 @@ export async function getOrCreateGiteaOrg({
try {
const org = await orgRes.json();
- console.log(`Successfully retrieved existing org: ${orgName} with ID: ${org.id}`);
+ console.log(
+ `Successfully retrieved existing org: ${orgName} with ID: ${org.id}`
+ );
// Note: Organization events are handled by the main mirroring process
// to avoid duplicate events
return org.id;
} catch (jsonError) {
const responseText = await orgResClone.text();
- console.error(`Failed to parse JSON response for existing org: ${responseText}`);
- throw new Error(`Failed to parse JSON response from Gitea API: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`);
+ console.error(
+ `Failed to parse JSON response for existing org: ${responseText}`
+ );
+ throw new Error(
+ `Failed to parse JSON response from Gitea API: ${
+ jsonError instanceof Error ? jsonError.message : String(jsonError)
+ }`
+ );
}
}
@@ -361,21 +394,29 @@ export async function getOrCreateGiteaOrg({
}),
});
- console.log(`Create org response status: ${createRes.status} for org: ${orgName}`);
+ console.log(
+ `Create org response status: ${createRes.status} for org: ${orgName}`
+ );
if (!createRes.ok) {
const errorText = await createRes.text();
- console.error(`Failed to create org ${orgName}. Status: ${createRes.status}, Response: ${errorText}`);
+ console.error(
+ `Failed to create org ${orgName}. Status: ${createRes.status}, Response: ${errorText}`
+ );
throw new Error(`Failed to create Gitea org: ${errorText}`);
}
// Check if response is actually JSON
const createContentType = createRes.headers.get("content-type");
if (!createContentType || !createContentType.includes("application/json")) {
- console.warn(`Expected JSON response but got content-type: ${createContentType}`);
+ console.warn(
+ `Expected JSON response but got content-type: ${createContentType}`
+ );
const responseText = await createRes.text();
console.warn(`Response body: ${responseText}`);
- throw new Error(`Invalid response format from Gitea API. Expected JSON but got: ${createContentType}`);
+ throw new Error(
+ `Invalid response format from Gitea API. Expected JSON but got: ${createContentType}`
+ );
}
// Note: Organization creation events are handled by the main mirroring process
@@ -386,12 +427,20 @@ export async function getOrCreateGiteaOrg({
try {
const newOrg = await createRes.json();
- console.log(`Successfully created new org: ${orgName} with ID: ${newOrg.id}`);
+ console.log(
+ `Successfully created new org: ${orgName} with ID: ${newOrg.id}`
+ );
return newOrg.id;
} catch (jsonError) {
const responseText = await createResClone.text();
- console.error(`Failed to parse JSON response for new org: ${responseText}`);
- throw new Error(`Failed to parse JSON response from Gitea API: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`);
+ console.error(
+ `Failed to parse JSON response for new org: ${responseText}`
+ );
+ throw new Error(
+ `Failed to parse JSON response from Gitea API: ${
+ jsonError instanceof Error ? jsonError.message : String(jsonError)
+ }`
+ );
}
} catch (error) {
const errorMessage =
@@ -399,7 +448,9 @@ export async function getOrCreateGiteaOrg({
? error.message
: "Unknown error occurred in getOrCreateGiteaOrg.";
- console.error(`Error in getOrCreateGiteaOrg for ${orgName}: ${errorMessage}`);
+ console.error(
+ `Error in getOrCreateGiteaOrg for ${orgName}: ${errorMessage}`
+ );
await createMirrorJob({
userId: config.userId,
@@ -469,7 +520,9 @@ export async function mirrorGitHubRepoToGiteaOrg({
status: "mirrored",
});
- console.log(`Repository ${repository.name} database status updated to mirrored in organization ${orgName}`);
+ console.log(
+ `Repository ${repository.name} database status updated to mirrored in organization ${orgName}`
+ );
return;
}
@@ -506,14 +559,26 @@ export async function mirrorGitHubRepoToGiteaOrg({
const apiUrl = `${config.giteaConfig.url}/api/v1/repos/migrate`;
- const migrateRes = await httpPost(apiUrl, {
- clone_addr: cloneAddress,
- uid: giteaOrgId,
- repo_name: repository.name,
- mirror: true,
- private: repository.isPrivate,
- }, {
- "Authorization": `token ${config.giteaConfig.token}`,
+ const migrateRes = await httpPost(
+ apiUrl,
+ {
+ clone_addr: cloneAddress,
+ uid: giteaOrgId,
+ repo_name: repository.name,
+ mirror: true,
+ wiki: config.githubConfig?.mirrorWiki || false, // will mirror wiki if it exists
+ private: repository.isPrivate,
+ },
+ {
+ Authorization: `token ${config.giteaConfig.token}`,
+ }
+ );
+
+ //mirror releases
+ await mirrorGitHubReleasesToGitea({
+ config,
+ octokit,
+ repository,
});
// Clone issues
@@ -677,9 +742,13 @@ export async function mirrorGitHubOrgToGitea({
.where(eq(repositories.organization, organization.name));
if (orgRepos.length === 0) {
- console.log(`No repositories found for organization ${organization.name} - marking as successfully mirrored`);
+ console.log(
+ `No repositories found for organization ${organization.name} - marking as successfully mirrored`
+ );
} else {
- console.log(`Mirroring ${orgRepos.length} repositories for organization ${organization.name}`);
+ console.log(
+ `Mirroring ${orgRepos.length} repositories for organization ${organization.name}`
+ );
// Import the processWithRetry function
const { processWithRetry } = await import("@/lib/utils/concurrency");
@@ -701,7 +770,9 @@ export async function mirrorGitHubOrgToGitea({
};
// Log the start of mirroring
- console.log(`Starting mirror for repository: ${repo.name} in organization ${organization.name}`);
+ console.log(
+ `Starting mirror for repository: ${repo.name} in organization ${organization.name}`
+ );
// Mirror the repository
await mirrorGitHubRepoToGiteaOrg({
@@ -721,12 +792,16 @@ export async function mirrorGitHubOrgToGitea({
onProgress: (completed, total, result) => {
const percentComplete = Math.round((completed / total) * 100);
if (result) {
- console.log(`Mirrored repository "${result.name}" in organization ${organization.name} (${completed}/${total}, ${percentComplete}%)`);
+ console.log(
+ `Mirrored repository "${result.name}" in organization ${organization.name} (${completed}/${total}, ${percentComplete}%)`
+ );
}
},
onRetry: (repo, error, attempt) => {
- console.log(`Retrying repository ${repo.name} in organization ${organization.name} (attempt ${attempt}): ${error.message}`);
- }
+ console.log(
+ `Retrying repository ${repo.name} in organization ${organization.name} (attempt ${attempt}): ${error.message}`
+ );
+ },
}
);
}
@@ -750,9 +825,10 @@ export async function mirrorGitHubOrgToGitea({
organizationId: organization.id,
organizationName: organization.name,
message: `Successfully mirrored organization: ${organization.name}`,
- details: orgRepos.length === 0
- ? `Organization ${organization.name} was processed successfully (no repositories found).`
- : `Organization ${organization.name} was mirrored to Gitea with ${orgRepos.length} repositories.`,
+ details:
+ orgRepos.length === 0
+ ? `Organization ${organization.name} was processed successfully (no repositories found).`
+ : `Organization ${organization.name} was mirrored to Gitea with ${orgRepos.length} repositories.`,
status: repoStatusEnum.parse("mirrored"),
});
} catch (error) {
@@ -836,18 +912,20 @@ export const syncGiteaRepo = async ({
const { present, actualOwner } = await checkRepoLocation({
config,
repository,
- expectedOwner: repoOwner
+ expectedOwner: repoOwner,
});
if (!present) {
- throw new Error(`Repository ${repository.name} not found in Gitea at any expected location`);
+ throw new Error(
+ `Repository ${repository.name} not found in Gitea at any expected location`
+ );
}
// Use the actual owner where the repo was found
const apiUrl = `${config.giteaConfig.url}/api/v1/repos/${actualOwner}/${repository.name}/mirror-sync`;
const response = await httpPost(apiUrl, undefined, {
- "Authorization": `token ${config.giteaConfig.token}`,
+ Authorization: `token ${config.giteaConfig.token}`,
});
// Mark repo as "synced" in DB
@@ -951,9 +1029,11 @@ export const mirrorGitRepoIssuesToGitea = async ({
);
// Filter out pull requests
- const filteredIssues = issues.filter(issue => !(issue as any).pull_request);
+ const filteredIssues = issues.filter((issue) => !(issue as any).pull_request);
- console.log(`Mirroring ${filteredIssues.length} issues from ${repository.fullName}`);
+ console.log(
+ `Mirroring ${filteredIssues.length} issues from ${repository.fullName}`
+ );
if (filteredIssues.length === 0) {
console.log(`No issues to mirror for ${repository.fullName}`);
@@ -964,7 +1044,7 @@ export const mirrorGitRepoIssuesToGitea = async ({
const giteaLabelsRes = await httpGet(
`${config.giteaConfig.url}/api/v1/repos/${repoOrigin}/${repository.name}/labels`,
{
- "Authorization": `token ${config.giteaConfig.token}`,
+ Authorization: `token ${config.giteaConfig.token}`,
}
);
@@ -994,10 +1074,12 @@ export const mirrorGitRepoIssuesToGitea = async ({
} else {
try {
const created = await httpPost(
- `${config.giteaConfig!.url}/api/v1/repos/${repoOrigin}/${repository.name}/labels`,
+ `${config.giteaConfig!.url}/api/v1/repos/${repoOrigin}/${
+ repository.name
+ }/labels`,
{ name, color: "#ededed" }, // Default color
{
- "Authorization": `token ${config.giteaConfig!.token}`,
+ Authorization: `token ${config.giteaConfig!.token}`,
}
);
@@ -1029,10 +1111,12 @@ export const mirrorGitRepoIssuesToGitea = async ({
// Create the issue in Gitea
const createdIssue = await httpPost(
- `${config.giteaConfig!.url}/api/v1/repos/${repoOrigin}/${repository.name}/issues`,
+ `${config.giteaConfig!.url}/api/v1/repos/${repoOrigin}/${
+ repository.name
+ }/issues`,
issuePayload,
{
- "Authorization": `token ${config.giteaConfig!.token}`,
+ Authorization: `token ${config.giteaConfig!.token}`,
}
);
@@ -1054,12 +1138,14 @@ export const mirrorGitRepoIssuesToGitea = async ({
comments,
async (comment) => {
await httpPost(
- `${config.giteaConfig!.url}/api/v1/repos/${repoOrigin}/${repository.name}/issues/${createdIssue.data.number}/comments`,
+ `${config.giteaConfig!.url}/api/v1/repos/${repoOrigin}/${
+ repository.name
+ }/issues/${createdIssue.data.number}/comments`,
{
body: `@${comment.user?.login} commented on GitHub:\n\n${comment.body}`,
},
{
- "Authorization": `token ${config.giteaConfig!.token}`,
+ Authorization: `token ${config.giteaConfig!.token}`,
}
);
return comment;
@@ -1069,8 +1155,10 @@ export const mirrorGitRepoIssuesToGitea = async ({
maxRetries: 2,
retryDelay: 1000,
onRetry: (_comment, error, attempt) => {
- console.log(`Retrying comment (attempt ${attempt}): ${error.message}`);
- }
+ console.log(
+ `Retrying comment (attempt ${attempt}): ${error.message}`
+ );
+ },
}
);
}
@@ -1084,14 +1172,69 @@ export const mirrorGitRepoIssuesToGitea = async ({
onProgress: (completed, total, result) => {
const percentComplete = Math.round((completed / total) * 100);
if (result) {
- console.log(`Mirrored issue "${result.title}" (${completed}/${total}, ${percentComplete}%)`);
+ console.log(
+ `Mirrored issue "${result.title}" (${completed}/${total}, ${percentComplete}%)`
+ );
}
},
onRetry: (issue, error, attempt) => {
- console.log(`Retrying issue "${issue.title}" (attempt ${attempt}): ${error.message}`);
- }
+ console.log(
+ `Retrying issue "${issue.title}" (attempt ${attempt}): ${error.message}`
+ );
+ },
}
);
- console.log(`Completed mirroring ${filteredIssues.length} issues for ${repository.fullName}`);
+ console.log(
+ `Completed mirroring ${filteredIssues.length} issues for ${repository.fullName}`
+ );
};
+
+export async function mirrorGitHubReleasesToGitea({
+ octokit,
+ repository,
+ config,
+}: {
+ octokit: Octokit;
+ repository: Repository;
+ config: Partial;
+}) {
+ if (
+ !config.giteaConfig?.username ||
+ !config.giteaConfig?.token ||
+ !config.giteaConfig?.url
+ ) {
+ throw new Error("Gitea config is incomplete for mirroring releases.");
+ }
+
+ const repoOwner = getGiteaRepoOwner({
+ config,
+ repository,
+ });
+
+ const { url, token } = config.giteaConfig;
+
+ const releases = await octokit.rest.repos.listReleases({
+ owner: repository.owner,
+ repo: repository.name,
+ });
+
+ for (const release of releases.data) {
+ await httpPost(
+ `${url}/api/v1/repos/${repoOwner}/${repository.name}/releases`,
+ {
+ tag_name: release.tag_name,
+ target: release.target_commitish,
+ title: release.name || release.tag_name,
+ note: release.body || "",
+ draft: release.draft,
+ prerelease: release.prerelease,
+ },
+ {
+ Authorization: `token ${token}`,
+ }
+ );
+ }
+
+ console.log(`✅ Mirrored ${releases.data.length} GitHub releases to Gitea`);
+}
\ No newline at end of file
diff --git a/src/pages/api/config/index.ts b/src/pages/api/config/index.ts
index cb519ff..9bc17ef 100644
--- a/src/pages/api/config/index.ts
+++ b/src/pages/api/config/index.ts
@@ -238,6 +238,7 @@ export const GET: APIRoute = async ({ request }) => {
skipForks: false,
privateRepositories: false,
mirrorIssues: false,
+ mirrorWiki: false,
mirrorStarred: true,
useSpecificUser: false,
preserveOrgStructure: true,
diff --git a/src/pages/api/job/mirror-repo.test.ts b/src/pages/api/job/mirror-repo.test.ts
index d530248..8eb882f 100644
--- a/src/pages/api/job/mirror-repo.test.ts
+++ b/src/pages/api/job/mirror-repo.test.ts
@@ -1,35 +1,75 @@
import { describe, test, expect, mock, beforeEach, afterEach } from "bun:test";
+import type { MirrorRepoRequest } from "@/types/mirror";
-// Create a mock POST function
-const mockPOST = mock(async ({ request }) => {
- const body = await request.json();
-
- // Check for missing userId or repositoryIds
- if (!body.userId || !body.repositoryIds) {
- return new Response(
- JSON.stringify({
- error: "Missing userId or repositoryIds."
- }),
- { status: 400 }
- );
- }
-
- // Success case
- return new Response(
- JSON.stringify({
- success: true,
- message: "Repository mirroring started",
- batchId: "test-batch-id"
- }),
- { status: 200 }
- );
-});
-
-// Create a mock module
-const mockModule = {
- POST: mockPOST
+// Mock the database module
+const mockDb = {
+ select: mock(() => ({
+ from: mock(() => ({
+ where: mock(() => ({
+ limit: mock(() => Promise.resolve([{
+ id: "config-id",
+ userId: "user-id",
+ githubConfig: {
+ token: "github-token",
+ preserveOrgStructure: false,
+ mirrorIssues: false
+ },
+ giteaConfig: {
+ url: "https://gitea.example.com",
+ token: "gitea-token",
+ username: "giteauser"
+ }
+ }]))
+ }))
+ }))
+ }))
};
+mock.module("@/lib/db", () => ({
+ db: mockDb,
+ configs: {},
+ repositories: {}
+}));
+
+// Mock the gitea module
+const mockMirrorGithubRepoToGitea = mock(() => Promise.resolve());
+const mockMirrorGitHubOrgRepoToGiteaOrg = mock(() => Promise.resolve());
+
+mock.module("@/lib/gitea", () => ({
+ mirrorGithubRepoToGitea: mockMirrorGithubRepoToGitea,
+ mirrorGitHubOrgRepoToGiteaOrg: mockMirrorGitHubOrgRepoToGiteaOrg
+}));
+
+// Mock the github module
+const mockCreateGitHubClient = mock(() => ({}));
+
+mock.module("@/lib/github", () => ({
+ createGitHubClient: mockCreateGitHubClient
+}));
+
+// Mock the concurrency module
+const mockProcessWithResilience = mock(() => Promise.resolve([]));
+
+mock.module("@/lib/utils/concurrency", () => ({
+ processWithResilience: mockProcessWithResilience
+}));
+
+// Mock drizzle-orm
+mock.module("drizzle-orm", () => ({
+ eq: mock(() => ({})),
+ inArray: mock(() => ({}))
+}));
+
+// Mock the types
+mock.module("@/types/Repository", () => ({
+ repositoryVisibilityEnum: {
+ parse: mock((value: string) => value)
+ },
+ repoStatusEnum: {
+ parse: mock((value: string) => value)
+ }
+}));
+
describe("Repository Mirroring API", () => {
// Mock console.log and console.error to prevent test output noise
let originalConsoleLog: typeof console.log;
diff --git a/src/pages/api/job/mirror-repo.ts b/src/pages/api/job/mirror-repo.ts
index 7199b8e..d35a1e2 100644
--- a/src/pages/api/job/mirror-repo.ts
+++ b/src/pages/api/job/mirror-repo.ts
@@ -9,7 +9,6 @@ import {
} from "@/lib/gitea";
import { createGitHubClient } from "@/lib/github";
import { processWithResilience } from "@/lib/utils/concurrency";
-import { v4 as uuidv4 } from "uuid";
export const POST: APIRoute = async ({ request }) => {
try {
@@ -77,9 +76,6 @@ export const POST: APIRoute = async ({ request }) => {
// Define the concurrency limit - adjust based on API rate limits
const CONCURRENCY_LIMIT = 3;
- // Generate a batch ID to group related repositories
- const batchId = uuidv4();
-
// Process repositories in parallel with resilience to container restarts
await processWithResilience(
repos,
@@ -120,7 +116,6 @@ export const POST: APIRoute = async ({ request }) => {
{
userId: config.userId || "",
jobType: "mirror",
- batchId,
getItemId: (repo) => repo.id,
getItemName: (repo) => repo.name,
concurrencyLimit: CONCURRENCY_LIMIT,
@@ -129,15 +124,19 @@ export const POST: APIRoute = async ({ request }) => {
checkpointInterval: 5, // Checkpoint every 5 repositories to reduce event frequency
onProgress: (completed, total, result) => {
const percentComplete = Math.round((completed / total) * 100);
- console.log(`Mirroring progress: ${percentComplete}% (${completed}/${total})`);
+ console.log(
+ `Mirroring progress: ${percentComplete}% (${completed}/${total})`
+ );
if (result) {
console.log(`Successfully mirrored repository: ${result.name}`);
}
},
onRetry: (repo, error, attempt) => {
- console.log(`Retrying repository ${repo.name} (attempt ${attempt}): ${error.message}`);
- }
+ console.log(
+ `Retrying repository ${repo.name} (attempt ${attempt}): ${error.message}`
+ );
+ },
}
);
@@ -168,7 +167,10 @@ export const POST: APIRoute = async ({ request }) => {
// Enhanced error logging for better debugging
console.error("=== ERROR MIRRORING REPOSITORIES ===");
console.error("Error type:", error?.constructor?.name);
- console.error("Error message:", error instanceof Error ? error.message : String(error));
+ console.error(
+ "Error message:",
+ error instanceof Error ? error.message : String(error)
+ );
if (error instanceof Error) {
console.error("Error stack:", error.stack);
@@ -181,9 +183,11 @@ export const POST: APIRoute = async ({ request }) => {
console.error("- Headers:", Object.fromEntries(request.headers.entries()));
// If it's a JSON parsing error, provide more context
- if (error instanceof SyntaxError && error.message.includes('JSON')) {
+ if (error instanceof SyntaxError && error.message.includes("JSON")) {
console.error("🚨 JSON PARSING ERROR DETECTED:");
- console.error("This suggests the response from Gitea API is not valid JSON");
+ console.error(
+ "This suggests the response from Gitea API is not valid JSON"
+ );
console.error("Common causes:");
console.error("- Gitea server returned HTML error page instead of JSON");
console.error("- Network connection interrupted");
@@ -196,14 +200,16 @@ export const POST: APIRoute = async ({ request }) => {
return new Response(
JSON.stringify({
- error: error instanceof Error ? error.message : "An unknown error occurred",
+ error:
+ error instanceof Error ? error.message : "An unknown error occurred",
errorType: error?.constructor?.name || "Unknown",
timestamp: new Date().toISOString(),
- troubleshooting: error instanceof SyntaxError && error.message.includes('JSON')
- ? "JSON parsing error detected. Check Gitea server status and logs. Ensure Gitea is returning valid JSON responses."
- : "Check application logs for more details"
+ troubleshooting:
+ error instanceof SyntaxError && error.message.includes("JSON")
+ ? "JSON parsing error detected. Check Gitea server status and logs. Ensure Gitea is returning valid JSON responses."
+ : "Check application logs for more details",
}),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
-};
+};
\ No newline at end of file
diff --git a/src/types/config.ts b/src/types/config.ts
index 65c5835..fecbe0d 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -31,6 +31,7 @@ export interface GitHubConfig {
skipForks: boolean;
privateRepositories: boolean;
mirrorIssues: boolean;
+ mirrorWiki: boolean;
mirrorStarred: boolean;
preserveOrgStructure: boolean;
skipStarredIssues: boolean;