Support for updated shapes

This commit is contained in:
Endeavorance 2025-03-08 14:45:08 -05:00
parent 9d4907aa25
commit d8ddff01a6
6 changed files with 58 additions and 65 deletions

View file

@ -4,7 +4,7 @@
"": { "": {
"name": "@proscenium/muse", "name": "@proscenium/muse",
"dependencies": { "dependencies": {
"@proscenium/playbill": "0.0.14", "@proscenium/playbill": "0.0.1",
"chalk": "^5.4.1", "chalk": "^5.4.1",
"yaml": "^2.7.0", "yaml": "^2.7.0",
"zod": "^3.24.1", "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=="], "@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.14", "https://git.astral.camp/api/packages/proscenium/npm/%40proscenium%2Fplaybill/-/0.0.14/playbill-0.0.14.tgz", { "peerDependencies": { "typescript": "^5" } }, "sha512-Fbo7IHTeRBDLwh6olETphh2duhMRrUx7dclFGT3VRhbCeCIqp+64N93M3fWlvOUrEyIU75WgpxkOz7FzfecAzg=="], "@proscenium/playbill": ["@proscenium/playbill@0.0.1", "https://git.astral.camp/api/packages/proscenium/npm/%40proscenium%2Fplaybill/-/0.0.1/playbill-0.0.1.tgz", { "peerDependencies": { "typescript": "^5" } }, "sha512-oKOqGMKeIrmHOmBvk65RFTBfbXH8KjxR+4PMtZQ2KLVGpYPexj60muf7SmG54K32Vd10xXpBqMAG5BvGZvhLeg=="],
"@types/bun": ["@types/bun@1.2.3", "", { "dependencies": { "bun-types": "1.2.3" } }, "sha512-054h79ipETRfjtsCW9qJK8Ipof67Pw9bodFWmkfkaUaRiIQ1dIV2VTlheshlBx3mpKr0KeK8VqnMMCtgN9rQtw=="], "@types/bun": ["@types/bun@1.2.3", "", { "dependencies": { "bun-types": "1.2.3" } }, "sha512-054h79ipETRfjtsCW9qJK8Ipof67Pw9bodFWmkfkaUaRiIQ1dIV2VTlheshlBx3mpKr0KeK8VqnMMCtgN9rQtw=="],

View file

@ -14,7 +14,7 @@
"chalk": "^5.4.1", "chalk": "^5.4.1",
"yaml": "^2.7.0", "yaml": "^2.7.0",
"zod": "^3.24.1", "zod": "^3.24.1",
"@proscenium/playbill": "0.0.14" "@proscenium/playbill": "0.0.1"
}, },
"scripts": { "scripts": {
"fmt": "bunx --bun biome check --fix", "fmt": "bunx --bun biome check --fix",

View file

@ -1,5 +1,5 @@
$binding: playbill $binding: playbill
extends: ../extend-me/binding.yaml extends: ../extend-me
id: the-great-spires id: the-great-spires
name: The Great Spires name: The Great Spires
author: "Endeavorance <hello@endeavorance.camp>" author: "Endeavorance <hello@endeavorance.camp>"

View file

@ -3,11 +3,12 @@ import { Glob } from "bun";
import z from "zod"; import z from "zod";
import { import {
getEmptyPlaybill, getEmptyPlaybill,
type AnyResource, type UnknownResource,
type Playbill, type Playbill,
} from "@proscenium/playbill"; } from "@proscenium/playbill";
import { loadYAMLFileOrFail } from "./files"; import { loadYAMLFileOrFail } from "./files";
import { loadResourceFile } from "./resource"; import { loadResourceFile } from "./resource";
import { FileNotFoundError } from "./errors";
const BindingFileSchema = z.object({ const BindingFileSchema = z.object({
$binding: z.literal("playbill"), $binding: z.literal("playbill"),
@ -39,21 +40,42 @@ export interface PlaybillBinding {
playbill: Playbill; playbill: Playbill;
} }
export async function resolveBindingPath(bindingPath: string): Promise<string> {
// If the path does not specify a filename, use binding.yamk
const inputLocation = Bun.file(bindingPath);
// If it is a directory, try again seeking a binding.yaml inside
const stat = await inputLocation.stat();
if (stat.isDirectory()) {
return resolveBindingPath(path.resolve(bindingPath, "binding.yaml"));
}
// If it doesnt exist, bail
if (!(await inputLocation.exists())) {
throw new FileNotFoundError(bindingPath);
}
// Resolve the path
return path.resolve(bindingPath);
}
export async function loadFromBinding( export async function loadFromBinding(
filePath: string, bindingPath: string,
): Promise<PlaybillBinding> { ): Promise<PlaybillBinding> {
const yamlContent = await loadYAMLFileOrFail(filePath); const resolvedBindingPath = await resolveBindingPath(bindingPath);
const yamlContent = await loadYAMLFileOrFail(resolvedBindingPath);
const binding = BindingFileSchema.parse(yamlContent); const binding = BindingFileSchema.parse(yamlContent);
const fileGlobs = binding.files; const fileGlobs = binding.files;
const bindingFileDirname = filePath.split("/").slice(0, -1).join("/"); const bindingFileDirname = bindingPath.split("/").slice(0, -1).join("/");
let basePlaybill: Playbill | null = null; let basePlaybill: Playbill | null = null;
// If this is extending another binding, load that first // If this is extending another binding, load that first
if (binding.extends) { if (binding.extends) {
// TODO: Allow extending built playbills in addition to bindings const pathFromHere = path.resolve(bindingFileDirname, binding.extends);
const extendBindingPath = path.resolve(bindingFileDirname, binding.extends); const extendBindingPath = await resolveBindingPath(pathFromHere);
basePlaybill = (await loadFromBinding(extendBindingPath)).playbill; const loadedBinding = await loadFromBinding(extendBindingPath);
basePlaybill = loadedBinding.playbill;
} }
const allFilePaths: string[] = []; const allFilePaths: string[] = [];
@ -71,13 +93,13 @@ export async function loadFromBinding(
allFilePaths.push(...results); allFilePaths.push(...results);
} }
const bindingFileAbsolutePath = path.resolve(filePath); const bindingFileAbsolutePath = path.resolve(bindingPath);
const filePathsWithoutBindingFile = allFilePaths.filter((filePath) => { const filePathsWithoutBindingFile = allFilePaths.filter((filePath) => {
return filePath !== bindingFileAbsolutePath; return filePath !== bindingFileAbsolutePath;
}); });
// -- LOAD ASSOCIATED RESOURCE FILES -- // // -- LOAD ASSOCIATED RESOURCE FILES -- //
const loadedResources: AnyResource[] = []; const loadedResources: UnknownResource[] = [];
for (const filepath of filePathsWithoutBindingFile) { for (const filepath of filePathsWithoutBindingFile) {
const loaded = await loadResourceFile(filepath); const loaded = await loadResourceFile(filepath);
loadedResources.push(...loaded); loadedResources.push(...loaded);
@ -93,43 +115,17 @@ export async function loadFromBinding(
playbill.description = binding.description ?? ""; playbill.description = binding.description ?? "";
for (const resource of loadedResources) { for (const resource of loadedResources) {
switch (resource.$define) { const resourceType = resource.$define;
case "ability": const resourceMap = playbill[resourceType] as Record<
playbill.abilities.push(resource); string,
break; typeof resource
case "species": >;
playbill.species.push(resource); resourceMap[resource.id] = resource;
break;
case "entity":
playbill.entities.push(resource);
break;
case "item":
playbill.items.push(resource);
break;
case "tag":
playbill.tags.push(resource);
break;
case "method":
playbill.methods.push(resource);
break;
case "lore":
playbill.lore.push(resource);
break;
case "rule":
playbill.rules.push(resource);
break;
case "guide":
playbill.guides.push(resource);
break;
case "glossary":
playbill.glossaries.push(resource);
break;
}
} }
return { return {
_raw: binding, _raw: binding,
bindingFilePath: filePath, bindingFilePath: bindingPath,
includedFiles: filePathsWithoutBindingFile, includedFiles: filePathsWithoutBindingFile,
id: binding.id, id: binding.id,

View file

@ -1,7 +1,7 @@
import { parseArgs } from "node:util"; import { parseArgs } from "node:util";
import { ValidatedPlaybillSchema, checkDirectives } from "@proscenium/playbill"; import { ValidatedPlaybillSchema } from "@proscenium/playbill";
import chalk from "chalk"; import chalk from "chalk";
import { loadFromBinding } from "./binding"; import { loadFromBinding, resolveBindingPath } from "./binding";
import { usage } from "./usage"; import { usage } from "./usage";
import { CLIError, MuseError } from "./errors"; import { CLIError, MuseError } from "./errors";
@ -74,7 +74,8 @@ async function main(): Promise<number> {
} }
// -- BINDING FILE -- // // -- BINDING FILE -- //
const bindingPath = args[0] ?? "./binding.yaml"; const bindingPathInput = args[0] ?? "./binding.yaml";
const bindingPath = await resolveBindingPath(bindingPathInput);
verboseLog(`Building Playbill with binding: ${bindingPath}`); verboseLog(`Building Playbill with binding: ${bindingPath}`);
@ -94,19 +95,12 @@ async function main(): Promise<number> {
return ExitCode.Error; return ExitCode.Error;
} }
const directiveValidationResult = checkDirectives(validatedPlaybill.data);
if (!directiveValidationResult) {
console.error("Error validating playbill directives");
return ExitCode.Error;
}
verboseLog("Playbill validated"); verboseLog("Playbill validated");
// -- EXIT EARLY IF JUST CHECKING VALIDATION --// // -- EXIT EARLY IF JUST CHECKING VALIDATION --//
if (options.check) { if (options.check) {
console.log(chalk.green("Playbill validated successfully")); console.log(chalk.green("Playbill validated successfully"));
return ExitCode.Error; return ExitCode.Success;
} }
// -- SERIALIZE TO JSON --// // -- SERIALIZE TO JSON --//

View file

@ -1,6 +1,6 @@
import { import {
type AnyResource, type UnknownResource,
AnyResourceSchema, UnknownResourceSchema,
ResourceMap, ResourceMap,
} from "@proscenium/playbill"; } from "@proscenium/playbill";
import YAML, { YAMLParseError } from "yaml"; import YAML, { YAMLParseError } from "yaml";
@ -15,7 +15,10 @@ function determineFileFormat(filePath: string): FileFormat {
return filePath.split(".").pop() === "md" ? "markdown" : "yaml"; return filePath.split(".").pop() === "md" ? "markdown" : "yaml";
} }
function loadYamlResourceFile(filePath: string, text: string): AnyResource[] { function loadYamlResourceFile(
filePath: string,
text: string,
): UnknownResource[] {
const parsedDocs = YAML.parseAllDocuments(text); const parsedDocs = YAML.parseAllDocuments(text);
if (parsedDocs.some((doc) => doc.toJS() === null)) { if (parsedDocs.some((doc) => doc.toJS() === null)) {
@ -32,11 +35,11 @@ function loadYamlResourceFile(filePath: string, text: string): AnyResource[] {
); );
} }
const collection: AnyResource[] = []; const collection: UnknownResource[] = [];
for (const doc of parsedDocs) { for (const doc of parsedDocs) {
const raw = doc.toJS(); const raw = doc.toJS();
const parsed = AnyResourceSchema.safeParse(raw); const parsed = UnknownResourceSchema.safeParse(raw);
if (!parsed.success) { if (!parsed.success) {
throw new MalformedResourceFileError( throw new MalformedResourceFileError(
@ -59,7 +62,7 @@ function loadYamlResourceFile(filePath: string, text: string): AnyResource[] {
function loadMarkdownResourceFile( function loadMarkdownResourceFile(
filePath: string, filePath: string,
text: string, text: string,
): AnyResource[] { ): UnknownResource[] {
try { try {
const frontmatter = extractFrontmatter(text); const frontmatter = extractFrontmatter(text);
const markdown = extractMarkdown(text); const markdown = extractMarkdown(text);
@ -69,7 +72,7 @@ function loadMarkdownResourceFile(
description: markdown, description: markdown,
}; };
const parsed = AnyResourceSchema.parse(together); const parsed = UnknownResourceSchema.parse(together);
return [parsed]; return [parsed];
} catch (e) { } catch (e) {
if (e instanceof YAMLParseError) { if (e instanceof YAMLParseError) {
@ -102,7 +105,7 @@ function loadMarkdownResourceFile(
*/ */
export async function loadResourceFile( export async function loadResourceFile(
filePath: string, filePath: string,
): Promise<AnyResource[]> { ): Promise<UnknownResource[]> {
const text = await loadFileOrFail(filePath); const text = await loadFileOrFail(filePath);
const format = determineFileFormat(filePath); const format = determineFileFormat(filePath);