76 lines
3.2 KiB
JavaScript
76 lines
3.2 KiB
JavaScript
// scripts/ci/lib/forgejo-generic-registry.mjs
|
|
// Forgejo generic package registry IO 어댑터 (portable-publish-policy.mjs 의 PackageRegistry 포트).
|
|
//
|
|
// - Forgejo generic registry는 HEAD를 405로 거부하고 파일 해시를 헤더로 주지 않는다.
|
|
// 패키지 버전의 파일 목록 API는 sha256을 주므로, 큰 볼륨을 내려받지 않고도 원격 바이트가
|
|
// 로컬과 같은지 정확히 판단할 수 있다. (Range GET의 크기만 비교하면 90MiB 볼륨에서 크기가
|
|
// 우연히 같을 때 다른 바이트를 "동일"로 오판한다 — 실측 사고.)
|
|
// - 404 외의 오류는 모두 예외로 올린다(fail-closed). 원격 상태를 모르면 게시 판단을 할 수 없다.
|
|
|
|
/**
|
|
* @param {{
|
|
* feedUrl: string,
|
|
* packageApiUrl: string,
|
|
* fetchImpl: (url: string, init?: RequestInit) => Promise<Response>,
|
|
* onUploaded?: (url: string) => void,
|
|
* }} deps
|
|
* @returns {import("./portable-publish-policy.mjs").PackageRegistry & { fileUrl: (versionPath: string, name: string) => string }}
|
|
*/
|
|
export function createForgejoGenericRegistry({ feedUrl, packageApiUrl, fetchImpl, onUploaded }) {
|
|
const versionApi = (versionPath) => `${packageApiUrl}/${encodeURIComponent(versionPath)}`;
|
|
const fileUrl = (versionPath, name) =>
|
|
`${feedUrl}/${encodeURIComponent(versionPath)}/${encodeURIComponent(name)}`;
|
|
|
|
return {
|
|
fileUrl,
|
|
|
|
async listFileHashes(versionPath) {
|
|
const response = await fetchImpl(`${versionApi(versionPath)}/files`);
|
|
if (response.status === 404) return new Map();
|
|
if (!response.ok) {
|
|
throw new Error(`파일 목록 조회 실패 (HTTP ${response.status}): ${versionPath}`);
|
|
}
|
|
const files = await response.json();
|
|
if (!Array.isArray(files)) {
|
|
throw new Error(`파일 목록 응답 형식이 올바르지 않습니다: ${versionPath}`);
|
|
}
|
|
return new Map(files.map((file) => [String(file.name), String(file.sha256)]));
|
|
},
|
|
|
|
async deleteVersion(versionPath) {
|
|
const response = await fetchImpl(versionApi(versionPath), { method: "DELETE" });
|
|
if (!response.ok && response.status !== 404) {
|
|
throw new Error(`버전 삭제 실패 (HTTP ${response.status}): ${versionPath}`);
|
|
}
|
|
},
|
|
|
|
async uploadFile(versionPath, file) {
|
|
const url = fileUrl(versionPath, file.name);
|
|
const response = await fetchImpl(url, {
|
|
method: "PUT",
|
|
headers: { "Content-Type": file.contentType },
|
|
body: file.bytes,
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`업로드 실패 (HTTP ${response.status}): ${url}\n` +
|
|
" HTTP 413이면 Cloudflare 본문 한도(100MiB) 초과입니다. 볼륨 크기를 줄이세요.",
|
|
);
|
|
}
|
|
onUploaded?.(url);
|
|
},
|
|
|
|
async readTextFile(versionPath, name) {
|
|
const response = await fetchImpl(`${fileUrl(versionPath, name)}?ts=${Date.now()}`, {
|
|
cache: "no-store",
|
|
});
|
|
if (response.status === 404) return undefined;
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`게시된 파일을 읽을 수 없습니다 (HTTP ${response.status}): ${versionPath}/${name}`,
|
|
);
|
|
}
|
|
return response.text();
|
|
},
|
|
};
|
|
}
|