feat: add mentorship onboarding
CI / dependency-pin-check-typescript (pull_request) Successful in 4s
CI / dependency-pin-check-python (pull_request) Successful in 4s
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 55s
CI / python (pull_request) Successful in 9m24s
CI / typescript (pull_request) Successful in 9m41s

This commit is contained in:
2026-02-03 10:46:12 -08:00
parent f5e8deca59
commit c6eaa4ff9c
2 changed files with 210 additions and 1 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ CROWDIN_TOKEN="op://Environment Variables - Development/Ephemere/Crowdin Token"
GITHUB_TOKEN="op://Environment Variables - Development/Ephemere/GitHub Token"
# Discord
DISCORD_TOKEN="op://Environment Variables - Development/Ephemere/Discord Token"
DISCORD_TOKEN="op://Environment Variables - Naomi/Hikari/discord_token"
DISCORD_CLIENT_ID="op://Private/Guild Counter/client id"
DISCORD_CLIENT_SECRET="op://Private/Guild Counter/client secret"
DISCORD_BOT_TOKEN="op://Environment Variables - Naomi/Amari/bot token"
+209
View File
@@ -0,0 +1,209 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { input } from "@inquirer/prompts";
import { Octokit } from "@octokit/rest";
// Environment variable checks
const githubToken = process.env.GITHUB_TOKEN;
const discordToken = process.env.DISCORD_TOKEN;
if (githubToken === undefined || githubToken === "") {
throw new Error("GITHUB_TOKEN is not set");
}
if (discordToken === undefined || discordToken === "") {
throw new Error("DISCORD_TOKEN is not set");
}
const octokit = new Octokit({ auth: githubToken });
/**
* Converts a full name to kebab-case.
* @param fullName - The full name to convert.
* @returns The kebab-case version.
*/
function toKebabCase(fullName: string): string {
return fullName.trim().toLowerCase().
replaceAll(/[^\d\sa-z-]/g, "").
replaceAll(/\s+/g, "-");
}
/**
* Prompts for mentee information.
* @returns The mentee information.
*/
async function getMenteeInfo(): Promise<{
discordId: string;
fullName: string;
githubUsername: string;
}> {
const discordId = await input({
message: "Enter the mentee's Discord ID:",
validate: (value) => {
const trimmed = value.trim();
if (trimmed === "") {
return "Discord ID cannot be empty";
}
if (!/^\d+$/.test(trimmed)) {
return "Discord ID must be numeric";
}
return true;
},
});
const fullName = await input({
message: "Enter the mentee's full name:",
validate: (value) => {
if (value.trim() === "") {
return "Full name cannot be empty";
}
return true;
},
});
const githubUsername = await input({
message: "Enter the mentee's GitHub username:",
validate: (value) => {
if (value.trim() === "") {
return "GitHub username cannot be empty";
}
return true;
},
});
return { discordId, fullName, githubUsername };
}
interface RepoData {
/**
* Using camelCase interface for internal consistency.
*/
htmlUrl: string;
}
/**
* Creates a public GitHub repository in the nhcarrigan-mentorship organization with auto-init enabled.
* @param repoName - The kebab-case repository name derived from mentee's name.
* @param fullName - The mentee's full name used in repository description.
* @returns The created repository data with HTML URL.
*/
async function createRepository(
repoName: string,
fullName: string,
): Promise<RepoData> {
console.log("\n1️⃣ Creating repository...");
const { data: repo } = await octokit.rest.repos.createInOrg({
// eslint-disable-next-line @typescript-eslint/naming-convention -- GitHub API
auto_init: true,
description: `Mentorship repository for ${fullName}`,
name: repoName,
org: "nhcarrigan-mentorship",
private: false,
});
console.log("✅ Repository created successfully!");
return { htmlUrl: repo.html_url };
}
/**
* Adds the mentee as a collaborator.
* @param repoName - The repository name.
* @param githubUsername - The mentee's GitHub username.
*/
async function addCollaborator(
repoName: string,
githubUsername: string,
): Promise<void> {
console.log("\n2️⃣ Adding collaborator...");
await octokit.rest.repos.addCollaborator({
owner: "nhcarrigan-mentorship",
permission: "maintain",
repo: repoName,
username: githubUsername,
});
console.log("✅ Collaborator added with maintain permissions!");
}
/**
* Sends a welcome message to Discord.
* @param discordId - The mentee's Discord ID.
* @param repoUrl - The repository URL.
*/
async function sendDiscordMessage(
discordId: string,
repoUrl: string,
): Promise<void> {
console.log("\n3️⃣ Sending Discord welcome message...");
const channelId = "1400589073613062204";
const welcomeMessage = {
content: `<@${discordId}> Welcome to the mentorship programme! 🎉\n\nYour personal repository has been created: ${repoUrl}\n\nYou have been added as a collaborator with maintain permissions. Feel free to use this space to practice, experiment, and work on projects. I'm here to help guide you on your journey!\n\nLooking forward to working with you! 💖`,
};
const discordResponse = await fetch(
`https://discord.com/api/v10/channels/${channelId}/messages`,
{
body: JSON.stringify(welcomeMessage),
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention -- Discord API
"Authorization": `Bot ${String(discordToken)}`,
// eslint-disable-next-line @typescript-eslint/naming-convention -- HTTP headers
"Content-Type": "application/json",
},
method: "POST",
},
);
if (!discordResponse.ok) {
const error = await discordResponse.text();
throw new Error(`Discord API error: ${error}`);
}
console.log("✅ Discord welcome message sent!");
}
/**
* Main function to onboard a new mentee.
*/
async function main(): Promise<void> {
console.log("🚀 Starting mentorship onboarding process...\n");
try {
const { discordId, fullName, githubUsername } = await getMenteeInfo();
const repoName = toKebabCase(fullName);
console.log(`\n📦 Repository will be created as: nhcarrigan-mentorship/${repoName}`);
const repo = await createRepository(repoName, fullName);
await addCollaborator(repoName, githubUsername);
await sendDiscordMessage(discordId, repo.htmlUrl);
// Success summary
console.log("\n🎊 Onboarding completed successfully!");
console.log(`\n📋 Summary:`);
console.log(` Mentee: ${fullName} (@${githubUsername})`);
console.log(` Discord ID: ${discordId}`);
console.log(` Repository: ${repo.htmlUrl}`);
console.log(` Permissions: Maintain`);
console.log(` Welcome message sent to Discord channel`);
} catch (error) {
console.error("\n❌ Error during onboarding:");
if (error instanceof Error) {
console.error(error.message);
} else {
console.error(error);
}
process.exit(1);
}
}
// Run the script
await main().catch((error: unknown) => {
console.error("❌ Unexpected error:", error);
process.exit(1);
});