import { existsSync } from "node:fs";
import { join, resolve } from "node:path";
import { mkdir } from "node:fs/promises";
import { spawn } from "node:child_process";
import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
import cuid from "cuid";
import type { Repository } from "@prisma/client";
import type { ForkRepositoryDTO, RepositoryServiceDeps } from "./types";
const makeForkRepository: ServiceMethodFactory<
RepositoryServiceDeps,
[ForkRepositoryDTO],
Promise<Repository>
> = ({ request }) => {
return async ({ source, target }) => {
if (
source == null ||
source.parentOrg == null ||
source.repository == null
) {
throw new Error(
"Cannot fork repository: invalid source object (either it is `null`, or its `parentOrg` and/or `repository` properties are `null`)."
);
}
if (
target == null ||
target.parentOrg == null ||
target.repoSlug == null ||
target.repoData == null
) {
throw new Error(
"Cannot fork repository: invalid target object (either it is `null`, or its `parentOrg`, `repoSlug`, and/or `repoData` properties are `null`)."
);
}
let existingRepoWithSameSlugInSameTargetOrg =
await request.prisma.repository.findFirst({
where: {
slug: target.repoSlug,
organization: {
id: target.parentOrg.id,
},
},
});
if (existingRepoWithSameSlugInSameTargetOrg != null) {
throw new Error(
"Cannot fork repository: a repository with the same slug already exists in this organization."
);
}
existingRepoWithSameSlugInSameTargetOrg = null;
console.log(`[..] creating target fork repository in database...`);
const newRepo = await request.prisma.repository.create({
data: {
...target.repoData,
isFork: true,
id: cuid(),
slug: target.repoSlug,
forkedFromRepoId: source.repository.id,
organizationId: target.parentOrg.id,
createdAt: new Date(Date.now()),
updatedAt: new Date(Date.now()),
avatarUri: source.repository.avatarUri,
keywords: source.repository.keywords,
shortDescription: source.repository.shortDescription,
websiteUrl: source.repository.websiteUrl,
},
});
console.log(
`[ok] created target fork repository in database with id "${newRepo.id}" and slug "${target.parentOrg.slug}/${newRepo.slug}" from source repo with id "${source.repository.id}" and slug "${source.parentOrg.slug}/${source.repository.slug}" !`
);
if (existsSync(target.parentOrgRepositoriesDir.toString()) === false) {
console.log(`[..] creating organization directory...`);
await mkdir(target.parentOrgRepositoriesDir.toString(), {
recursive: true,
});
console.log(
`[ok] created organization directory in:`,
target.parentOrgRepositoriesDir.toString()
);
}
const sourceRepositoryPathResolved = resolve(
join(
source.parentOrgRepositoriesDir.toString(),
`${source.repository.slug}.git`
)
);
if (existsSync(sourceRepositoryPathResolved) === false) {
throw new Error(
"Cannot fork repository: no .git/ folder exists for source repository in source organization."
);
}
const targetRepositoryPathResolved = resolve(
join(target.parentOrgRepositoriesDir.toString(), `${target.repoSlug}.git`)
);
if (existsSync(targetRepositoryPathResolved) === true) {
throw new Error(
"Cannot fork repository: a .git/ folder with the same name already exists in target organization."
);
}
console.log(
`[..] forking repository folder from:`,
sourceRepositoryPathResolved,
`to:`,
targetRepositoryPathResolved
);
const gitCopyForkRepoProcess = spawn(
"/bin/cp",
["-rf", sourceRepositoryPathResolved, targetRepositoryPathResolved],
{
env: {
LANG: "C",
},
}
);
const gitCopyForkRepoResult = await new Promise<string>(
(resolve, reject) => {
let buffer = [] as string[];
gitCopyForkRepoProcess.stdout.on("data", (data) => buffer.push(data));
gitCopyForkRepoProcess.stderr.on("data", (data) => {
reject(new Error(Buffer.from(data).toString("utf-8")));
});
gitCopyForkRepoProcess.stdout.on("close", () => {
resolve(buffer.join(""));
});
}
);
console.log(
`[ok] finished execution of "cp -rf ${sourceRepositoryPathResolved} ${targetRepositoryPathResolved}" with result:\n\t`,
gitCopyForkRepoResult
);
console.log(
`[ok] forked repository folder from:`,
sourceRepositoryPathResolved,
`to:`,
targetRepositoryPathResolved,
"->",
newRepo
);
return newRepo;
};
};
export default makeForkRepository;