generated from nhcarrigan/template
feat: script to post technical breakdowns
I use this for sprint initiatives very helpful
This commit is contained in:
@@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* @copyright NHCarrigan
|
||||||
|
* @license Naomi's Public License
|
||||||
|
* @author Naomi Carrigan
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readFile, readdir } from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { Octokit } from "@octokit/rest";
|
||||||
|
|
||||||
|
if (process.env.GITHUB_TOKEN === undefined) {
|
||||||
|
throw new Error("Missing Github Token - did you run this with `op`?");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change this to the desired organization name.
|
||||||
|
*/
|
||||||
|
const orgName = "freeCodeCamp-Alpha-and-Omega";
|
||||||
|
|
||||||
|
const gh = new Octokit({
|
||||||
|
auth: process.env.GITHUB_TOKEN,
|
||||||
|
});
|
||||||
|
|
||||||
|
const storiesDirectory = join(
|
||||||
|
import.meta.dirname,
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"data",
|
||||||
|
"stories",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Read all markdown files from the stories directory
|
||||||
|
const files = await readdir(storiesDirectory);
|
||||||
|
const markdownFiles = files.filter((file) => {
|
||||||
|
return file.endsWith(".md");
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Found ${markdownFiles.length.toString()} story files to process.`);
|
||||||
|
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
for (const file of markdownFiles) {
|
||||||
|
// Parse filename: format is "{repo-name}-{issue-number}.md"
|
||||||
|
const filenameRegex = /^(?<repoName>.+)-(?<issueNumber>\d+)\.md$/u;
|
||||||
|
const match = filenameRegex.exec(file);
|
||||||
|
const groups = match?.groups;
|
||||||
|
if (!groups) {
|
||||||
|
console.error(`Skipping ${file}: filename format not recognized (expected: repo-name-issue-number.md)`);
|
||||||
|
errorCount = errorCount + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { issueNumber: issueNumberString, repoName } = groups;
|
||||||
|
if (
|
||||||
|
repoName === undefined
|
||||||
|
|| issueNumberString === undefined
|
||||||
|
|| repoName === ""
|
||||||
|
|| issueNumberString === ""
|
||||||
|
) {
|
||||||
|
console.error(`Skipping ${file}: failed to extract repo name or issue number`);
|
||||||
|
errorCount = errorCount + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const issueNumber = Number.parseInt(issueNumberString, 10);
|
||||||
|
|
||||||
|
if (Number.isNaN(issueNumber)) {
|
||||||
|
console.error(`Skipping ${file}: invalid issue number ${issueNumberString}`);
|
||||||
|
errorCount = errorCount + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the file content
|
||||||
|
const filePath = join(storiesDirectory, file);
|
||||||
|
const content = await readFile(filePath, "utf-8");
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`Updating issue #${issueNumber.toString()} in ${orgName}/${repoName}...`);
|
||||||
|
|
||||||
|
await gh.rest.issues.update({
|
||||||
|
body: content,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- API parameter name.
|
||||||
|
issue_number: issueNumber,
|
||||||
|
owner: orgName,
|
||||||
|
repo: repoName,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✓ Successfully updated issue #${issueNumber.toString()} in ${orgName}/${repoName}`);
|
||||||
|
successCount = successCount + 1;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`✗ Failed to update issue #${issueNumber.toString()} in ${orgName}/${repoName}:`, error);
|
||||||
|
errorCount = errorCount + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n=== Summary ===`);
|
||||||
|
console.log(`Total files processed: ${markdownFiles.length.toString()}`);
|
||||||
|
console.log(`Successful updates: ${successCount.toString()}`);
|
||||||
|
console.log(`Errors: ${errorCount.toString()}`);
|
||||||
|
|
||||||
+10
-3
@@ -4,6 +4,7 @@
|
|||||||
* @author Naomi Carrigan
|
* @author Naomi Carrigan
|
||||||
*/
|
*/
|
||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||||
import { input } from "@inquirer/prompts";
|
import { input } from "@inquirer/prompts";
|
||||||
|
|
||||||
@@ -15,16 +16,22 @@ if (accessKeyId === undefined || secretAccessKey === undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fileName = await input({
|
const fileName = await input({
|
||||||
message: "Enter the ABSOLUTE PATH of the file to upload, including leading slash:",
|
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: 'naomi.png' or 'img/naomi.png'",
|
||||||
});
|
});
|
||||||
if (fileName === "") {
|
if (fileName === "") {
|
||||||
throw new Error("File name is not set");
|
throw new Error("File name is not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = await readFile(fileName);
|
const file = await readFile(
|
||||||
|
join(import.meta.dirname, "..", "..", "data", fileName),
|
||||||
|
);
|
||||||
|
|
||||||
const uploadPath = await input({
|
const uploadPath = await input({
|
||||||
message: "Enter the PATH to upload the file to, WITHOUT leading slash:",
|
message:
|
||||||
|
// eslint-disable-next-line stylistic/max-len -- Big boi string.
|
||||||
|
"Enter the PATH to upload the file to, WITHOUT leading slash. Example: 'img/naomi.png' or 'naomi.png':",
|
||||||
});
|
});
|
||||||
if (uploadPath === "") {
|
if (uploadPath === "") {
|
||||||
throw new Error("Upload path is not set");
|
throw new Error("Upload path is not set");
|
||||||
|
|||||||
Reference in New Issue
Block a user