From 8b276cc22627f0664a9fbb48b7095eb9fc44fe54 Mon Sep 17 00:00:00 2001 From: Endeavorance Date: Mon, 24 Feb 2025 14:03:04 -0500 Subject: [PATCH] Clean up --- bun.lock | 4 +- package.json | 2 +- src/binding.ts | 39 +++++++++++------ src/index.ts | 106 +++++++++++++++++++++------------------------ src/resource.ts | 113 +++++++++++++++++++++++++++++------------------- 5 files changed, 145 insertions(+), 119 deletions(-) diff --git a/bun.lock b/bun.lock index d2e5706..0b3f09a 100644 --- a/bun.lock +++ b/bun.lock @@ -4,7 +4,7 @@ "": { "name": "@proscenium/muse", "dependencies": { - "@proscenium/playbill": "0.0.2", + "@proscenium/playbill": "0.0.5", "chalk": "^5.4.1", "yaml": "^2.7.0", "zod": "^3.24.1", @@ -37,7 +37,7 @@ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], - "@proscenium/playbill": ["@proscenium/playbill@0.0.2", "https://git.astral.camp/api/packages/proscenium/npm/%40proscenium%2Fplaybill/-/0.0.2/playbill-0.0.2.tgz", { "peerDependencies": { "typescript": "^5" } }, "sha512-zoYCHbm7DGmDRbB+tkQGPQTA5eR0xDHfyv+cp+3JfLy8n/6hOJvx+pG51emD1jBP+CecUE819nFriJqDf+ow0A=="], + "@proscenium/playbill": ["@proscenium/playbill@0.0.5", "https://git.astral.camp/api/packages/proscenium/npm/%40proscenium%2Fplaybill/-/0.0.5/playbill-0.0.5.tgz", { "peerDependencies": { "typescript": "^5" } }, "sha512-4TzEe3LpZ2WAep/OWtuNoGiRb2NilfBBN9KQ65/sYSkmBq4Wtdploqo/7+R1Twja/AvOBGgw+Art12beywhqtQ=="], "@types/bun": ["@types/bun@1.2.3", "", { "dependencies": { "bun-types": "1.2.3" } }, "sha512-054h79ipETRfjtsCW9qJK8Ipof67Pw9bodFWmkfkaUaRiIQ1dIV2VTlheshlBx3mpKr0KeK8VqnMMCtgN9rQtw=="], diff --git a/package.json b/package.json index 957d4b9..fd5a9de 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "chalk": "^5.4.1", "yaml": "^2.7.0", "zod": "^3.24.1", - "@proscenium/playbill": "0.0.2" + "@proscenium/playbill": "0.0.5" }, "scripts": { "fmt": "bunx --bun biome check --fix", diff --git a/src/binding.ts b/src/binding.ts index 957c37f..2a734fc 100644 --- a/src/binding.ts +++ b/src/binding.ts @@ -3,31 +3,36 @@ import { Glob } from "bun"; import YAML from "yaml"; import z from "zod"; -const BindingSchema = z.object({ +const BindingFileSchema = z.object({ $binding: z.literal("playbill"), id: z.string(), name: z.string().default("Unnamed playbill"), author: z.string().optional(), + description: z.string().optional(), version: z.string(), files: z.array(z.string()).default(["**/*.yaml", "**/*.md"]), }); -type Binding = z.infer; +type BindingFileContent = z.infer; export interface PlaybillBinding { - info: Binding; - bindingPath: string; - files: string[]; + _raw: BindingFileContent; + bindingFilePath: string; + includedFiles: string[]; + + id: string; + name: string; + author: string; + version: string; + description: string; } -export async function loadBindingFile( +export function parseBindingFile( + text: string, filePath: string, -): Promise { - const file = Bun.file(filePath); - const text = await file.text(); +): PlaybillBinding { const parsed = YAML.parse(text); - const binding = BindingSchema.parse(parsed); - + const binding = BindingFileSchema.parse(parsed); const fileGlobs = binding.files; const bindingFileDirname = filePath.split("/").slice(0, -1).join("/"); @@ -52,8 +57,14 @@ export async function loadBindingFile( }); return { - info: binding, - bindingPath: filePath, - files: filteredFilePaths, + _raw: binding, + bindingFilePath: filePath, + includedFiles: filteredFilePaths, + + id: binding.id, + name: binding.name, + author: binding.author ?? "Anonymous", + description: binding.description ?? "", + version: binding.version, }; } diff --git a/src/index.ts b/src/index.ts index 77e3b0e..8240cb1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,20 +1,34 @@ import { parseArgs } from "node:util"; import { type AnyResource, - DirectiveError, ValidatedPlaybillSchema, - compileScript, getEmptyPlaybill, } from "@proscenium/playbill"; import chalk from "chalk"; -import { loadBindingFile } from "./binding"; +import { parseBindingFile } from "./binding"; import { loadResourceFile } from "./resource"; import { usage } from "./usage"; -class CLIError extends Error {} +enum ExitCode { + Success = 0, + Error = 1, +} -async function main(): Promise { - // Parse command line arguments +class CLIError extends Error { } + +async function loadFileOrFail(filePath: string): Promise { + const fileToLoad = Bun.file(filePath); + const fileExists = await fileToLoad.exists(); + + if (!fileExists) { + throw new Error(`File not found: ${filePath}`); + } + + return await fileToLoad.text(); +} + +async function main(): Promise { + // -- ARGUMENT PARSING -- // const { values: options, positionals: args } = parseArgs({ args: Bun.argv.slice(2), options: { @@ -53,11 +67,13 @@ async function main(): Promise { allowPositionals: true, }); + // -- HELP TEXT -- // if (options.help) { console.log(usage); - return true; + return ExitCode.Success; } + // -- ARG VALIDATION -- // if (options.check && options.write) { throw new CLIError("Cannot use --check and --write together"); } @@ -74,30 +90,22 @@ async function main(): Promise { } } + // -- BINDING FILE -- // const bindingPath = args[0] ?? "./binding.yaml"; verboseLog(`Building Playbill with binding: ${bindingPath}`); - // Check if the binding file exists - const bindingFileCheck = Bun.file(bindingPath); - const bindingFileExists = await bindingFileCheck.exists(); - - if (!bindingFileExists) { - throw new Error(`Binding file not found: ${bindingPath}`); - } - - const binding = await loadBindingFile(bindingPath); + const bindingFileContent = await loadFileOrFail(bindingPath); + const binding = parseBindingFile(bindingFileContent, bindingPath); verboseLog( - `↳ Binding loaded with ${binding.files.length} associated resources`, + `↳ Binding loaded with ${binding.includedFiles.length} associated resources`, ); - const loadedResources: AnyResource[] = []; - + // -- RESOURCE FILES -- // verboseLog("Loading resources"); - - // Load resources listed in the binding file - for (const filepath of binding.files) { + const loadedResources: AnyResource[] = []; + for (const filepath of binding.includedFiles) { verboseLog(`↳ Loading ${filepath}`); const loaded = await loadResourceFile(filepath); verboseLog(` ↳ Loaded ${loaded.length} resources`); @@ -106,13 +114,14 @@ async function main(): Promise { verboseLog("Constructing playbill"); - // Consjtruct the playbill object + // -- COMPILE RESOURCES --// const playbill = getEmptyPlaybill(); - playbill.id = binding.info.id; - playbill.name = binding.info.name; - playbill.author = binding.info.author ?? "Anonymous"; - playbill.version = binding.info.version; + playbill.id = binding.id; + playbill.name = binding.name; + playbill.author = binding.author; + playbill.version = binding.version; + playbill.description = binding.description; for (const resource of loadedResources) { switch (resource.$define) { @@ -145,66 +154,49 @@ async function main(): Promise { } } - // Evaluate directives in descriptions for all resources - verboseLog("Processing descriptions"); - for (const resource of loadedResources) { - try { - resource.description = compileScript(resource.description, playbill); - } catch (error) { - if (error instanceof DirectiveError) { - console.error( - `Error processing directives in ${resource.$define}/${resource.id}`, - ); - console.error(error.message); - } else { - console.error(error); - } - return false; - } - } - - // Validate the playbill object + // -- VALDATE PLAYBILL -- // verboseLog("Validating playbill"); const validatedPlaybill = ValidatedPlaybillSchema.safeParse(playbill); if (!validatedPlaybill.success) { console.error("Error validating playbill"); console.error(validatedPlaybill.error.errors); - return false; + return ExitCode.Error; } verboseLog("Playbill validated"); - // If --check is set, exit here + // -- EXIT EARLY IF JUST CHECKING VALIDATION --// if (options.check) { console.log(chalk.green("Playbill validated successfully")); - return true; + return ExitCode.Error; } + // -- SERIALIZE TO JSON --// const finalizedPlaybill = JSON.stringify( validatedPlaybill.data, null, options.minify ? undefined : 2, ); + + // -- WRITE TO DISK OR STDOUT --// if (options.write) { await Bun.write(options.outfile, finalizedPlaybill); - return true; + return ExitCode.Success; } console.log(finalizedPlaybill); - return true; + return ExitCode.Success; } try { - const success = await main(); - if (success) { - process.exit(0); - } else { - process.exit(1); - } + const exitCode = await main(); + process.exit(exitCode); } catch (error) { if (error instanceof Error) { + console.error("Uncaught error:"); console.error(chalk.red(error.message)); + console.error(error.stack); } else { console.error("An unknown error occurred"); console.error(error); diff --git a/src/resource.ts b/src/resource.ts index aa90a72..0063ec9 100644 --- a/src/resource.ts +++ b/src/resource.ts @@ -3,12 +3,71 @@ import { AnyResourceSchema, ResourceMap, } from "@proscenium/playbill"; -import YAML from "yaml"; +import YAML, { YAMLParseError } from "yaml"; import { extractFrontmatter, extractMarkdown } from "./markdown"; type FileFormat = "yaml" | "markdown"; -export class ResourceFileError extends Error {} -export class NullResourceError extends Error {} +export class ResourceFileError extends Error { } +export class NullResourceError extends Error { } + +function determineFileFormat(filePath: string): FileFormat { + return filePath.split(".").pop() === "md" ? "markdown" : "yaml"; +} + +function loadYamlResourceFile(filePath: string, text: string): AnyResource[] { + const parsedDocs = YAML.parseAllDocuments(text); + + if (parsedDocs.some((doc) => doc.toJS() === null)) { + throw new NullResourceError(`Null resource defined in ${filePath}`); + } + + const errors = parsedDocs.flatMap((doc) => doc.errors); + + if (errors.length > 0) { + throw new ResourceFileError(`Error parsing ${filePath}: ${errors}`); + } + + const collection: AnyResource[] = []; + + for (const doc of parsedDocs) { + if (doc.errors.length > 0) { + throw new ResourceFileError(`Error parsing ${filePath}: ${doc.errors}`); + } + + const raw = doc.toJS(); + const parsed = AnyResourceSchema.parse(raw); + const type = parsed.$define; + const schemaToUse = ResourceMap[type]; + const validated = schemaToUse.parse(parsed); + collection.push(validated); + } + + return collection; +} + +function loadMarkdownResourceFile( + filePath: string, + text: string, +): AnyResource[] { + try { + const frontmatter = extractFrontmatter(text); + const markdown = extractMarkdown(text); + + const together = { + ...frontmatter, + description: markdown, + }; + + const parsed = AnyResourceSchema.parse(together); + return [parsed]; + } catch (e) { + if (e instanceof YAMLParseError) { + throw new ResourceFileError(`Error parsing frontmatter in ${filePath}`); + } + + throw e; + } +} /** * Load and process all documents in a yaml file at the given path @@ -22,49 +81,13 @@ export async function loadResourceFile( filePath: string, ): Promise { const file = Bun.file(filePath); - const format: FileFormat = - filePath.split(".").pop() === "md" ? "markdown" : "yaml"; const text = await file.text(); + const format = determineFileFormat(filePath); - if (format === "yaml") { - const parsedDocs = YAML.parseAllDocuments(text); - - if (parsedDocs.some((doc) => doc.toJS() === null)) { - throw new NullResourceError(`Null resource defined in ${filePath}`); - } - - const errors = parsedDocs.flatMap((doc) => doc.errors); - - if (errors.length > 0) { - throw new ResourceFileError(`Error parsing ${filePath}: ${errors}`); - } - - const collection: AnyResource[] = []; - - for (const doc of parsedDocs) { - if (doc.errors.length > 0) { - throw new ResourceFileError(`Error parsing ${filePath}: ${doc.errors}`); - } - - const raw = doc.toJS(); - const parsed = AnyResourceSchema.parse(raw); - const type = parsed.$define; - const schemaToUse = ResourceMap[type]; - const validated = schemaToUse.parse(parsed); - collection.push(validated); - } - - return collection; + switch (format) { + case "yaml": + return loadYamlResourceFile(filePath, text); + case "markdown": + return loadMarkdownResourceFile(filePath, text); } - - const frontmatter = extractFrontmatter(text); - const markdown = extractMarkdown(text); - - const together = { - ...frontmatter, - description: markdown, - }; - - const parsed = AnyResourceSchema.parse(together); - return [parsed]; }