Organize new setup
This commit is contained in:
parent
3682a7a763
commit
16660823ea
11 changed files with 165 additions and 142 deletions
|
@ -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";
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export const name = "Scream";
|
||||
|
||||
export function process(binding) {
|
||||
const modifiedEntries = binding.entries.map(entry => {
|
||||
if (binding.markdownKey in entry.data) {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
|
@ -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
|
|
@ -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
42
src/core/processor.ts
Normal 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,
|
||||
};
|
||||
}
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export type * from "./types";
|
||||
export * from "./errors";
|
||||
export type * from "#types";
|
||||
export * from "#errors";
|
||||
export const DEFAULT_CONTENT_KEY = "content";
|
||||
|
|
57
src/types.ts
57
src/types.ts
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue