feat: script to post technical breakdowns

I use this for sprint initiatives very helpful
This commit is contained in:
2025-12-22 10:11:45 -08:00
parent 13504965f1
commit 1f779f7655
2 changed files with 111 additions and 3 deletions
+101
View File
@@ -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
View File
@@ -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");