import * as yaml from "js-yaml"; import { err, ok, Result } from "neverthrow"; import { appError, AppError, AppErrorVariant } from "@/error"; import { EMPTY_PROJECT_CONFIG, normalizeTag, type ProjectConfig, } from "@/model/projectConfig"; import { isTaskStatus } from "@/model/taskStatus"; function parseShownHiddenColumns( value: unknown, ): Result { if (value === undefined) return ok([]); if (!Array.isArray(value)) { return err( appError(AppErrorVariant.INVALID_PROJECT_CONFIG, { detail: "shownHiddenColumns must be an array of strings", }), ); } for (const item of value) { if (typeof item !== "string" || !isTaskStatus(item)) { return err( appError(AppErrorVariant.INVALID_PROJECT_CONFIG, { detail: `invalid status in shownHiddenColumns: ${item}`, }), ); } } return ok(value as string[]); } export function parseProjectConfig( content: string, ): Result { if (content.trim() === "") { return ok({ ...EMPTY_PROJECT_CONFIG }); } let data: unknown; try { data = yaml.load(content); } catch (error) { const detail = error instanceof Error ? error.message : "invalid YAML"; return err(appError(AppErrorVariant.INVALID_PROJECT_CONFIG, { detail })); } if (data === null || data === undefined) { return ok({ ...EMPTY_PROJECT_CONFIG }); } if (typeof data !== "object" || Array.isArray(data)) { return err( appError(AppErrorVariant.INVALID_PROJECT_CONFIG, { detail: "root must be a mapping", }), ); } const record = data as Record; let tags: string[]; if (record.tags === undefined) { tags = []; } else { if (!Array.isArray(record.tags)) { return err( appError(AppErrorVariant.INVALID_PROJECT_CONFIG, { detail: "tags must be an array of strings", }), ); } tags = []; const seen = new Set(); for (const item of record.tags) { if (typeof item !== "string") { return err( appError(AppErrorVariant.INVALID_PROJECT_CONFIG, { detail: "tags must be an array of strings", }), ); } const tag = normalizeTag(item); if (!tag || seen.has(tag)) continue; seen.add(tag); tags.push(tag); } } const shownHiddenColumnsResult = parseShownHiddenColumns( record.shownHiddenColumns, ); if (shownHiddenColumnsResult.isErr()) return err(shownHiddenColumnsResult.error); return ok({ tags, shownHiddenColumns: shownHiddenColumnsResult.value, }); }