generated from nhcarrigan/template
feat: add script for conditional repo uploads
This commit is contained in:
@@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
* @copyright NHCarrigan
|
||||||
|
* @license Naomi's Public License
|
||||||
|
* @author Naomi Carrigan
|
||||||
|
*/
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { input, select } from "@inquirer/prompts";
|
||||||
|
import { paginatedFetch } from "../utils/paginatedFetch.js";
|
||||||
|
import type { File, Repository } from "../interfaces/gitea.js";
|
||||||
|
|
||||||
|
const giteaToken = process.env.GITEA_TOKEN;
|
||||||
|
if (giteaToken === undefined) {
|
||||||
|
throw new Error("GITEA_TOKEN is not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
const giteaUrl = "https://git.nhcarrigan.com";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be something like "actions.yml" or "gitea/actions.yml".
|
||||||
|
*/
|
||||||
|
const fileName = await input({
|
||||||
|
message:
|
||||||
|
// eslint-disable-next-line stylistic/max-len -- Big boi string.
|
||||||
|
"Enter the name of the file to upload. Your file MUST be in the `data` directory in this repository. WITHOUT leading slash. Example: 'actions.yml' or 'gitea/actions.yml'",
|
||||||
|
});
|
||||||
|
if (fileName === "") {
|
||||||
|
throw new Error("File name is not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = await readFile(
|
||||||
|
join(import.meta.dirname, "..", "..", "data", fileName),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be something like ".gitea/workflows/security.yml".
|
||||||
|
*/
|
||||||
|
const uploadPath = await input({
|
||||||
|
message:
|
||||||
|
// eslint-disable-next-line stylistic/max-len -- Big boi string.
|
||||||
|
"Enter the PATH to upload the file to, WITHOUT leading slash. Example: '.gitea/workflows/security.yml'",
|
||||||
|
});
|
||||||
|
if (uploadPath === "") {
|
||||||
|
throw new Error("Upload path is not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file path to check for in each repo to determine if upload should proceed.
|
||||||
|
* Will be something like ".gitea/workflows/ci.yml" or "package.json".
|
||||||
|
*/
|
||||||
|
const conditionFilePath = await input({
|
||||||
|
message:
|
||||||
|
// eslint-disable-next-line stylistic/max-len -- Big boi string.
|
||||||
|
"Enter the file path to check for in each repo (condition file). WITHOUT leading slash. Example: '.gitea/workflows/ci.yml' or 'package.json'",
|
||||||
|
});
|
||||||
|
if (conditionFilePath === "") {
|
||||||
|
throw new Error("Condition file path is not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to upload when the condition file exists or doesn't exist.
|
||||||
|
*/
|
||||||
|
const uploadCondition = await select({
|
||||||
|
choices: [
|
||||||
|
{ name: "Upload if condition file EXISTS", value: "exists" },
|
||||||
|
{ name: "Upload if condition file DOES NOT EXIST", value: "not_exists" },
|
||||||
|
],
|
||||||
|
message: "When should the upload proceed?",
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgs = [ "nhcarrigan", "nhcarrigan-private", "nhcarrigan-games" ];
|
||||||
|
|
||||||
|
let totalReposProcessed = 0;
|
||||||
|
let totalReposSucceeded = 0;
|
||||||
|
let totalReposFailed = 0;
|
||||||
|
let totalReposSkipped = 0;
|
||||||
|
|
||||||
|
for (const org of orgs) {
|
||||||
|
console.log(`\n=== Fetching repositories for org: ${org} ===`);
|
||||||
|
const repos = await paginatedFetch<Array<Repository>>(`${giteaUrl}/api/v1/orgs/${org}/repos`, 100, { headers: { authorization: `Bearer ${giteaToken}` } });
|
||||||
|
console.log(`Found ${repos.length.toString()} repositories in ${org}`);
|
||||||
|
|
||||||
|
for (const repo of repos) {
|
||||||
|
totalReposProcessed = totalReposProcessed + 1;
|
||||||
|
console.log(`Checking condition file in ${org}/${repo.name}`);
|
||||||
|
|
||||||
|
// Check if condition file exists
|
||||||
|
const conditionFileResponse = await fetch(`${giteaUrl}/api/v1/repos/${org}/${repo.name}/contents/${conditionFilePath}`, {
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${giteaToken}`,
|
||||||
|
},
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
const conditionFileExists = conditionFileResponse.ok;
|
||||||
|
const shouldUpload = uploadCondition === "exists"
|
||||||
|
? conditionFileExists
|
||||||
|
: !conditionFileExists;
|
||||||
|
|
||||||
|
if (!shouldUpload) {
|
||||||
|
totalReposSkipped = totalReposSkipped + 1;
|
||||||
|
console.log(`Skipping ${org}/${repo.name} (condition file ${conditionFileExists
|
||||||
|
? "exists"
|
||||||
|
: "does not exist"}, but upload condition is "${uploadCondition}")`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Condition met for ${org}/${repo.name}, proceeding with upload`);
|
||||||
|
console.log(`Checking if upload file exists in ${org}/${repo.name}`);
|
||||||
|
const fileResponse = await fetch(`${giteaUrl}/api/v1/repos/${org}/${repo.name}/contents/${uploadPath}`, {
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${giteaToken}`,
|
||||||
|
},
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
if (fileResponse.ok) {
|
||||||
|
console.log(`File already exists in ${org}/${repo.name}`);
|
||||||
|
const fileData: File = await fileResponse.json();
|
||||||
|
console.log(`Updating ${fileName} in ${org}/${repo.name}`);
|
||||||
|
const response = await fetch(`${giteaUrl}/api/v1/repos/${org}/${repo.name}/contents/${uploadPath}`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
branch: repo.default_branch,
|
||||||
|
content: Buffer.from(file).toString("base64"),
|
||||||
|
message: `feat: automated upload of ${uploadPath}`,
|
||||||
|
sha: fileData.sha,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${giteaToken}`,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- Standard header convention.
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
method: "PUT",
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
totalReposFailed = totalReposFailed + 1;
|
||||||
|
console.error(`Failed to update ${fileName} in ${org}/${repo.name}: ${response.statusText}`);
|
||||||
|
console.error(await response.text());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
totalReposSucceeded = totalReposSucceeded + 1;
|
||||||
|
console.log(`Updated ${fileName} in ${org}/${repo.name}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
console.log(`Uploading ${fileName} to ${org}/${repo.name}`);
|
||||||
|
const response = await fetch(`${giteaUrl}/api/v1/repos/${org}/${repo.name}/contents/${uploadPath}`, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
branch: repo.default_branch,
|
||||||
|
content: Buffer.from(file).toString("base64"),
|
||||||
|
message: `feat: automated upload of ${uploadPath}`,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"authorization": `Bearer ${giteaToken}`,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- Standard header convention.
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
totalReposFailed = totalReposFailed + 1;
|
||||||
|
console.error(`Failed to upload ${fileName} to ${org}/${repo.name}: ${response.statusText}`);
|
||||||
|
console.error(await response.text());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
totalReposSucceeded = totalReposSucceeded + 1;
|
||||||
|
console.log(`Uploaded ${fileName} to ${org}/${repo.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n=== Summary ===`);
|
||||||
|
console.log(`Total repositories processed: ${totalReposProcessed.toString()}`);
|
||||||
|
console.log(`Successfully uploaded/updated: ${totalReposSucceeded.toString()}`);
|
||||||
|
console.log(`Failed: ${totalReposFailed.toString()}`);
|
||||||
|
console.log(`Skipped (condition not met): ${totalReposSkipped.toString()}`);
|
||||||
|
|
||||||
Reference in New Issue
Block a user