149 lines
3.9 KiB
TypeScript
149 lines
3.9 KiB
TypeScript
import { Octokit } from '@octokit/rest'
|
|
import type { RemoteFile } from './types'
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Octokit helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function getOctokit(): Octokit {
|
|
const token = process.env.GITHUB_TOKEN
|
|
if (!token) throw new Error('GITHUB_TOKEN non configure')
|
|
return new Octokit({ auth: token })
|
|
}
|
|
|
|
function parseRepoUrl(): { owner: string; repo: string } {
|
|
const url = process.env.GIT_REPO_URL
|
|
if (!url) throw new Error('GIT_REPO_URL non configure')
|
|
|
|
const httpsMatch = url.match(/github\.com\/([^/]+)\/([^/.]+)/)
|
|
const sshMatch = url.match(/github\.com:([^/]+)\/([^/.]+)/)
|
|
const shortMatch = url.match(/^([^/]+)\/([^/]+)$/)
|
|
|
|
const match = httpsMatch || sshMatch || shortMatch
|
|
if (!match) throw new Error(`Format GIT_REPO_URL invalide: "${url}"`)
|
|
|
|
return { owner: match[1], repo: match[2] }
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Read remote folder contents (with size per file)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function getRemoteFolder(
|
|
folderPath: string,
|
|
): Promise<{ exists: boolean; files: RemoteFile[] }> {
|
|
const octokit = getOctokit()
|
|
const { owner, repo } = parseRepoUrl()
|
|
const branch = process.env.GIT_BRANCH ?? 'main'
|
|
|
|
try {
|
|
const { data } = await octokit.repos.getContent({
|
|
owner,
|
|
repo,
|
|
path: folderPath,
|
|
ref: branch,
|
|
})
|
|
|
|
if (Array.isArray(data)) {
|
|
return {
|
|
exists: true,
|
|
files: data.map((f) => ({ name: f.name, size: f.size })),
|
|
}
|
|
}
|
|
|
|
return { exists: false, files: [] }
|
|
} catch (err: unknown) {
|
|
const status = (err as { status?: number })?.status
|
|
if (status === 404) {
|
|
return { exists: false, files: [] }
|
|
}
|
|
throw err
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Push all files in a single commit (with optional deletions)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function pushAllToGitHub(
|
|
files: { path: string; contentBase64: string }[],
|
|
deletePaths: string[],
|
|
commitMessage: string,
|
|
): Promise<{ commitUrl: string }> {
|
|
const octokit = getOctokit()
|
|
const { owner, repo } = parseRepoUrl()
|
|
const branch = process.env.GIT_BRANCH ?? 'main'
|
|
|
|
// 1. Get latest commit on branch
|
|
const { data: ref } = await octokit.git.getRef({
|
|
owner,
|
|
repo,
|
|
ref: `heads/${branch}`,
|
|
})
|
|
const latestCommitSha = ref.object.sha
|
|
|
|
// 2. Get that commit's tree
|
|
const { data: commit } = await octokit.git.getCommit({
|
|
owner,
|
|
repo,
|
|
commit_sha: latestCommitSha,
|
|
})
|
|
|
|
// 3. Create all blobs in parallel
|
|
const blobResults = await Promise.all(
|
|
files.map((f) =>
|
|
octokit.git.createBlob({
|
|
owner,
|
|
repo,
|
|
content: f.contentBase64,
|
|
encoding: 'base64',
|
|
}),
|
|
),
|
|
)
|
|
|
|
// 4. Build tree entries: new/changed files + deletions
|
|
const newFilePaths = new Set(files.map((f) => f.path))
|
|
const deleteEntries = deletePaths
|
|
.filter((p) => !newFilePaths.has(p))
|
|
.map((p) => ({
|
|
path: p,
|
|
mode: '100644' as const,
|
|
type: 'blob' as const,
|
|
sha: null,
|
|
}))
|
|
|
|
const { data: newTree } = await octokit.git.createTree({
|
|
owner,
|
|
repo,
|
|
base_tree: commit.tree.sha,
|
|
tree: [
|
|
...files.map((f, i) => ({
|
|
path: f.path,
|
|
mode: '100644' as const,
|
|
type: 'blob' as const,
|
|
sha: blobResults[i].data.sha,
|
|
})),
|
|
...deleteEntries,
|
|
],
|
|
})
|
|
|
|
// 5. Create a single commit
|
|
const { data: newCommit } = await octokit.git.createCommit({
|
|
owner,
|
|
repo,
|
|
message: commitMessage,
|
|
tree: newTree.sha,
|
|
parents: [latestCommitSha],
|
|
})
|
|
|
|
// 6. Update branch ref
|
|
await octokit.git.updateRef({
|
|
owner,
|
|
repo,
|
|
ref: `heads/${branch}`,
|
|
sha: newCommit.sha,
|
|
})
|
|
|
|
return { commitUrl: newCommit.html_url }
|
|
}
|