Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 52 additions & 22 deletions code/check.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
import { readFileSync, readdirSync, watch } from "node:fs";
import { access } from "node:fs/promises";
import path from "node:path";
import { type ContributorSchema as ContributorSchemaType } from "../contributors/_schema";
import { createJiti } from "jiti";

import { parse } from "yaml";

import { ContributorSchema as ContributorFunction } from "../contributors/_schema";
import z from "zod";

const jiti = createJiti(import.meta.url);

async function getContributor() {
// Clear schema cache to pick up changes
Object.keys(jiti.cache).forEach((key) => {
if (key.includes("_schema")) {
delete jiti.cache[key];
}
});
const { ContributorSchema } = (await jiti.import(
"../contributors/_schema"
)) as { ContributorSchema: typeof ContributorSchemaType };
// @ts-expect-error This is fucked up because it's type of image in Astro
return ContributorSchema({ image: z.string });
}

const TEAM_DIR = path.resolve(process.cwd(), "contributors");
const PROJECTS_FILE = path.resolve(process.cwd(), "contributors/_schema/projects.ts");
const SCHEMA_FILE = path.resolve(process.cwd(), "contributors/_schema/index.ts");
const PROJECTS_FILE = path.resolve(
process.cwd(),
"contributors/_schema/projects.ts"
);
const SCHEMA_FILE = path.resolve(
process.cwd(),
"contributors/_schema/index.ts"
);

// Global abort controller to manage running validations
let currentValidationController: AbortController | null = null;
Expand All @@ -21,9 +44,9 @@ interface ValidationResult {
}

