From 1f779f765555c60615d5809e220bf5fd02cbe2d4 Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Mon, 22 Dec 2025 10:11:45 -0800 Subject: [PATCH] feat: script to post technical breakdowns I use this for sprint initiatives very helpful --- src/github/postUserStories.ts | 101 ++++++++++++++++++++++++++++++++++ src/s3/upload.ts | 13 ++++- 2 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 src/github/postUserStories.ts diff --git a/src/github/postUserStories.ts b/src/github/postUserStories.ts new file mode 100644 index 0000000..c2cc618 --- /dev/null +++ b/src/github/postUserStories.ts @@ -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 = /^(?.+)-(?\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()}`); + diff --git a/src/s3/upload.ts b/src/s3/upload.ts index b727156..d5f9cd1 100644 --- a/src/s3/upload.ts +++ b/src/s3/upload.ts @@ -4,6 +4,7 @@ * @author Naomi Carrigan */ import { readFile } from "node:fs/promises"; +import { join } from "node:path"; import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; import { input } from "@inquirer/prompts"; @@ -15,16 +16,22 @@ if (accessKeyId === undefined || secretAccessKey === undefined) { } 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 === "") { 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({ - 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 === "") { throw new Error("Upload path is not set");