diff --git a/data/projects.yml b/data/projects.yml index fb5afcd..d68f05f 100644 --- a/data/projects.yml +++ b/data/projects.yml @@ -161,7 +161,7 @@ name: Lucinda premium: false url: 'https://lucinda.nhcarrigan.com' - wip: false + wip: true - avatar: null category: websites description: Our homepage and marketing landing. @@ -517,4 +517,74 @@ name: Meridia premium: false url: null + wip: true +- avatar: 'https://cdn.nhcarrigan.com/new-avatars/a4p.png' + category: community + description: A custom Discord bot for the Artists4Palestine charity initiative. + name: Artists4Palestine Bot + premium: false + url: null + wip: false +- avatar: null + category: community + description: A custom Discord bot for Caylus that removes our booster colour role when someone stops boosting. + name: Boost Monitor + premium: false + url: null + wip: false +- avatar: null + category: apps + description: Our custom ESLint rules. + name: ESLint Config + premium: false + url: "https://www.npmjs.com/package/@nhcarrigan/eslint-config" + wip: false +- avatar: "https://cdn.nhcarrigan.com/new-avatars/celestine.png" + category: community + description: A powerful moderation bot for Discord. + name: Celestine + premium: true + url: "https://celestine.nhcarrigan.com" + wip: false +- avatar: null + category: websites + description: Our custom script that injects our global styles and scripts into our websites. + name: Website Headers + premium: false + url: "https://cdn.nhcarrigan.com/headers/indexjs" + wip: false +- avatar: null + category: apps + description: Our custom TypeScript configuration. + name: TypeScript Config + premium: false + url: "https://www.npmjs.com/package/@nhcarrigan/typescript-config" + wip: false +- avatar: null + category: apps + description: Our custom logger library. + name: Logger + premium: false + url: "https://www.npmjs.com/package/@nhcarrigan/logger" + wip: false +- avatar: null + category: websites + description: A collection of static pages bundled via custom scripts. + name: Static Pages + premium: false + url: null + wip: false +- avatar: null + category: websites + description: A printable web version of our resume. + name: Resume + premium: false + url: "https://resume.nhcarrigan.com" + wip: false +- avatar: null + category: apps + description: An Isekai story, and our first paid game. + name: "Naomi's Adventure I: An Isekai Story" + premium: true + url: null wip: true \ No newline at end of file diff --git a/test/projects.spec.ts b/test/projects.spec.ts new file mode 100644 index 0000000..6201b9d --- /dev/null +++ b/test/projects.spec.ts @@ -0,0 +1,174 @@ +/* eslint-disable @typescript-eslint/naming-convention -- We are dealing with repository names, which are in kebab-case */ +/** + * @copyright NHCarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { parse } from "yaml"; +import type { Projects } from "../src/interfaces/projects.js"; + +interface Repository { + id: number; + owner: { + id: number; + login: string; + login_name: string; + source_id: number; + full_name: string; + email: string; + avatar_url: string; + html_url: string; + language: string; + is_admin: boolean; + last_login: string; + created: string; + restricted: boolean; + active: boolean; + prohibit_login: boolean; + location: string; + website: string; + description: string; + visibility: string; + followers_count: number; + following_count: number; + starred_repos_count: number; + username: string; + }; + name: string; + full_name: string; + description: string; + empty: boolean; + private: boolean; + fork: boolean; + template: boolean; + mirror: boolean; + size: number; + language: string; + languages_url: string; + html_url: string; + url: string; + link: string; + ssh_url: string; + clone_url: string; + original_url: string; + website: string; + stars_count: number; + forks_count: number; + watchers_count: number; + open_issues_count: number; + open_pr_counter: number; + release_counter: number; + default_branch: string; + archived: boolean; + created_at: string; + updated_at: string; + archived_at: string; + permissions: { + admin: boolean; + push: boolean; + pull: boolean; + }; + has_issues: boolean; + internal_tracker: { + enable_time_tracker: boolean; + allow_only_contributors_to_track_time: boolean; + enable_issue_dependencies: boolean; + }; + has_wiki: boolean; + has_pull_requests: boolean; + has_projects: boolean; + projects_mode: string; + has_releases: boolean; + has_packages: boolean; + has_actions: boolean; + ignore_whitespace_conflicts: boolean; + allow_merge_commits: boolean; + allow_rebase: boolean; + allow_rebase_explicit: boolean; + allow_squash_merge: boolean; + allow_fast_forward_only_merge: boolean; + allow_rebase_update: boolean; + default_delete_branch_after_merge: boolean; + default_merge_style: string; + default_allow_maintainer_edit: boolean; + avatar_url: string; + internal: boolean; + mirror_interval: string; + object_format_name: string; + mirror_updated: string; + topics: Array; + licenses: Array; +} +const getRepositories = async(): Promise> => { + const repos: Array = []; + const orgs = [ + "nhcarrigan", + "nhcarrigan-games", + ]; + for (const org of orgs) { + const response = await fetch(`https://git.nhcarrigan.com/api/v1/orgs/${org}/repos`); + const data = await response.json(); + repos.push(...data); + } + return repos; +}; + +const repoNameMap = { + "a4p-bot": "Artists4Palestine Bot", + "beccalia-origins": "Beccalia: Origins", + "beccalia-prologue": "Beccalia: Prologue", + "blog": "Naomi's Blog", + "docs": "NHCarrigan Documentation", + "eslint-config": "ESLint Config", + "life-of-a-naomi": "Life of a Naomi", + "naomis-adventure-1": "Naomi's Adventure I: An Isekai Story", + "ruu-goblin-quest": "Ruu's Goblin Quest", + "typescript-config": "TypeScript Config", + "vscode-themes": "Naomi's VSCode Themes", +}; + +const excludedRepos = new Set([ + "template", + "espanso", + "rig-task-bot", + "security", + "nginx-configs", + ".profile", + ".gitea", +]); + +const convertKebabCaseToTitleCase = (string_: string): string => { + return string_.replaceAll("-", " ").replaceAll(/\b\w/g, (char) => { + return char.toUpperCase(); + }); +}; + +describe("projects data", () => { + it("should include all repositories", async() => { + expect.hasAssertions(); + const repos = await getRepositories(); + const data = await readFile( + join(import.meta.dirname, "..", "data", "projects.yml"), + "utf8", + ); + const parsed = parse(data) as Projects; + expect(parsed, `Parsed projects data should be defined`).toBeDefined(); + expect(Array.isArray(parsed), `Parsed projects data should be an array`).toBeTruthy(); + expect(parsed.length, `There should be at least one project`).toBeGreaterThan(0); + for (const repo of repos) { + if (excludedRepos.has(repo.name)) { + continue; + } + const project = parsed.find((p) => { + return repo.name in repoNameMap + ? p.name === repoNameMap[repo.name] + : p.name === convertKebabCaseToTitleCase(repo.name); + }); + expect(project, `Project should be defined for repository ${convertKebabCaseToTitleCase(repo.name)}`).toBeDefined(); + } + }); +});