class InvalidRoleError extends Error {
constructor(projectName: string, filePath: string) {
constructor(roleName: string, projectName: string, filePath: string) {
super(
`Invalid role in project "${projectName}" for file ./${path.relative(
`Invalid role ${roleName} in project "${projectName}" for file ./${path.relative(
process.cwd(),
filePath
)}.`
Expand All @@ -42,9 +65,6 @@ class InvalidProjectError extends Error {
}
}

// @ts-expect-error This is fucked up because it's type of image in Astro
const Contributor = ContributorFunction({ image: z.string})

function formatValidationError(error: any, filePath: string): string {
if (error.code === "unrecognized_keys") {
if (error.path.length === 1 && error.path[0].includes("roles")) {
Expand All @@ -57,7 +77,11 @@ function formatValidationError(error: any, filePath: string): string {
const path = error.path.join(".");
if (path.includes("roles.")) {
const projectName = path.split(".")[1];
throw new InvalidRoleError(projectName, filePath);
const roleName = error.unionErrors
// TODO: figure out zod typings
.flatMap((e: any) => e.issues)
.find((issue: any) => issue.code == "invalid_enum_value")?.received;
throw new InvalidRoleError(roleName, projectName, filePath);
}
}

Expand All @@ -84,6 +108,7 @@ async function validateTeamFile(

const content = readFileSync(filePath, "utf-8");
const data = parse(content);
const Contributor = await getContributor();

const validation = await Contributor.superRefine(
async (contributor, ctx) => {
Expand Down Expand Up @@ -149,8 +174,13 @@ function validateAllTeamFiles(signal?: AbortSignal) {
return results;
}

function printResults(results: ValidationResult[]): void {
console.clear();
async function printResults(
results: ValidationResult[],
isWatchMode: boolean = false
) {
if (isWatchMode) {
console.clear();
}
const hasErrors = results.some((r) => r.errors.length > 0);

if (hasErrors) {
Expand All @@ -174,6 +204,7 @@ function printResults(results: ValidationResult[]): void {
);

if (hasErrors) {
const Contributor = await getContributor();
console.log("\n💡 Tips:");
console.log(
` • Project names and roles must match those in ./${path.relative(
Expand All @@ -200,7 +231,9 @@ async function runValidation(isWatchMode: boolean = false) {
const signal = currentValidationController.signal;

try {
console.clear();
if (isWatchMode) {
console.clear();
}
console.log("Running validation...");

// Needs to wait or it's not clear anything is happening
Expand All @@ -209,7 +242,7 @@ async function runValidation(isWatchMode: boolean = false) {
const results = await Promise.all(await validateAllTeamFiles(signal));
const hasErrors = results.some((r) => !r.isValid);

printResults(await Promise.all(results));
await printResults(await Promise.all(results));
if (isWatchMode) {
console.log("Waiting for more changes...");
}
Expand All @@ -230,36 +263,33 @@ function startWatchMode(): void {
console.log("Time to watch 👀");

// Run initial validation
runValidation();
runValidation(true);

const watchers: Array<{ close: () => void }> = [];

const teamWatcher = watch(
TEAM_DIR,
{ recursive: true },
(eventType, filename) => {
console.log(`${eventType}: ${filename}`);
if (
filename &&
(filename.endsWith(".yaml") || filename.endsWith(".yml"))
) {
runValidation();
runValidation(true);
}
}
);
watchers.push(teamWatcher);

// Watch projects file
const projectsWatcher = watch(PROJECTS_FILE, (eventType) => {
console.log(`${eventType}: ${PROJECTS_FILE}`);
runValidation();
const projectsWatcher = watch(PROJECTS_FILE, () => {
runValidation(true);
});
watchers.push(projectsWatcher);

// Watch schema file
const schemaWatcher = watch(SCHEMA_FILE, (eventType) => {
console.log(`${eventType}: ${SCHEMA_FILE}`);
runValidation();
const schemaWatcher = watch(SCHEMA_FILE, () => {
runValidation(true);
});
watchers.push(schemaWatcher);

Expand Down
24 changes: 11 additions & 13 deletions contributors/_schema/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ export const PROJECTS = [
"Volume 0 Issue 1",
"Fandom Cookies",
"FujoCoded",
"Learn@",
] as const;
export type Project = typeof PROJECTS[number];
export type Project = (typeof PROJECTS)[number];

export const VOLUME_0_ISSUE_1_ROLES = [
"Technical Writer",
Expand All @@ -25,15 +26,9 @@ export const VOLUME_0_ISSUE_1_ROLES = [
"Data Collection & Entry",
];

export const WEBSITES_ROLES = [
"fujoweb.dev",
"fujocoded.com",
];
export const WEBSITES_ROLES = ["fujoweb.dev", "fujocoded.com"];

export const FUJOCODED_ROLES = [
"HimeOps",
"FujoCoded",
];
export const FUJOCODED_ROLES = ["HimeOps", "FujoCoded"];

export const FANDOM_COOKIES_ROLES = [
"Artist",
Expand All @@ -48,16 +43,19 @@ export const FANDOM_COOKIES_ROLES = [
"Art Direction",
"Cookie Catcher Design",
"Promo Campaign Assistance",
"Feedback",
"Feedback",
];

export const PROJECT_ROLES : Record<Project, string[]> = {
export const LEARN_AT_ROLES = ["Writers Coordinator", "Article Writer"];

export const PROJECT_ROLES: Record<Project, string[]> = {
"Volume 0 Issue 1": VOLUME_0_ISSUE_1_ROLES,
"Volume 0 Kickstarter": [],
"Volume 0": [],
"Websites": WEBSITES_ROLES,
Websites: WEBSITES_ROLES,
"Fandom Cookies": FANDOM_COOKIES_ROLES,
"FujoCoded": FUJOCODED_ROLES,
FujoCoded: FUJOCODED_ROLES,
"Learn@": LEARN_AT_ROLES,
};

export default PROJECTS;
Binary file added contributors/avatars/rie.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions contributors/rie.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Rie
avatar: "./avatars/rie.png"
type:
- community
- contractor
roles:
Learn@:
- role: Writers Coordinator
details: Style Guide Author & Process Coordinator
- role: Article Writer
details: Introduction to NodeJS
contacts:
- https://notavodkashot.carrd.co/
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"dependencies": {
"@fujocoded/zod-transform-socials": "^0.0.12",
"jiti": "^2.6.1",
"zod": "^3.25.76"
},
"devDependencies": {
Expand Down