151 lines
4.1 KiB
TypeScript
151 lines
4.1 KiB
TypeScript
import path from "node:path";
|
|
import { Glob } from "bun";
|
|
import z from "zod";
|
|
import {
|
|
getEmptyPlaybill,
|
|
type UnknownResource,
|
|
type Playbill,
|
|
} from "@proscenium/playbill";
|
|
import { loadYAMLFileOrFail } from "./files";
|
|
import { loadResourceFile } from "./resource";
|
|
import { FileNotFoundError } from "./errors";
|
|
|
|
const HTMLRenderOptionsSchema = z.object({
|
|
stylesheet: z.string().optional(),
|
|
});
|
|
|
|
const BindingFileSchema = z.object({
|
|
$binding: z.literal("playbill"),
|
|
extends: z.string().optional(),
|
|
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"]),
|
|
|
|
html: HTMLRenderOptionsSchema.optional(),
|
|
});
|
|
|
|
type BindingFileContent = z.infer<typeof BindingFileSchema>;
|
|
|
|
export interface PlaybillBinding {
|
|
_raw: BindingFileContent;
|
|
|
|
// File information
|
|
bindingFilePath: string;
|
|
bindingFileDirname: string;
|
|
includedFiles: string[];
|
|
|
|
// Binding properties
|
|
id: string;
|
|
name: string;
|
|
author: string;
|
|
version: string;
|
|
description: string;
|
|
|
|
html?: z.infer<typeof HTMLRenderOptionsSchema>;
|
|
|
|
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(
|
|
bindingPath: string,
|
|
): Promise<PlaybillBinding> {
|
|
const resolvedBindingPath = await resolveBindingPath(bindingPath);
|
|
const yamlContent = await loadYAMLFileOrFail(resolvedBindingPath);
|
|
const binding = BindingFileSchema.parse(yamlContent);
|
|
const fileGlobs = binding.files;
|
|
const bindingFileDirname = bindingPath.split("/").slice(0, -1).join("/");
|
|
|
|
let basePlaybill: Playbill | null = null;
|
|
|
|
// If this is extending another binding, load that first
|
|
if (binding.extends) {
|
|
const pathFromHere = path.resolve(bindingFileDirname, binding.extends);
|
|
const extendBindingPath = await resolveBindingPath(pathFromHere);
|
|
const loadedBinding = await loadFromBinding(extendBindingPath);
|
|
basePlaybill = loadedBinding.playbill;
|
|
}
|
|
|
|
const allFilePaths: string[] = [];
|
|
|
|
for (const thisGlob of fileGlobs) {
|
|
const glob = new Glob(thisGlob);
|
|
|
|
const results = glob.scanSync({
|
|
cwd: bindingFileDirname,
|
|
absolute: true,
|
|
followSymlinks: true,
|
|
onlyFiles: true,
|
|
});
|
|
|
|
allFilePaths.push(...results);
|
|
}
|
|
|
|
const bindingFileAbsolutePath = path.resolve(bindingPath);
|
|
const filePathsWithoutBindingFile = allFilePaths.filter((filePath) => {
|
|
return filePath !== bindingFileAbsolutePath;
|
|
});
|
|
|
|
// -- LOAD ASSOCIATED RESOURCE FILES -- //
|
|
const loadedResources: UnknownResource[] = [];
|
|
for (const filepath of filePathsWithoutBindingFile) {
|
|
const loaded = await loadResourceFile(filepath);
|
|
loadedResources.push(...loaded);
|
|
}
|
|
|
|
// -- COMPILE PLAYBILL FROM RESOURCES --//
|
|
const playbill = basePlaybill ?? getEmptyPlaybill();
|
|
|
|
playbill.id = binding.id;
|
|
playbill.name = binding.name;
|
|
playbill.author = binding.author ?? "Anonymous";
|
|
playbill.version = binding.version;
|
|
playbill.description = binding.description ?? "";
|
|
|
|
for (const resource of loadedResources) {
|
|
const resourceType = resource.$define;
|
|
const resourceMap = playbill[resourceType] as Record<
|
|
string,
|
|
typeof resource
|
|
>;
|
|
resourceMap[resource.id] = resource;
|
|
}
|
|
|
|
return {
|
|
_raw: binding,
|
|
bindingFilePath: bindingPath,
|
|
bindingFileDirname,
|
|
includedFiles: filePathsWithoutBindingFile,
|
|
|
|
id: binding.id,
|
|
name: binding.name,
|
|
author: binding.author ?? "Anonymous",
|
|
description: binding.description ?? "",
|
|
version: binding.version,
|
|
|
|
html: binding.html,
|
|
|
|
playbill,
|
|
};
|
|
}
|