import { promises } from 'fs'; import { prepareDirectoryForExport } from '../../export/helpers/prepare.mjs'; import { getGitHubRepoHash } from './hash.mjs'; import { downloadFile } from '../api/download.mjs'; import { unzip } from '../helpers/unzip.mjs'; import 'pathe'; import '../api/index.mjs'; import '../api/cache.mjs'; import 'crypto'; import '../../misc/scan.mjs'; import '../api/config.mjs'; import '../api/fetch.mjs'; import 'fs/promises'; import 'extract-zip'; async function findMatchingDirs(rootDir, hash) { const matches = []; const files = await promises.readdir(rootDir); for (let i = 0; i < files.length; i++) { const file = files[i]; const lastChunk = file.split("-").pop(); if (lastChunk.length < 4 || lastChunk !== hash.slice(0, lastChunk.length)) { continue; } const stat = await promises.stat(rootDir + "/" + file); if (stat.isDirectory()) { matches.push(file); } } return matches; } async function downloadGitHubRepo(options) { const hash = await getGitHubRepoHash(options); const ifModifiedSince = options.ifModifiedSince; if (ifModifiedSince) { const expectedHash = typeof ifModifiedSince === "string" ? ifModifiedSince : ifModifiedSince.downloadType === "github" ? ifModifiedSince.hash : null; if (hash === expectedHash) { return "not_modified"; } } options.target = options.target.replace("{hash}", hash); const rootDir = options.target = await prepareDirectoryForExport(options); const archiveTarget = rootDir + "/" + hash + ".zip"; let exists = false; try { const stat = await promises.stat(archiveTarget); exists = stat.isFile(); } catch (err) { } if (!exists) { const uri = `https://api.github.com/repos/${options.user}/${options.repo}/zipball/${hash}`; await downloadFile( { uri, headers: { Accept: "application/vnd.github.v3+json", Authorization: "token " + options.token } }, archiveTarget ); } const files = await promises.readdir(rootDir); const hashSearch = "-" + hash; for (let i = 0; i < files.length; i++) { const file = files[i]; if (file === hash + ".zip") { continue; } const filename = rootDir + "/" + files[i]; const stat = await promises.lstat(filename); const isDir = stat.isDirectory(); if ( // Remove symbolic links stat.isSymbolicLink() || // Remove if directory matches hash to avoid errors extracting zip isDir && filename.slice(0 - hashSearch.length) === hashSearch || // Remove if directory and cleanupOldDirectories is not disabled isDir && options.cleanupOldDirectories !== false || // Remove if file and cleanupOldFiles is enabled !isDir && options.cleanupOldFiles ) { try { await promises.rm(filename, { force: true, recursive: true }); } catch (err) { } } } await unzip(archiveTarget, rootDir); const matchingDirs = await findMatchingDirs(rootDir, hash); if (matchingDirs.length !== 1) { throw new Error(`Error unpacking ${hash}.zip`); } const contentsDir = rootDir + "/" + matchingDirs[0]; return { downloadType: "github", rootDir, contentsDir, hash }; } export { downloadGitHubRepo };