Organize new setup

This commit is contained in:
Endeavorance 2025-04-03 10:16:08 -04:00
parent 3682a7a763
commit 16660823ea
11 changed files with 165 additions and 142 deletions

View file

@ -1,4 +1,5 @@
export const name = "Announce Slam Dunks";
export const description = "Get hype when a file has slam dunks";
export const process = (binding, { dunks }) => {
const slamDunkKey = dunks?.key ?? "slamDunks";

View file

@ -1,4 +1,5 @@
export const name = "Scream";
export function process(binding) {
const modifiedEntries = binding.entries.map(entry => {
if (binding.markdownKey in entry.data) {

View file

@ -32,9 +32,9 @@
"clean": "rm -rf dist",
"types": "dts-bundle-generator -o dist/muse.d.ts --project ./tsconfig.json ./src/exports.ts",
"transpile": "bun build ./src/exports.ts --outfile ./dist/muse.js",
"compile": "bun build ./src/cli.ts --outfile ./dist/muse --compile",
"compile": "bun run clean && bun build ./src/cli/index.ts --outfile ./dist/muse --compile",
"compile:install": "bun run compile && mv ./dist/muse ~/.local/bin/muse",
"build": "bun run clean && bun run transpile && bun run types",
"demo": "bun run ./src/cli.ts -- ./demo/main"
"demo": "bun run ./src/cli/index.ts -- ./demo/main"
}
}

View file

@ -1,9 +1,14 @@
import { parseArgs } from "node:util";
import chalk from "chalk";
import { version } from "../package.json";
import { loadBinding } from "./binding";
import { MuseError } from "./errors";
import type { Binding, CLIArguments } from "./types";
import { type Binding, loadBinding } from "#core/binding";
import { MuseError } from "#errors";
import { version } from "../../package.json";
interface CLIFlags {
help: boolean;
verbose: boolean;
stdout: boolean;
}
interface Loggers {
log: (msg: string) => void;
@ -36,7 +41,7 @@ Options:
*
* @throws {CLIError} if the arguments are invalid
*/
export function parseCLIArguments(argv: string[]): CLIArguments {
function parseCLIArguments(argv: string[]): [string, CLIFlags] {
const { values: options, positionals: args } = parseArgs({
args: argv,
options: {
@ -60,17 +65,12 @@ export function parseCLIArguments(argv: string[]): CLIArguments {
allowPositionals: true,
});
return {
inputFilePath: args[0] ?? "./binding.yaml",
flags: {
help: options.help,
verbose: options.verbose,
stdout: options.stdout,
},
};
return [args[0] ?? "./", options];
}
async function processBinding(
{ inputFilePath, flags }: CLIArguments,
inputFilePath: string,
flags: CLIFlags,
{ log, verbose }: Loggers,
) {
// Load the binding
@ -90,10 +90,16 @@ async function processBinding(
for (const processor of binding.processors) {
const lastStep = processedSteps[processedSteps.length - 1];
const { process, name } = processor;
const { process, name, description } = processor;
log(chalk.bold(`${name}`) + chalk.dim(` (${processor.description})`));
const thisStep = await process(lastStep, binding.options, flags);
let processorLogLine = chalk.bold(`${name}`);
if (description && description.length > 0) {
processorLogLine += chalk.dim(` (${description})`);
}
log(processorLogLine);
const thisStep = await process(lastStep, binding.options);
processedSteps.push(thisStep);
}
@ -111,8 +117,7 @@ async function processBinding(
}
async function main(): Promise<number> {
const cliArguments = parseCLIArguments(Bun.argv.slice(2));
const { flags } = cliArguments;
const [inputFilePath, flags] = parseCLIArguments(Bun.argv.slice(2));
// If --help is specified, print usage and exit
if (flags.help) {
@ -127,7 +132,7 @@ async function main(): Promise<number> {
}
: () => {};
const lastProcessResult = await processBinding(cliArguments, {
const lastProcessResult = await processBinding(inputFilePath, flags, {
log: logFn,
verbose: verboseFn,
});

View file

@ -1,39 +1,49 @@
import path, { dirname } from "node:path";
import { Glob } from "bun";
import { MuseError, MuseFileNotFoundError } from "./errors";
import { parseMuseFile } from "./parse";
import {
type Binding,
BindingSchema,
type MuseEntry,
type MuseProcessor,
} from "./types";
import { resolveFilePath } from "./util";
import { z } from "zod";
import { MuseError, MuseFileNotFoundError } from "#errors";
import type { UnknownRecord } from "#types";
import { resolveFilePath } from "#util";
import { type MuseEntry, parseMuseFile } from "./parse";
import { type MuseProcessor, loadProcessor } from "./processor";
async function loadProcessor(
bindingDirname: string,
processorPath: string,
): Promise<MuseProcessor> {
const resolvedProcessorPath = processorPath.startsWith("http")
? processorPath
: path.resolve(bindingDirname, processorPath);
const { process, name, description } = await import(resolvedProcessorPath);
// Function to parse an unknown object into parts of a binding
export const BindingSchema = z.object({
contentKey: z.string().default("content"),
sources: z
.array(z.string())
.default(["**/*.yaml", "**/*.md", "**/*.toml", "**/*.json"]),
options: z.record(z.string(), z.any()).default({}),
processors: z.array(z.string()).default([]),
renderer: z.string().optional(),
});
if (!process || typeof process !== "function") {
throw new MuseError(
`Processor at ${processorPath} does not export a process() function`,
);
}
/**
* The core object of a Muse binding
* Contains information about the binding file, the configuration of
* the project, and a list of entries to be processed
*/
export interface Binding<MetaShape = UnknownRecord> {
/** The raw data read from the binding file */
readonly _raw: z.infer<typeof BindingSchema>;
const processorName = String(name ?? "Unnamed Processor");
const processorDescription = String(description ?? "No description provided");
/** The full file path to the binding file */
readonly bindingPath: string;
return {
filePath: resolvedProcessorPath,
process: process as MuseProcessor["process"],
name: processorName,
description: processorDescription,
};
/** The key used to for default content in unstructured files */
readonly contentKey: string;
/** All entries loaded from the binding file */
readonly entries: MuseEntry[];
/** A list of processors to be applied to the entries */
readonly processors: MuseProcessor[];
/** Arbitrary metadata for processors to use to cache project data */
readonly meta: MetaShape;
/** Configuration options for Muse and all processors */
readonly options: UnknownRecord;
}
/**
@ -46,47 +56,39 @@ async function loadProcessor(
* @returns The absolute resolved path to the binding file
*/
async function resolveBindingPath(bindingPath: string): Promise<string> {
// If the path does not specify a filename, use binding.yaml
const DEFAULT_BINDING_FILES = [
"binding.json",
"binding.yaml",
"binding.toml",
"binding.md",
] as const;
const inputLocation = Bun.file(bindingPath);
// If it is a directory, try for the four main supported types
const stat = await inputLocation.stat();
if (stat.isDirectory()) {
const jsonPath = await resolveFilePath(
path.resolve(bindingPath, "binding.json"),
);
if (jsonPath) {
return jsonPath;
for (const bindingFile of DEFAULT_BINDING_FILES) {
const filePath = path.resolve(bindingPath, bindingFile);
const resolvedPath = await resolveFilePath(filePath);
if (resolvedPath) {
return resolvedPath;
}
}
const yamlPath = await resolveFilePath(
path.resolve(bindingPath, "binding.yaml"),
throw new MuseFileNotFoundError(
bindingPath,
"Failed to find a binding file at the provided directory.",
);
if (yamlPath) {
return yamlPath;
}
const tomlPath = await resolveFilePath(
path.resolve(bindingPath, "binding.toml"),
);
if (tomlPath) {
return tomlPath;
}
const mdPath = await resolveFilePath(
path.resolve(bindingPath, "binding.md"),
);
if (mdPath) {
return mdPath;
}
throw new MuseFileNotFoundError(bindingPath);
}
// If not a directory, check if its a file that exists
const exists = await inputLocation.exists();
if (!exists) {
throw new MuseFileNotFoundError(bindingPath);
throw new MuseFileNotFoundError(
bindingPath,
"Provided binding path does not exist.",
);
}
// If it is a file, return the path

View file

@ -2,7 +2,15 @@ import { extname, normalize } from "node:path";
import EMDY from "@endeavorance/emdy";
import TOML from "smol-toml";
import YAML from "yaml";
import type { MuseEntry, UnknownRecord } from "./types";
import type { UnknownRecord } from "#types";
import { MuseFileNotFoundError } from "#errors";
export interface MuseEntry<MetaShape = UnknownRecord> {
_raw: string;
filePath: string;
data: Record<string, unknown>;
meta: MetaShape;
}
function parseYAMLEntries(text: string): UnknownRecord[] {
const parsedDocs = YAML.parseAllDocuments(text);
@ -62,6 +70,12 @@ export async function parseMuseFile(
const filePath = normalize(rawFilePath);
const file = Bun.file(filePath);
const fileType = extname(filePath).slice(1);
const fileExists = await file.exists();
if (!fileExists) {
throw new MuseFileNotFoundError(filePath, "Failed to load source file.");
}
const rawFileContent = await file.text();
const partial = {

42
src/core/processor.ts Normal file
View file

@ -0,0 +1,42 @@
import path from "node:path";
import { MuseError } from "#errors";
import type { Binding } from "./binding";
export type MuseProcessorFn = (
binding: Binding,
opts: Record<string, unknown>,
) => Promise<Binding>;
export interface MuseProcessor {
readonly filePath: string;
readonly name: string;
readonly description: string;
readonly process: MuseProcessorFn;
}
export async function loadProcessor(
bindingDirname: string,
processorPath: string,
): Promise<MuseProcessor> {
// Resolve local paths, use URLs as-is
const resolvedProcessorPath = processorPath.startsWith("http")
? processorPath
: path.resolve(bindingDirname, processorPath);
const { process, name, description } = await import(resolvedProcessorPath);
if (!process || typeof process !== "function") {
throw new MuseError(
`Processor at ${processorPath} does not export a process() function`,
);
}
const processorName = String(name ?? "Unnamed Processor");
const processorDescription = String(description ?? "");
return {
filePath: resolvedProcessorPath,
process: process as MuseProcessor["process"],
name: processorName,
description: processorDescription,
};
}

View file

@ -20,10 +20,11 @@ export class MuseFileError extends MuseError {
}
export class MuseFileNotFoundError extends MuseFileError {
constructor(filePath: string) {
constructor(filePath: string, details?: string) {
super("File not found", filePath);
this.message = "File not found";
this.filePath = filePath;
this.details = details ?? "No additional information.";
}
}

View file

@ -1,3 +1,3 @@
export type * from "./types";
export * from "./errors";
export type * from "#types";
export * from "#errors";
export const DEFAULT_CONTENT_KEY = "content";

View file

@ -1,60 +1,3 @@
import { z } from "zod";
export type UnknownRecord = Record<string, unknown>;
export interface CLIFlags {
help: boolean;
verbose: boolean;
stdout: boolean;
}
export interface CLIArguments {
inputFilePath: string;
flags: CLIFlags;
}
export const BindingSchema = z.object({
contentKey: z.string().default("content"),
sources: z
.array(z.string())
.default(["**/*.yaml", "**/*.md", "**/*.toml", "**/*.json"]),
options: z.record(z.string(), z.any()).default({}),
processors: z.array(z.string()).default([]),
renderer: z.string().optional(),
});
export type MuseProcessorFn = (
binding: Binding,
opts: Record<string, unknown>,
flags: CLIFlags,
) => Promise<Binding>;
export type MuseRenderFn = (
binding: Binding,
opts: UnknownRecord,
flags: CLIFlags,
) => Promise<boolean>;
export interface MuseProcessor {
readonly filePath: string;
readonly name: string;
readonly description: string;
readonly process: MuseProcessorFn;
}
export interface MuseEntry<MetaShape = UnknownRecord> {
_raw: string;
filePath: string;
data: Record<string, unknown>;
meta: MetaShape;
}
export interface Binding<MetaShape = UnknownRecord> {
readonly _raw: z.infer<typeof BindingSchema>;
readonly bindingPath: string;
readonly contentKey: string;
readonly entries: MuseEntry[];
readonly processors: MuseProcessor[];
readonly meta: MetaShape;
readonly options: UnknownRecord;
}

View file

@ -26,11 +26,25 @@
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
"baseUrl": "./src",
"paths": {},
"paths": {
"#core/*": [
"./core/*.ts"
],
"#util": [
"./util.ts"
],
"#types": [
"./types.ts"
],
"#errors": [
"./errors.ts"
]
},
"outDir": "./dist",
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/preload/http-plugin.js",
]
}