diff --git a/.gitignore b/.gitignore
index 4cda367..8a4e461 100644
--- a/.gitignore
+++ b/.gitignore
@@ -174,5 +174,3 @@ dist
# Finder (MacOS) folder config
.DS_Store
-demo-data
-demo.html
diff --git a/README.md b/README.md
index a1543bf..d122169 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,17 @@
-# Playbill Builder CLI
+# Muse
-This is a CLI tool for compiling Markdown and YAML files into a validated Playbill for [Proscenium](https://proscenium.game)
+_Bind data into anything_
+
+## Overview
+
+**Muse** is a CLI and toolchain for operating on data collected from
+json, yaml, toml, and markdown files.
+
+Muse scans included files, parses them into data, and streams
+the loaded data through plugins.
+
+Each plugin can modify the data as well as enact side effects
+such as writing files to disk.
## Usage
@@ -9,209 +20,78 @@ Usage:
muse [/path/to/binding.yaml]
Options:
- --check Only load and check the current binding and resources, but do not compile
- --outfile, -o Specify the output file path. If not specified, output to stdout
- --watch, -w Watch the directory for changes and recompile
- --renderer, -r Specify the output renderer. Options: json, html
+ --stdout -s Output final data to stdout
+ --verbose, -v Enable verbose logging
--help, -h Show this help message
```
## Binding File
-Each Muse project should specify a `binding.yaml` file with the following shape:
+Each Muse project should specify a `binding` file with the following shape:
```yaml
-name: My Playbill # Required
-author: My Name # Required
-version: 0.0.1 # Required
-extend: ../another-playbill # Optional
-files: # Optional
- - "**/*.yaml"
-styles: # Optional
- - stylesheet.css
-terms: # Optional
- Some Term: Some Definition
- Another Term: Another Definition
+sources: # Optional
+ - file: "**/*.yaml"
+ - web: "https://example.com/data.json"
+contentKey: "content" # Optional
+options: # Optional
+ someOption: someValue
+processors:
+ - first-processor
+ - second-processor
```
-The **Binding** file is used to specify the metadata for the Playbill, as well as the files to be compiled. It is also the entry point for a project.
+The `binding` file can be any of the supported file types for Muse and can
+be named anything. If the CLI is invoked with a directory instead of a file,
+Muse will look for a binding file in the directory with the following order:
-When using the `muse` CLI, you must provide either a path to a `binding.yaml` file or a directory which contains a `binding.yaml` file.
+1. `binding.json`
+2. `binding.yaml`
+3. `binding.toml`
+4. `binding.md`
-## Common Types
+## Markdown Files
-Many Playbill components share common properties that expect a value
-from a predefined list. These are the common types used in the Playbill.
+Muse supports loading and parsing Markdown files with optional YAML frontmatter.
-### `Roll` Options
+```
+---
+someKey: someValue
+---
-- `none`
-- `attack`
-- `fate`
-- `muscle`
-- `focus`
-- `knowledge`
-- `charm`
-- `cunning`
-- `spark`
-
-### `Damage Type` Options
-
-- `phy` (denotes "physical damage")
-- `arc` (denotes "arcane damage")
-
-### `Prowess` Options
-
-- `novice`
-- `adept`
-- `master`
-
-### `Challenge` Options
-
-- `novice`
-- `adept`
-- `master`
-- `theatrical`
-
-### `Ability Type` Options
-
-- `action`
-- `cue`
-- `trait`
-
-## Definition Shapes
-
-These YAML representations of definitions show the properties available for each Playbill component.
-
-Note that every Playbill component definition can define the following properties:
-
-```yaml
-id: an-id # defaults to a slugified version of the file name
-name: Component Name # defaults to the file name
-description: Markdown description # defaults to a placeholder
-categories: # defaults to no categories
- - list
- - of
- - categories
+Your markdown content here
```
-### Ability Definition
+When loading markdown files, Muse will load the content of the file
+into a key called `content` by default. This can be changed in the binding
+configuration by setting a `contentKey` value as an override.
-```yaml
-$define: ability
-type: # Defaults to action
-ap: 0 # AP cost to use this ability (default 0)
-hp: 0 # HP cost to use this ability (default 0)
-ep: 0 # EP cost to use this ability (default 0)
-xp: 0 # XP cost to learn this ability (default 1)
-damage: 0 # Damage dealt by this ability (default 0)
-damageType: # Type of damage dealt by this ability (default phy)
-roll: # Roll type for this ability (default none)
+## Plugin API
+
+Muse plugins are written in TypeScript/JavaScript and expose information
+describing the plugin, as well as the functions to operate with:
+
+```typescript
+export const name = "Plugin Name";
+export const description = "Plugin Description";
+export async function step(binding: Binding): Promise {}
```
-### Blueprint Definition
+The main function of a plugin is `step`, which takes a `Binding` object
+and returns a modified `Binding` object.
-```yaml
-$define: blueprint
-species: species-id # ID of the species this blueprint is for
-items: # List of item IDs (default empty)
- - item-id
-abilities: # List of ability IDs (default empty)
- - ability-id
-ap: 0 # Starting AP (default 4)
-hp: 0 # Starting HP (default 5)
-ep: 0 # Starting EP (default 1)
-xp: 0 # Starting XP (default 0)
-muscle: # Starting muscle prowess (default novice)
-focus: # Starting focus prowess (default novice)
-knowledge: # Starting knowledge prowess (default novice)
-charm: # Starting charm prowess (default novice)
-cunning: # Starting cunning prowess (default novice)
-spark: # Starting spark prowess (default novice)
-```
-
-### Item Definition
-
-```yaml
-$define: item
-type: - # Defaults to wielded
-rarity: # Defaults to common
-affinity: # Defaults to none
-damage: 0 # Defaults to 0
-damageType: # Defaults to phy
-hands: 1 | 2 # Only for wielded items
-slot:
- # Only for worn items
-```
-
-#### `Item Type` Options
-
-- `wielded`
-- `worn`
-- `trinket`
-
-#### `Rarity` Options
-
-- `common`
-- `uncommon`
-- `rare`
-- `legendary`
-
-#### `Item Slot` Options
-
-- `headgear`
-- `outfit`
-- `gloves`
-- `boots`
-- `adornment`
-
-### Method Definition
-
-```yaml
-$define: method
-curator: Someone # The name of the curator
-abilities: # A 2-d array of ability IDs
- - [rank-1-ability-id-one, rank-1-ability-id-two]
- - [rank-2-ability-id-one]
-```
-
-### Resource Definition
-
-```yaml
-$define: resource
-type: # Defaults to text
-url: https://url.com/image.jpeg # Only for image resources
-data: # Only for table resources
-```
-
-#### `Resource Type` Options
-
-- `text`
-- `image`
-- `table`
-
-### Rule Definition
-
-```yaml
-$define: rule
-overrule: another-rule-id # Optional
-order: 0 # Optional
-```
-
-### Species Definition
-
-```yaml
-$define: species
-hands: 2 # Defaults to 2
-abilities: # A list of innate ability IDs
- - some-ability-id
- - another-ability-id
-ap: 0 # Starting AP modifier
-hp: 0 # Starting HP modifier
-ep: 0 # Starting EP modifier
-xp: 0 # Starting XP modifier
-muscle: # Defaults to novice
-focus: # Defaults to novice
-knowledge: # Defaults to novice
-charm: # Defaults to novice
-cunning: # Defaults to novice
-spark: # Defaults to novice
+When returning a new Binding object, it is best practice to make immutable
+changes:
+
+```typescript
+export async function step(binding: Binding): Promise {
+ const newBinding = { ...binding };
+ newBinding.options.someOption = "newValue";
+ return {
+ ...binding,
+ meta: {
+ ...binding.meta,
+ customValue: true,
+ }
+ };
+}
```
diff --git a/biome.json b/biome.json
index 1814905..7988bf6 100644
--- a/biome.json
+++ b/biome.json
@@ -7,10 +7,7 @@
},
"files": {
"ignoreUnknown": false,
- "ignore": [
- "*.d.ts",
- "*.json"
- ],
+ "ignore": ["*.d.ts", "*.json"],
"include": [
"./src/**/*.ts",
"./src/**/*.tsx",
diff --git a/bun.lock b/bun.lock
index b85882f..8427e5e 100644
--- a/bun.lock
+++ b/bun.lock
@@ -4,32 +4,16 @@
"": {
"name": "@proscenium/muse",
"dependencies": {
- "@proscenium/playbill": "link:@proscenium/playbill",
+ "@endeavorance/emdy": "1.0.0",
"chalk": "^5.4.1",
- "classnames": "^2.5.1",
- "lodash-es": "^4.17.21",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "rehype-headings-normalize": "^0.0.2",
- "rehype-raw": "^7.0.0",
- "rehype-shift-heading": "^2.0.0",
- "rehype-stringify": "^10.0.1",
- "remark-gfm": "^4.0.1",
- "remark-normalize-headings": "^4.0.0",
- "remark-parse": "^11.0.0",
- "remark-rehype": "^11.1.1",
- "remark-wiki-link": "^2.0.1",
- "slugify": "^1.6.6",
- "unified": "^11.0.5",
+ "smol-toml": "^1.3.1",
"yaml": "^2.7.0",
"zod": "^3.24.1",
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@types/bun": "latest",
- "@types/lodash-es": "^4.17.12",
- "@types/react": "^19.0.11",
- "@types/react-dom": "^19.0.4",
+ "dts-bundle-generator": "^9.5.1",
},
"peerDependencies": {
"typescript": "^5.0.0",
@@ -37,8 +21,6 @@
},
},
"packages": {
- "@babel/runtime": ["@babel/runtime@7.26.10", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw=="],
-
"@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="],
@@ -57,284 +39,60 @@
"@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@link:@proscenium/playbill", {}],
+ "@endeavorance/emdy": ["@endeavorance/emdy@1.0.0", "https://git.astral.camp/api/packages/endeavorance/npm/%40endeavorance%2Femdy/-/1.0.0/emdy-1.0.0.tgz", { "dependencies": { "yaml": "^2.7.1" }, "peerDependencies": { "typescript": "^5" } }, "sha512-W3yDK3Ya76mF/QqmcX/iyNdAswfF82uOtFP+XstG8qgsbwp4I0lnuVjESkyBfj0AKvKJ/s6Xpa/64RhDHtlt6g=="],
- "@types/bun": ["@types/bun@1.2.5", "", { "dependencies": { "bun-types": "1.2.5" } }, "sha512-w2OZTzrZTVtbnJew1pdFmgV99H0/L+Pvw+z1P67HaR18MHOzYnTYOi6qzErhK8HyT+DB782ADVPPE92Xu2/Opg=="],
+ "@types/bun": ["@types/bun@1.2.8", "", { "dependencies": { "bun-types": "1.2.7" } }, "sha512-t8L1RvJVUghW5V+M/fL3Thbxcs0HwNsXsnTEBEfEVqGteiJToOlZ/fyOEaR1kZsNqnu+3XA4RI/qmnX4w6+S+w=="],
- "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
+ "@types/node": ["@types/node@22.13.17", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-nAJuQXoyPj04uLgu+obZcSmsfOenUg6DxPKogeUy6yNCFwWaj5sBF8/G/pNo8EtBJjAfSVgfIlugR/BCOleO+g=="],
- "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
+ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
- "@types/lodash": ["@types/lodash@4.17.16", "", {}, "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="],
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
- "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="],
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
- "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
-
- "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
-
- "@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
-
- "@types/react": ["@types/react@19.0.11", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-vrdxRZfo9ALXth6yPfV16PYTLZwsUWhVjjC+DkfE5t1suNSbBrWC9YqSuuxJZ8Ps6z1o2ycRpIqzZJIgklq4Tw=="],
-
- "@types/react-dom": ["@types/react-dom@19.0.4", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg=="],
-
- "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
-
- "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
-
- "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
-
- "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
-
- "bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="],
-
- "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
+ "bun-types": ["bun-types@1.2.7", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-P4hHhk7kjF99acXqKvltyuMQ2kf/rzIw3ylEDpCxDS9Xa0X0Yp/gJu/vDCucmWpiur5qJ0lwB2bWzOXa2GlHqA=="],
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
- "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
+ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
- "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
- "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
- "character-reference-invalid": ["character-reference-invalid@1.1.4", "", {}, "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="],
+ "dts-bundle-generator": ["dts-bundle-generator@9.5.1", "", { "dependencies": { "typescript": ">=5.0.2", "yargs": "^17.6.0" }, "bin": { "dts-bundle-generator": "dist/bin/dts-bundle-generator.js" } }, "sha512-DxpJOb2FNnEyOzMkG11sxO2dmxPjthoVWxfKqWYJ/bI/rT1rvTMktF5EKjAYrRZu6Z6t3NhOUZ0sZ5ZXevOfbA=="],
- "classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="],
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
- "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
- "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
- "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
- "decode-named-character-reference": ["decode-named-character-reference@1.1.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w=="],
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
- "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
+ "smol-toml": ["smol-toml@1.3.1", "", {}, "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ=="],
- "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
- "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
-
- "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
-
- "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
-
- "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="],
-
- "hast-util-heading-rank": ["hast-util-heading-rank@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA=="],
-
- "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="],
-
- "hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
-
- "hast-util-shift-heading": ["hast-util-shift-heading@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-heading-rank": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-1WzYRfcydfUIAn/N/QBDLlaG/JlXnE3Tx2ybuQFZKP4ZfM9v8FPJ/8i6pEaSoEtEI+FzjYmALlOJROkgRg3epg=="],
-
- "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
-
- "hast-util-to-parse5": ["hast-util-to-parse5@8.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw=="],
-
- "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
-
- "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
-
- "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
-
- "is-alphabetical": ["is-alphabetical@1.0.4", "", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="],
-
- "is-alphanumerical": ["is-alphanumerical@1.0.4", "", { "dependencies": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A=="],
-
- "is-decimal": ["is-decimal@1.0.4", "", {}, "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="],
-
- "is-hexadecimal": ["is-hexadecimal@1.0.4", "", {}, "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="],
-
- "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
-
- "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
-
- "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
-
- "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
-
- "mdast-normalize-headings": ["mdast-normalize-headings@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-PlUAiR4x49XGYLoyEoQYbqJfO6MdcGt5Uq/0ivZvAUv55YABk8psT6177lhng6hAwTF64P6CjXrE8Aew8oqSAQ=="],
-
- "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
-
- "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
-
- "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
-
- "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
-
- "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
-
- "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
-
- "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
-
- "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
-
- "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
-
- "mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="],
-
- "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
-
- "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
-
- "mdast-util-wiki-link": ["mdast-util-wiki-link@0.1.2", "", { "dependencies": { "@babel/runtime": "^7.12.1", "mdast-util-to-markdown": "^0.6.5" } }, "sha512-DTcDyOxKDo3pB3fc0zQlD8myfQjYkW4hazUKI9PUyhtoj9JBeHC2eIdlVXmaT22bZkFAVU2d47B6y2jVKGoUQg=="],
-
- "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
-
- "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
-
- "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
-
- "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
-
- "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
-
- "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
-
- "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
-
- "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
-
- "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
-
- "micromark-extension-wiki-link": ["micromark-extension-wiki-link@0.0.4", "", { "dependencies": { "@babel/runtime": "^7.12.1" } }, "sha512-dJc8AfnoU8BHkN+7fWZvIS20SMsMS1ZlxQUn6We67MqeKbOiEDZV5eEvCpwqGBijbJbxX3Kxz879L4K9HIiOvw=="],
-
- "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
-
- "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
-
- "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
-
- "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
-
- "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
-
- "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
-
- "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
-
- "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
-
- "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
-
- "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
-
- "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
-
- "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
-
- "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
-
- "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
-
- "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
-
- "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
-
- "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
-
- "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
-
- "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
-
- "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
-
- "parse-entities": ["parse-entities@2.0.0", "", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="],
-
- "parse5": ["parse5@7.2.1", "", { "dependencies": { "entities": "^4.5.0" } }, "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ=="],
-
- "property-information": ["property-information@7.0.0", "", {}, "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg=="],
-
- "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="],
-
- "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
-
- "regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="],
-
- "rehype-headings-normalize": ["rehype-headings-normalize@0.0.2", "", { "dependencies": { "hast-util-heading-rank": "^3.0.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "rehype-stringify": "^10.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", "unified": "^11.0.4" } }, "sha512-YIK76cnNu95g31etRITkKqxTJ2WAbfKUnzyKv5nSWoUlBM9YPR6n4MzyBke2gzy0WusejSBOGgiplw32rR296Q=="],
-
- "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
-
- "rehype-shift-heading": ["rehype-shift-heading@2.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-shift-heading": "^4.0.0" } }, "sha512-ykIgOpIop4Telm+JXv0yDaN8jeMSBE9jwcEOxQJrlSnC15OVFq4/jS/X3MC6zjhfGYAUyvMcYSY0eORne/gX5A=="],
-
- "rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="],
-
- "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
-
- "remark-normalize-headings": ["remark-normalize-headings@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-normalize-headings": "^4.0.0" } }, "sha512-fSE2hJxQlqxEsiB/Oni3UcERQ0IbnUuO3RNmQd4LjgEaknek0I//fK05wgFrKP/wYhWEWA+hgm1MHkddpPDcJQ=="],
-
- "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
-
- "remark-rehype": ["remark-rehype@11.1.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ=="],
-
- "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
-
- "remark-wiki-link": ["remark-wiki-link@2.0.1", "", { "dependencies": { "@babel/runtime": "^7.4.4", "mdast-util-wiki-link": "^0.1.2", "micromark-extension-wiki-link": "^0.0.4" } }, "sha512-F8Eut1E7GWfFm4ZDTI6/4ejeZEHZgnVk6E933Yqd/ssYsc4AyI32aGakxwsGcEzbbE7dkWi1EfLlGAdGgOZOsA=="],
-
- "repeat-string": ["repeat-string@1.6.1", "", {}, "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w=="],
-
- "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
-
- "slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="],
-
- "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
-
- "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
-
- "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
-
- "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
- "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
+ "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
- "unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="],
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
- "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
+ "yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="],
- "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
+ "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
- "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
-
- "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="],
-
- "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
-
- "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="],
-
- "vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="],
-
- "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
-
- "yaml": ["yaml@2.7.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="],
+ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
-
- "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
-
- "hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
-
- "mdast-util-wiki-link/mdast-util-to-markdown": ["mdast-util-to-markdown@0.6.5", "", { "dependencies": { "@types/unist": "^2.0.0", "longest-streak": "^2.0.0", "mdast-util-to-string": "^2.0.0", "parse-entities": "^2.0.0", "repeat-string": "^1.0.0", "zwitch": "^1.0.0" } }, "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ=="],
-
- "parse-entities/character-entities": ["character-entities@1.2.4", "", {}, "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="],
-
- "parse-entities/character-entities-legacy": ["character-entities-legacy@1.1.4", "", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="],
-
- "mdast-util-wiki-link/mdast-util-to-markdown/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
-
- "mdast-util-wiki-link/mdast-util-to-markdown/longest-streak": ["longest-streak@2.0.4", "", {}, "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg=="],
-
- "mdast-util-wiki-link/mdast-util-to-markdown/mdast-util-to-string": ["mdast-util-to-string@2.0.0", "", {}, "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w=="],
-
- "mdast-util-wiki-link/mdast-util-to-markdown/zwitch": ["zwitch@1.0.5", "", {}, "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw=="],
}
}
diff --git a/bunfig.toml b/bunfig.toml
new file mode 100644
index 0000000..a7461c6
--- /dev/null
+++ b/bunfig.toml
@@ -0,0 +1 @@
+preload = ["./src/preload/http-plugin.js"]
diff --git a/demo/main/binding.md b/demo/main/binding.md
new file mode 100644
index 0000000..cb097e7
--- /dev/null
+++ b/demo/main/binding.md
@@ -0,0 +1,14 @@
+---
+markdownKey: description
+options:
+ dunks:
+ key: dunkaroos
+sources:
+ - file: ./stats/*.toml
+ - url: https://git.astral.camp/endeavorance/emdy/raw/branch/main/package.json
+processors:
+ - ./processors/scream.js
+ - ./processors/announce-slam-dunks.js
+---
+
+A markdown-powered binding file!
diff --git a/demo/main/processors/announce-slam-dunks.js b/demo/main/processors/announce-slam-dunks.js
new file mode 100644
index 0000000..e1889a3
--- /dev/null
+++ b/demo/main/processors/announce-slam-dunks.js
@@ -0,0 +1,19 @@
+export const name = "Announce Slam Dunks";
+export const description = "Get hype when a file has slam dunks";
+
+export const step = ({ entries, options, ...rest }) => {
+ const { dunks } = options;
+ const slamDunkKey = dunks?.key ?? "slamDunks";
+
+ for (const entry of entries) {
+ if (slamDunkKey in entry.data) {
+ console.log(`Slam dunk!`);
+ }
+ }
+
+ return {
+ entries,
+ options,
+ ...rest,
+ };
+};
diff --git a/demo/main/processors/scream.js b/demo/main/processors/scream.js
new file mode 100644
index 0000000..4a8a629
--- /dev/null
+++ b/demo/main/processors/scream.js
@@ -0,0 +1,17 @@
+export function step(binding) {
+ const modifiedEntries = binding.entries.map(entry => {
+ if (binding.markdownKey in entry.data) {
+ entry.data = {
+ ...entry.data,
+ [binding.markdownKey]: entry.data[binding.markdownKey].toUpperCase()
+ }
+ }
+
+ return entry;
+ });
+
+ return {
+ ...binding,
+ entries: modifiedEntries
+ };
+}
diff --git a/demo/main/stats/statistics.toml b/demo/main/stats/statistics.toml
new file mode 100644
index 0000000..d28b28b
--- /dev/null
+++ b/demo/main/stats/statistics.toml
@@ -0,0 +1,2 @@
+player = "Flynn"
+dunkaroos = 100
diff --git a/demo/main/stories/a-neat-story.md b/demo/main/stories/a-neat-story.md
new file mode 100644
index 0000000..8b9e564
--- /dev/null
+++ b/demo/main/stories/a-neat-story.md
@@ -0,0 +1,5 @@
+---
+published: true
+---
+
+This is a neat story I wrote!
diff --git a/package.json b/package.json
index a1bf80f..e7e1428 100644
--- a/package.json
+++ b/package.json
@@ -1,43 +1,40 @@
{
- "name": "@proscenium/muse",
- "version": "0.0.1",
- "module": "index.ts",
+ "name": "@endeavorance/muse",
+ "version": "0.3.0",
+ "module": "dist/index.js",
+ "exports": {
+ ".": {
+ "import": "./dist/muse.js",
+ "types": "./dist/muse.d.ts"
+ }
+ },
"type": "module",
+ "files": [
+ "dist"
+ ],
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@types/bun": "latest",
- "@types/lodash-es": "^4.17.12",
- "@types/react": "^19.0.11",
- "@types/react-dom": "^19.0.4"
+ "dts-bundle-generator": "^9.5.1"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
- "@proscenium/playbill": "link:@proscenium/playbill",
+ "@endeavorance/emdy": "1.0.0",
"chalk": "^5.4.1",
- "classnames": "^2.5.1",
- "lodash-es": "^4.17.21",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "rehype-headings-normalize": "^0.0.2",
- "rehype-raw": "^7.0.0",
- "rehype-shift-heading": "^2.0.0",
- "rehype-stringify": "^10.0.1",
- "remark-gfm": "^4.0.1",
- "remark-normalize-headings": "^4.0.0",
- "remark-parse": "^11.0.0",
- "remark-rehype": "^11.1.1",
- "remark-wiki-link": "^2.0.1",
- "slugify": "^1.6.6",
- "unified": "^11.0.5",
+ "smol-toml": "^1.3.1",
"yaml": "^2.7.0",
"zod": "^3.24.1"
},
"scripts": {
"fmt": "bunx --bun biome check --fix",
- "build": "bun build ./src/index.ts --outfile muse --compile",
- "demo": "bun run ./src/index.ts -- ./demo-data/great-spires/binding.yaml",
- "build:install": "bun run build && mv muse ~/.local/bin/muse"
+ "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 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/index.ts -- ./demo/main"
}
}
diff --git a/src/cli/index.ts b/src/cli/index.ts
new file mode 100644
index 0000000..edb3d9d
--- /dev/null
+++ b/src/cli/index.ts
@@ -0,0 +1,152 @@
+import { parseArgs } from "node:util";
+import chalk from "chalk";
+import { loadBinding } from "#core/binding";
+import type { MusePlugin } from "#core/plugins";
+import { MuseError } from "#errors";
+import { version } from "../../package.json";
+
+interface CLIFlags {
+ help: boolean;
+ verbose: boolean;
+ stdout: boolean;
+}
+
+interface Loggers {
+ log: (msg: string) => void;
+ verbose: (msg: string) => void;
+}
+
+enum ExitCode {
+ Success = 0,
+ Error = 1,
+}
+
+export const USAGE = `
+${chalk.bold("muse")} - Compile and process troves of data
+${chalk.dim(`v${version}`)}
+
+Usage:
+ muse [/path/to/binding.yaml]
+
+Options:
+ --stdout -s Output final data to stdout
+ --verbose, -v Enable verbose logging
+ --help, -h Show this help message
+`.trim();
+
+/**
+ * Given an array of CLI arguments, parse them into a structured object
+ *
+ * @param argv The arguments to parse
+ * @returns The parsed CLI arguments
+ *
+ * @throws {CLIError} if the arguments are invalid
+ */
+function parseCLIArguments(argv: string[]): [string, CLIFlags] {
+ const { values: options, positionals: args } = parseArgs({
+ args: argv,
+ options: {
+ help: {
+ short: "h",
+ default: false,
+ type: "boolean",
+ },
+ verbose: {
+ short: "v",
+ default: false,
+ type: "boolean",
+ },
+ stdout: {
+ short: "s",
+ default: false,
+ type: "boolean",
+ },
+ },
+ strict: true,
+ allowPositionals: true,
+ });
+
+ return [args[0] ?? "./", options];
+}
+
+function getStepLogLine(plugin: MusePlugin): string {
+ const { name, description } = plugin;
+ let processorLogLine = chalk.bold(`↪ ${name}`);
+
+ if (description && description.length > 0) {
+ processorLogLine += chalk.dim(` (${description})`);
+ }
+
+ return processorLogLine;
+}
+
+async function processBinding(
+ inputFilePath: string,
+ flags: CLIFlags,
+ { log, verbose }: Loggers,
+) {
+ // Load the binding
+ let binding = await loadBinding(inputFilePath);
+ verbose(`Binding ${binding.bindingPath}`);
+
+ const entryCount = binding.entries.length;
+ const stepCount = binding.plugins.length;
+ const stepWord = stepCount === 1 ? "step" : "steps";
+ log(`Processing ${entryCount} entries with ${stepCount} ${stepWord}`);
+
+ const processStart = performance.now();
+
+ // Run the data through relevant plugins
+ for (const plugin of binding.plugins) {
+ log(getStepLogLine(plugin));
+ const { step } = plugin;
+ binding = await step(binding);
+ }
+
+ const processEnd = performance.now();
+ const processTime = ((processEnd - processStart) / 1000).toFixed(2);
+ verbose(`Processing completed in ${processTime}s`);
+
+ if (flags.stdout) {
+ console.log(JSON.stringify(binding, null, 2));
+ }
+ return ExitCode.Success;
+}
+
+async function main(): Promise {
+ const [inputFilePath, flags] = parseCLIArguments(Bun.argv.slice(2));
+
+ // If --help is specified, print usage and exit
+ if (flags.help) {
+ console.log(USAGE);
+ return ExitCode.Success;
+ }
+
+ const logFn = !flags.stdout ? console.log : () => {};
+ const verboseFn = flags.verbose
+ ? (msg: string) => {
+ console.log(chalk.dim(msg));
+ }
+ : () => {};
+
+ const lastProcessResult = await processBinding(inputFilePath, flags, {
+ log: logFn,
+ verbose: verboseFn,
+ });
+
+ return lastProcessResult;
+}
+
+try {
+ const exitCode = await main();
+ process.exit(exitCode);
+} catch (error) {
+ if (error instanceof MuseError) {
+ console.error(chalk.red(error.fmt()));
+ } else {
+ console.error("An unexpected error occurred");
+ console.error(error);
+ }
+
+ process.exit(1);
+}
diff --git a/src/core/binding.ts b/src/core/binding.ts
new file mode 100644
index 0000000..7652c0a
--- /dev/null
+++ b/src/core/binding.ts
@@ -0,0 +1,145 @@
+import path, { dirname } from "node:path";
+import { z } from "zod";
+import type { UnknownRecord } from "#core/records";
+import { MuseError, MuseFileNotFoundError } from "#errors";
+import { resolveFilePath } from "#util";
+import { type MusePlugin, loadPlugin } from "./plugins";
+import {
+ type MuseEntry,
+ type MuseSource,
+ SourceSchema,
+ loadFromSource,
+ parseMuseFile,
+ sourceShorthandToSource,
+} from "./sources";
+
+// Function to parse an unknown object into parts of a binding
+const BindingSchema = z.object({
+ contentKey: z.string().default("content"),
+ sources: z.array(SourceSchema).default([
+ {
+ file: "./**/*.{json,yaml,toml,md}",
+ },
+ ]),
+ options: z.record(z.string(), z.any()).default({}),
+ plugins: z.array(z.string()).default([]),
+});
+
+/**
+ * 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 {
+ /** Information about the sources used to load entries */
+ readonly sources: MuseSource[];
+
+ /** The full file path to the binding file */
+ readonly bindingPath: string;
+
+ /** 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 plugins: MusePlugin[];
+
+ /** Arbitrary metadata for processors to use to cache project data */
+ readonly meta: MetaShape;
+
+ /** Configuration options for Muse and all processors */
+ readonly options: UnknownRecord;
+}
+
+/**
+ * Given a path, find the binding file
+ * If the path is to a directory, check for a binding inside
+ * Check order: binding.json, binding.yaml, binding.toml, binding.md
+ * Otherwise throw an error
+ *
+ * @param bindingPath - The path to the binding file
+ * @returns The absolute resolved path to the binding file
+ */
+async function resolveBindingPath(bindingPath: string): Promise {
+ 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()) {
+ for (const bindingFile of DEFAULT_BINDING_FILES) {
+ const filePath = path.resolve(bindingPath, bindingFile);
+ const resolvedPath = await resolveFilePath(filePath);
+ if (resolvedPath) {
+ return resolvedPath;
+ }
+ }
+
+ 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);
+ }
+
+ // If it is a file, return the path
+ return path.resolve(bindingPath);
+}
+
+export async function loadBinding(initialPath: string): Promise {
+ // Resolve the file location if possible
+ const bindingPath = await resolveBindingPath(initialPath);
+ const bindingDirname = dirname(bindingPath);
+ const loadedBindingData = await parseMuseFile(bindingPath, {
+ contentKey: "content",
+ cwd: bindingDirname,
+ ignore: [],
+ });
+
+ if (loadedBindingData.length === 0) {
+ throw new MuseError("No entries found in binding file");
+ }
+
+ // Load and parse the Binding YAML content
+ const parsedBinding = BindingSchema.parse(loadedBindingData[0].data);
+
+ const sourceOptions = {
+ cwd: bindingDirname,
+ contentKey: parsedBinding.contentKey,
+ ignore: [bindingPath],
+ };
+
+ const sources = parsedBinding.sources.map(sourceShorthandToSource);
+
+ const entries: MuseEntry[] = [];
+
+ for (const source of sources) {
+ const entriesFromSource = await loadFromSource(source, sourceOptions);
+ entries.push(...entriesFromSource);
+ }
+
+ // Load and check plugins
+ const plugins: MusePlugin[] = await Promise.all(
+ parsedBinding.plugins.map(loadPlugin.bind(null, bindingDirname)),
+ );
+
+ return {
+ sources,
+ bindingPath,
+ contentKey: parsedBinding.contentKey,
+ entries,
+ options: parsedBinding.options,
+ plugins,
+ meta: {},
+ };
+}
diff --git a/src/core/files.ts b/src/core/files.ts
new file mode 100644
index 0000000..89b2817
--- /dev/null
+++ b/src/core/files.ts
@@ -0,0 +1,34 @@
+import { basename, dirname, extname, normalize } from "node:path";
+import { MuseFileNotFoundError } from "#errors";
+
+export interface LoadedFile {
+ content: string;
+ filePath: string;
+ fileType: string;
+ dirname: string;
+ basename: string;
+ mimeType: string;
+}
+
+export async function loadFileContent(filePath: string): Promise {
+ const normalizedPath = normalize(filePath);
+ const file = Bun.file(normalizedPath);
+
+ const exists = await file.exists();
+ if (!exists) {
+ throw new MuseFileNotFoundError(normalizedPath);
+ }
+
+ const filecontent = await file.text();
+
+ const extension = extname(normalizedPath);
+
+ return {
+ content: filecontent,
+ filePath: normalizedPath,
+ fileType: extension.slice(1),
+ dirname: dirname(normalizedPath),
+ basename: basename(normalizedPath, extension),
+ mimeType: file.type,
+ };
+}
diff --git a/src/core/plugins.ts b/src/core/plugins.ts
new file mode 100644
index 0000000..3f2ffed
--- /dev/null
+++ b/src/core/plugins.ts
@@ -0,0 +1,52 @@
+import path from "node:path";
+import { MuseError, MusePluginError } from "#errors";
+import type { Binding } from "./binding";
+
+export type MuseStepFn = (binding: Binding) => Promise;
+
+export interface MusePlugin {
+ readonly filePath: string;
+ readonly name: string;
+ readonly description: string;
+ readonly version: string;
+ readonly step: MuseStepFn;
+}
+
+export async function loadPlugin(
+ bindingDirname: string,
+ pluginPath: string,
+): Promise {
+ // Resolve local paths, use URLs as-is
+ const resolvedProcessorPath = pluginPath.startsWith("http")
+ ? pluginPath
+ : path.resolve(bindingDirname, pluginPath);
+ const { step, name, description, version } = await import(
+ resolvedProcessorPath
+ );
+
+ if (!step || typeof step !== "function") {
+ throw new MuseError(
+ `Processor at ${pluginPath} does not export a step() function`,
+ );
+ }
+
+ if (name && typeof name !== "string") {
+ throw new MusePluginError(pluginPath, "Plugin name is not a string.");
+ }
+
+ if (description && typeof description !== "string") {
+ throw new MusePluginError(pluginPath, "Plugin description is not a string");
+ }
+
+ if (version && typeof version !== "string") {
+ throw new MusePluginError(pluginPath, "Plugin version is not a string");
+ }
+
+ return {
+ filePath: resolvedProcessorPath,
+ step: step as MuseStepFn,
+ name: String(name ?? pluginPath),
+ description: String(description ?? ""),
+ version: String(version ?? "0.0.0"),
+ };
+}
diff --git a/src/core/records.ts b/src/core/records.ts
new file mode 100644
index 0000000..891b18e
--- /dev/null
+++ b/src/core/records.ts
@@ -0,0 +1,107 @@
+import EMDY from "@endeavorance/emdy";
+import TOML from "smol-toml";
+import YAML from "yaml";
+import type { LoadedFile } from "./files";
+
+export type UnknownRecord = Record;
+
+/**
+ * Given an unknown shape, ensure it is an object or array of objects,
+ * then return it as an array of objects.
+ *
+ * @param val - The unknown value to format
+ * @returns - An array of objects
+ */
+function formatEntries(val: unknown): UnknownRecord[] {
+ if (Array.isArray(val) && val.every((el) => typeof el === "object")) {
+ return val;
+ }
+
+ if (typeof val === "object" && val !== null) {
+ return [val as UnknownRecord];
+ }
+
+ throw new Error(
+ `Invalid data format. Entry files must define an object or array of objects, but found "${val}"`,
+ );
+}
+
+/**
+ * Parse one or more YAML documents from a string
+ *
+ * @param text - The YAML string to parse
+ * @returns - An array of parsed objects
+ */
+export function parseYAMLEntries(text: string): UnknownRecord[] {
+ const parsedDocs = YAML.parseAllDocuments(text);
+
+ if (parsedDocs.some((doc) => doc.toJS() === null)) {
+ throw new Error("Encountered NULL resource");
+ }
+
+ const errors = parsedDocs.flatMap((doc) => doc.errors);
+
+ if (errors.length > 0) {
+ throw new Error(
+ `Error parsing YAML resource: ${errors.map((e) => e.message).join(", ")}`,
+ );
+ }
+
+ const collection: UnknownRecord[] = parsedDocs.map((doc) => doc.toJS());
+ return collection;
+}
+
+/**
+ * Parse one or more JSON documents from a string
+ *
+ * @param text - The JSON string to parse
+ * @returns - An array of parsed objects
+ */
+export function parseJSONEntries(text: string): UnknownRecord[] {
+ return formatEntries(JSON.parse(text));
+}
+
+/**
+ * Parse one or more TOML documents from a string
+ *
+ * @param text - The TOML string to parse
+ * @returns - An array of parsed objects
+ */
+export function parseTOMLEntries(text: string): UnknownRecord[] {
+ return formatEntries(TOML.parse(text));
+}
+
+/**
+ * Parse one or more Markdown documents from a string
+ *
+ * @param text - The Markdown string to parse
+ * @returns - An array of parsed objects
+ */
+export function parseMarkdownEntry(
+ text: string,
+ contentKey: string,
+): UnknownRecord[] {
+ return formatEntries(EMDY.parse(text, contentKey));
+}
+
+export function autoParseEntry(loadedFile: LoadedFile, contentKey: string) {
+ const { fileType, content } = loadedFile;
+
+ if (fileType === "md") {
+ return parseMarkdownEntry(content, contentKey);
+ }
+
+ if (fileType === "yaml") {
+ return parseYAMLEntries(content);
+ }
+
+ if (fileType === "json") {
+ return parseJSONEntries(content);
+ }
+
+ if (fileType === "toml") {
+ return parseTOMLEntries(content);
+ }
+
+ throw new Error(`Unsupported file type: ${fileType}`);
+}
diff --git a/src/core/sources.ts b/src/core/sources.ts
new file mode 100644
index 0000000..5c38e4b
--- /dev/null
+++ b/src/core/sources.ts
@@ -0,0 +1,172 @@
+import { Glob } from "bun";
+import { type LoadedFile, loadFileContent } from "./files";
+import { type UnknownRecord, autoParseEntry } from "./records";
+import { z } from "zod";
+import { MuseError } from "#errors";
+
+export const SourceSchema = z.union([
+ z.object({
+ file: z.string(),
+ }),
+ z.object({
+ url: z.string().url(),
+ }),
+]);
+
+export type SourceShorthand = z.infer;
+
+export function sourceShorthandToSource(
+ shorthand: SourceShorthand,
+): MuseSource {
+ if ("file" in shorthand) {
+ return {
+ location: shorthand.file,
+ type: "file",
+ };
+ }
+
+ if ("url" in shorthand) {
+ return {
+ location: shorthand.url,
+ type: "url",
+ };
+ }
+
+ throw new MuseError("Invalid source shorthand");
+}
+
+export type SourceType = "file" | "url";
+
+/** A descriptor of a location to load into a Muse binding */
+export interface MuseSource {
+ location: string;
+ type: SourceType;
+}
+
+interface SourceOptions {
+ cwd: string;
+ contentKey: string;
+ ignore: string[];
+}
+
+/** A single loaded entry from a Muse source */
+export interface MuseEntry {
+ _raw: string;
+ location: string;
+ data: Record;
+ meta: MetaShape;
+ source: MuseSource;
+}
+
+export async function parseMuseFile(
+ rawFilePath: string,
+ { contentKey = "content" }: SourceOptions,
+): Promise {
+ const file = await loadFileContent(rawFilePath);
+ const entries = autoParseEntry(file, contentKey);
+
+ const partial = {
+ _raw: file.content,
+ source: {
+ location: file.filePath,
+ type: "file" as SourceType,
+ },
+ location: file.filePath,
+ meta: {},
+ };
+
+ return entries.map((data) => ({
+ ...partial,
+ data,
+ }));
+}
+
+async function loadFromFileSource(
+ source: MuseSource,
+ options: SourceOptions,
+): Promise {
+ const paths = Array.from(
+ new Glob(source.location).scanSync({
+ cwd: options.cwd,
+ absolute: true,
+ followSymlinks: true,
+ onlyFiles: true,
+ }),
+ );
+
+ const filteredPaths = paths.filter((path) => !options.ignore.includes(path));
+
+ const entries: MuseEntry[] = [];
+ for (const filePath of filteredPaths) {
+ const fileEntries = await parseMuseFile(filePath, options);
+ entries.push(...fileEntries);
+ }
+
+ return entries;
+}
+
+function getFileExtensionFromURL(url: string): string | null {
+ const parsedUrl = new URL(url);
+
+ const pathname = parsedUrl.pathname;
+ const filename = pathname.substring(pathname.lastIndexOf("/") + 1);
+ const extension = filename.substring(filename.lastIndexOf(".") + 1);
+
+ return extension === filename ? null : extension;
+}
+
+async function loadFromURLSource(
+ source: MuseSource,
+ options: SourceOptions,
+): Promise {
+ const { contentKey = "content" } = options;
+ const response = await fetch(source.location);
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch URL: ${source.location}`);
+ }
+ const content = await response.text();
+ const mimeType = response.headers.get("Content-Type") || "unknown";
+
+ const parseType = getFileExtensionFromURL(source.location) ?? "unknown";
+
+ const loadedFile: LoadedFile = {
+ content,
+ filePath: source.location,
+ fileType: parseType,
+ dirname: "",
+ basename: "",
+ mimeType,
+ };
+
+ const entries = autoParseEntry(loadedFile, contentKey);
+
+ const partial = {
+ _raw: content,
+ source: {
+ location: source.location,
+ type: "url" as SourceType,
+ },
+ location: source.location,
+ meta: {},
+ };
+
+ return entries.map((data) => ({
+ ...partial,
+ data,
+ }));
+}
+
+export async function loadFromSource(
+ source: MuseSource,
+ options: SourceOptions,
+): Promise {
+ switch (source.type) {
+ case "file":
+ return loadFromFileSource(source, options);
+ case "url":
+ return loadFromURLSource(source, options);
+ default:
+ throw new Error(`Unsupported source type: ${source.type}`);
+ }
+}
diff --git a/src/css.d.ts b/src/css.d.ts
deleted file mode 100644
index cb02ac0..0000000
--- a/src/css.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-declare module '*.css' {
-}
diff --git a/src/define/index.ts b/src/define/index.ts
deleted file mode 100644
index 68b0aa6..0000000
--- a/src/define/index.ts
+++ /dev/null
@@ -1,326 +0,0 @@
-import {
- type Ability,
- type AnyPlaybillComponent,
- type Blueprint,
- type Item,
- type Method,
- type Resource,
- type Rule,
- type Species,
- parseAbility,
- parseBlueprint,
- parseItem,
- parseMethod,
- parseResource,
- parseRule,
- parseSpecies,
-} from "@proscenium/playbill";
-import { z } from "zod";
-
-const Base = z.object({
- $define: z.string(),
- $hidden: z.boolean().default(false),
- id: z.string(), // TODO: Validate ID shapes
- name: z.string().default("Unnamed Component"),
- description: z.string().default("No description provided"),
- categories: z.array(z.string()).default([]),
-});
-
-const AbilitySchema = Base.extend({
- $define: z.literal("ability"),
- type: z.string().default("action"),
- ap: z.number().int().default(0),
- hp: z.number().int().default(0),
- ep: z.number().int().default(0),
- xp: z.number().int().default(1),
- damage: z.number().int().default(0),
- damageType: z.string().default("phy"),
- roll: z.string().default("none"),
-});
-
-const BlueprintSchema = Base.extend({
- $define: z.literal("blueprint"),
- species: z.string(),
- items: z.array(z.string()).default([]),
- abilities: z.array(z.string()).default([]),
- ap: z.number().int().default(4),
- hp: z.number().int().default(5),
- ep: z.number().int().default(1),
- xp: z.number().int().default(0),
- muscle: z.string().default("novice"),
- focus: z.string().default("novice"),
- knowledge: z.string().default("novice"),
- charm: z.string().default("novice"),
- cunning: z.string().default("novice"),
- spark: z.string().default("novice"),
-});
-
-const GlossarySchema = Base.extend({
- $define: z.literal("glossary"),
- terms: z.record(z.string(), z.string()),
-});
-
-const BaseItem = Base.extend({
- $define: z.literal("item"),
- rarity: z.string().default("common"),
- affinity: z.string().default("none"),
- damage: z.number().int().default(0),
- damageType: z.string().default("phy"),
- abilities: z.array(z.string()).default([]),
-});
-
-const ItemSchema = z.discriminatedUnion("type", [
- BaseItem.extend({
- type: z.literal("wielded"),
- hands: z.number().int().default(1),
- }),
- BaseItem.extend({
- type: z.literal("worn"),
- slot: z.string().default("unique"),
- }),
- BaseItem.extend({
- type: z.literal("trinket"),
- }),
-]);
-
-const MethodSchema = Base.extend({
- $define: z.literal("method"),
- curator: z.string().default("Someone"),
- abilities: z.array(z.array(z.string())).default([]),
- rank1: z.array(z.string()).default([]),
- rank2: z.array(z.string()).default([]),
- rank3: z.array(z.string()).default([]),
- rank4: z.array(z.string()).default([]),
- rank5: z.array(z.string()).default([]),
- rank6: z.array(z.string()).default([]),
- rank7: z.array(z.string()).default([]),
- rank8: z.array(z.string()).default([]),
- rank9: z.array(z.string()).default([]),
-});
-
-const ResourceSchema = z.discriminatedUnion("type", [
- Base.extend({
- $define: z.literal("resource"),
- type: z.literal("text"),
- }),
- Base.extend({
- $define: z.literal("resource"),
- type: z.literal("image"),
- url: z.string(),
- }),
- Base.extend({
- $define: z.literal("resource"),
- type: z.literal("table"),
- data: z.array(z.array(z.string())),
- }),
-]);
-
-const RuleSchema = Base.extend({
- $define: z.literal("rule"),
- overrule: z.nullable(z.string()).default(null),
- order: z.number().int().default(Number.POSITIVE_INFINITY),
-});
-
-const SpeciesSchema = Base.extend({
- $define: z.literal("species"),
- hands: z.number().int().default(2),
- abilities: z.array(z.string()).default([]),
- ap: z.number().int().default(0),
- hp: z.number().int().default(0),
- ep: z.number().int().default(0),
- xp: z.number().int().default(0),
- muscle: z.string().default("novice"),
- focus: z.string().default("novice"),
- knowledge: z.string().default("novice"),
- charm: z.string().default("novice"),
- cunning: z.string().default("novice"),
- spark: z.string().default("novice"),
-});
-
-const SchemaMapping = {
- ability: AbilitySchema,
- blueprint: BlueprintSchema,
- glossary: GlossarySchema,
- item: ItemSchema,
- method: MethodSchema,
- resource: ResourceSchema,
- rule: RuleSchema,
- species: SpeciesSchema,
-} as const;
-
-type ComponentType = keyof typeof SchemaMapping;
-
-const parseComponentType = (val: unknown): ComponentType => {
- if (typeof val !== "string") {
- throw new Error("Component type must be a string");
- }
-
- if (!(val in SchemaMapping)) {
- throw new Error(`Unknown component type: ${val}`);
- }
-
- return val as ComponentType;
-};
-
-function parseAbilityDefinition(obj: unknown): Ability {
- const parsed = AbilitySchema.parse(obj);
-
- const ability = parseAbility({
- id: parsed.id,
- name: parsed.name,
- description: parsed.description,
- categories: parsed.categories,
- type: parsed.type,
- ap: parsed.ap,
- ep: parsed.ep,
- hp: parsed.hp,
- xp: parsed.xp,
- damage:
- parsed.damage > 0
- ? {
- amount: parsed.damage,
- type: parsed.damageType,
- }
- : null,
- roll: parsed.roll,
- });
-
- return ability;
-}
-
-function parseBlueprintDefinition(obj: unknown): Blueprint {
- const parsed = BlueprintSchema.parse(obj);
-
- const blueprint = parseBlueprint({
- id: parsed.id,
- name: parsed.name,
- description: parsed.description,
- categories: parsed.categories,
- species: parsed.species,
- items: parsed.items,
- abilities: parsed.abilities,
- baggage: [],
- ap: parsed.ap,
- hp: parsed.hp,
- ep: parsed.ep,
- xp: parsed.xp,
- muscle: parsed.muscle,
- focus: parsed.focus,
- knowledge: parsed.knowledge,
- charm: parsed.charm,
- cunning: parsed.cunning,
- spark: parsed.spark,
- });
-
- return blueprint;
-}
-
-function parseItemDefinition(obj: unknown): Item {
- const parsed = ItemSchema.parse(obj);
-
- return parseItem({
- ...parsed,
- damage:
- parsed.damage > 0
- ? {
- amount: parsed.damage,
- type: parsed.damageType,
- }
- : null,
- abilities: parsed.abilities,
- tweak: "",
- temper: "",
- });
-}
-
-function parseMethodDefinition(obj: unknown): Method {
- const parsed = MethodSchema.parse(obj);
-
- // Prefer `abilities` if defined, otherwise
- // construct `abilities` using the rankX properties
- const abilities = parsed.abilities;
-
- if (abilities.length === 0) {
- const allRanks = [
- parsed.rank1,
- parsed.rank2,
- parsed.rank3,
- parsed.rank4,
- parsed.rank5,
- parsed.rank6,
- parsed.rank7,
- parsed.rank8,
- parsed.rank9,
- ];
-
- // Add all ranks until an empty rank is found
- for (const rank of allRanks) {
- if (rank.length > 0) {
- abilities.push(rank);
- } else {
- break;
- }
- }
- }
-
- const method = {
- id: parsed.id,
- name: parsed.name,
- description: parsed.description,
- categories: parsed.categories,
- curator: parsed.curator,
- abilities: abilities,
- };
-
- return parseMethod(method);
-}
-
-function parseResourceDefinition(obj: unknown): Resource {
- const parsed = ResourceSchema.parse(obj);
- return parseResource(parsed);
-}
-
-function parseRuleDefinition(obj: unknown): Rule {
- const parsed = RuleSchema.parse(obj);
-
- return parseRule(parsed);
-}
-
-function parseSpeciesDefinition(obj: unknown): Species {
- const parsed = SpeciesSchema.parse(obj);
-
- return parseSpecies(parsed);
-}
-
-export function parsePlaybillComponent(
- obj: unknown,
-): AnyPlaybillComponent | null {
- const baseParse = Base.parse(obj);
-
- if (baseParse.$hidden) {
- return null;
- }
-
- const type = parseComponentType(baseParse.$define);
- const schema = SchemaMapping[type];
- const component = schema.parse(obj);
-
- switch (type) {
- case "ability":
- return parseAbilityDefinition(component);
- case "blueprint":
- return parseBlueprintDefinition(component);
- case "item":
- return parseItemDefinition(component);
- case "method":
- return parseMethodDefinition(component);
- case "resource":
- return parseResourceDefinition(component);
- case "rule":
- return parseRuleDefinition(component);
- case "species":
- return parseSpeciesDefinition(component);
- default:
- throw new Error(`Unknown component type: ${type}`);
- }
-}
diff --git a/src/errors.ts b/src/errors.ts
new file mode 100644
index 0000000..5f43323
--- /dev/null
+++ b/src/errors.ts
@@ -0,0 +1,41 @@
+export class MuseError extends Error {
+ fmt(): string {
+ return `-- ERROR -- \nDetails: ${this.message}`;
+ }
+}
+
+export class MusePluginError extends MuseError {
+ plugin: string;
+
+ constructor(message: string, plugin: string) {
+ super(message);
+ this.plugin = plugin;
+ }
+
+ fmt(): string {
+ return `-- PLUGIN ERROR --\Plugin: ${this.plugin}\nDetails: ${this.message}`;
+ }
+}
+
+export class MuseFileError extends MuseError {
+ filePath: string;
+
+ constructor(message: string, filePath: string) {
+ super(message);
+ this.filePath = filePath;
+ }
+
+ fmt(): string {
+ return `-- FILE ERROR --\nFile Path: ${this.filePath}\nDetails: ${this.message}`;
+ }
+}
+
+export class MuseFileNotFoundError extends MuseFileError {
+ constructor(filePath: string) {
+ super("File not found", filePath);
+ this.message = "File does not exist";
+ this.filePath = filePath;
+ }
+}
+
+export class CLIError extends MuseError {}
diff --git a/src/exports.ts b/src/exports.ts
new file mode 100644
index 0000000..451c4f8
--- /dev/null
+++ b/src/exports.ts
@@ -0,0 +1,12 @@
+// Content exports
+export * from "#errors";
+
+// Constants
+export const DEFAULT_CONTENT_KEY = "content";
+
+// Types
+export type * from "#core/binding";
+export type * from "#core/plugins";
+export type * from "#core/sources";
+export type * from "#core/records";
+export type * from "#core/files";
diff --git a/src/index.ts b/src/index.ts
deleted file mode 100644
index 23d166c..0000000
--- a/src/index.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import { watch } from "node:fs/promises";
-import chalk from "chalk";
-import {
- CLIError,
- MuseError,
- compileMarkdownInPlaybill,
- loadFromBinding,
- resolveBindingPath,
-} from "#lib";
-import { renderPlaybillToHTML } from "#render/html";
-import {
- type CLIArguments,
- getAbsoluteDirname,
- parseCLIArguments,
- USAGE,
-} from "#util";
-
-enum ExitCode {
- Success = 0,
- Error = 1,
-}
-
-async function processBinding({ inputFilePath, options }: CLIArguments) {
- // Load the binding
- const bindingPath = await resolveBindingPath(inputFilePath);
- const binding = await loadFromBinding(bindingPath);
-
- // If --check is specified, exit early
- if (options.check) {
- console.log(chalk.green("Playbill validated successfully"));
- return ExitCode.Success;
- }
-
- if (options.markdown) {
- await compileMarkdownInPlaybill(binding);
- }
-
- // Serialize (default: JSON)
- let serializedPlaybill = "";
-
- switch (options.renderer) {
- case "json":
- serializedPlaybill = binding.playbill.serialize();
- break;
- case "html":
- serializedPlaybill = await renderPlaybillToHTML(binding);
- break;
- default:
- throw new CLIError(`Unknown renderer: ${options.renderer}`);
- }
-
- // Write to disk if --outfile is specified
- if (options.outfile !== "") {
- await Bun.write(options.outfile, serializedPlaybill);
- return ExitCode.Success;
- }
-
- // Otherwise, write to stdout
- console.log(serializedPlaybill);
- return ExitCode.Success;
-}
-
-async function main(): Promise {
- const cliArguments = parseCLIArguments(Bun.argv.slice(2));
- const { options } = cliArguments;
-
- // If --help is specified, print usage and exit
- if (options.help) {
- console.log(USAGE);
- return ExitCode.Success;
- }
-
- let lastProcessResult = await processBinding(cliArguments);
-
- if (options.watch) {
- const watchDir = getAbsoluteDirname(cliArguments.inputFilePath);
-
- console.log(`Watching ${watchDir} for changes...`);
-
- const watcher = watch(watchDir, {
- recursive: true,
- });
-
- for await (const event of watcher) {
- console.log(
- `Detected ${event.eventType} on ${event.filename}. Reprocessing...`,
- );
-
- try {
- lastProcessResult = await processBinding(cliArguments);
-
- if (lastProcessResult === ExitCode.Error) {
- console.error(`Error processing ${event.filename}`);
- } else {
- console.log("Reprocessed changes");
- }
- } catch (error) {
- console.error(`Error processing ${event.filename}`);
- console.error(error);
- }
- }
- return ExitCode.Success;
- }
-
- return ExitCode.Success;
-}
-
-try {
- const exitCode = await main();
- process.exit(exitCode);
-} catch (error) {
- if (error instanceof MuseError) {
- console.error(chalk.red(error.fmt()));
- } else {
- console.error("An unexpected error occurred");
- console.error(error);
- }
-
- process.exit(1);
-}
diff --git a/src/lib/binding.ts b/src/lib/binding.ts
deleted file mode 100644
index 9e75fc8..0000000
--- a/src/lib/binding.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-import path from "node:path";
-import { type AnyPlaybillComponent, Playbill } from "@proscenium/playbill";
-import { Glob } from "bun";
-import z from "zod";
-import { loadYAMLFileOrFail } from "#util";
-import { ComponentFile } from "./component-file";
-import { FileNotFoundError } from "./errors";
-
-// binding.yaml file schema
-const BindingSchema = z.object({
- include: z.array(z.string()).default([]),
-
- name: z.string().default("Unnamed playbill"),
- author: z.string().default("Someone"),
- description: z.string().default("No description provided"),
- version: z.string().default("0.0.0"),
-
- files: z.array(z.string()).default(["**/*.yaml", "**/*.md"]),
- omitDefaultStyles: z.boolean().default(false),
- styles: z
- .union([z.string(), z.array(z.string())])
- .transform((val) => (Array.isArray(val) ? val : [val]))
- .default([]),
- terms: z.record(z.string(), z.string()).default({}),
-});
-
-export interface BoundPlaybill {
- // File information
- bindingFilePath: string;
- bindingFileDirname: string;
- files: string[];
- componentFiles: ComponentFile[];
-
- // Binding properties
- name: string;
- author: string;
- version: string;
- description: string;
-
- omitDefaultStyles: boolean;
- styles: string;
-
- playbill: Playbill;
-}
-
-/**
- * Given a path, find the binding.yaml file
- * If the path is to a directory, check for a binding.yaml inside
- * If the path is to a file, check if it exists and is a binding.yaml
- * Otherwise throw an error
- *
- * @param bindingPath - The path to the binding file
- * @returns The absolute resolved path to the binding file
- */
-export async function resolveBindingPath(bindingPath: string): Promise {
- // If the path does not specify a filename, use binding.yaml
- 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 {
- const resolvedBindingPath = await resolveBindingPath(bindingPath);
- const yamlContent = await loadYAMLFileOrFail(resolvedBindingPath);
- const binding = BindingSchema.parse(yamlContent);
- const fileGlobs = binding.files;
- const bindingFileDirname = path.dirname(resolvedBindingPath);
-
- const playbill = new Playbill(
- binding.name,
- binding.author,
- binding.description,
- binding.version,
- );
-
- // If this is extending another binding, load that first
- for (const includePath of binding.include) {
- const pathFromHere = path.resolve(bindingFileDirname, includePath);
- const extendBindingPath = await resolveBindingPath(pathFromHere);
- const loadedBinding = await loadFromBinding(extendBindingPath);
- playbill.extend(loadedBinding.playbill);
- }
-
- // Load any specified stylesheets
- const loadedStyles: string[] = [];
- for (const style of binding.styles) {
- const cssFilePath = path.resolve(bindingFileDirname, style);
- const cssFile = Bun.file(cssFilePath);
- const cssFileExists = await cssFile.exists();
-
- if (cssFileExists) {
- loadedStyles.push(await cssFile.text());
- } else {
- throw new FileNotFoundError(cssFilePath);
- }
- }
- const styles = loadedStyles.join("\n");
-
- // Scan for all files matching the globs
- 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);
- }
-
- // Exclude the binding.yaml file
- const filePathsWithoutBindingFile = allFilePaths.filter((filePath) => {
- return filePath !== resolvedBindingPath;
- });
-
- // Load discovered component files
- const componentFiles: ComponentFile[] = [];
- const fileLoadPromises: Promise[] = [];
-
- for (const filepath of filePathsWithoutBindingFile) {
- const componentFile = new ComponentFile(filepath);
- componentFiles.push(componentFile);
- fileLoadPromises.push(componentFile.load());
- }
-
- await Promise.all(fileLoadPromises);
-
- // Aggregate all loaded components
- const loadedComponents: AnyPlaybillComponent[] = [];
- for (const file of componentFiles) {
- loadedComponents.push(...file.components);
- }
-
- // Add all components to the playbill
- // (This method ensures proper addition order maintain correctness)
- playbill.addManyComponents(loadedComponents);
-
- // Add all definitions
- for (const [term, definition] of Object.entries(binding.terms)) {
- playbill.addDefinition(term, definition);
- }
-
- return {
- bindingFilePath: bindingPath,
- bindingFileDirname,
- files: filePathsWithoutBindingFile,
- componentFiles,
-
- name: binding.name,
- author: binding.author ?? "Anonymous",
- description: binding.description ?? "",
- version: binding.version,
-
- omitDefaultStyles: binding.omitDefaultStyles,
- styles,
-
- playbill,
- };
-}
diff --git a/src/lib/component-file.ts b/src/lib/component-file.ts
deleted file mode 100644
index 0dd6ea4..0000000
--- a/src/lib/component-file.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-import path from "node:path";
-import { parsePlaybillComponent } from "define";
-import YAML, { YAMLParseError } from "yaml";
-import { ZodError } from "zod";
-import { loadFileOrFail } from "#util";
-import { MalformedResourceFileError } from "./errors";
-import { toSlug } from "./slug";
-import type { AnyPlaybillComponent } from "@proscenium/playbill";
-
-type FileFormat = "yaml" | "markdown";
-
-const FRONTMATTER_REGEX = /^---[\s\S]*?---/gm;
-
-/**
- * Attempt to parse YAML frontmatter from a mixed yaml/md doc
- * @param content The raw markdown content
- * @returns Any successfully parsed frontmatter
- */
-function extractFrontmatter(content: string) {
- // If it does not start with `---`, it is invalid for frontmatter
- if (content.trim().indexOf("---") !== 0) {
- return {};
- }
-
- if (FRONTMATTER_REGEX.test(content)) {
- const frontmatterString = content.match(FRONTMATTER_REGEX)?.[0] ?? "";
- const cleanFrontmatter = frontmatterString.replaceAll("---", "").trim();
- return YAML.parse(cleanFrontmatter);
- }
-
- return {};
-}
-
-/**
- * Given a string of a markdown document, extract the markdown content
- * @param content The raw markdown content
- * @returns The markdown content without frontmatter
- */
-function extractMarkdown(content: string): string {
- if (content.trim().indexOf("---") !== 0) {
- return content;
- }
- return content.replace(FRONTMATTER_REGEX, "").trim();
-}
-
-function parseYAMLResourceFile(
- filePath: string,
- text: string,
-): AnyPlaybillComponent[] {
- const parsedDocs = YAML.parseAllDocuments(text);
-
- if (parsedDocs.some((doc) => doc.toJS() === null)) {
- throw new MalformedResourceFileError("Encountered NULL resource", filePath);
- }
-
- const errors = parsedDocs.flatMap((doc) => doc.errors);
-
- if (errors.length > 0) {
- throw new MalformedResourceFileError(
- "Error parsing YAML resource",
- filePath,
- errors.map((e) => e.message).join(", "),
- );
- }
-
- const collection: AnyPlaybillComponent[] = [];
-
- for (const doc of parsedDocs) {
- const raw = doc.toJS();
- const parsedComponent = parsePlaybillComponent(raw);
- if (parsedComponent !== null) {
- collection.push(parsedComponent);
- }
- }
-
- return collection;
-}
-
-function parseMarkdownResourceFile(
- filePath: string,
- text: string,
-): AnyPlaybillComponent[] {
- try {
- const defaultName = path.basename(filePath, ".md");
- const defaultId = toSlug(defaultName);
- const frontmatter = extractFrontmatter(text);
- const markdown = extractMarkdown(text);
-
- const together = parsePlaybillComponent({
- // Use the file name, allow it to be overridden by frontmatter
- name: defaultName,
- id: defaultId,
- ...frontmatter,
- description: markdown,
- });
-
- // Null means hidden
- if (together === null) {
- return [];
- }
-
- return [together];
- } catch (e) {
- if (e instanceof YAMLParseError) {
- throw new MalformedResourceFileError(
- "Error parsing Markdown frontmatter",
- filePath,
- e.message,
- );
- }
-
- if (e instanceof ZodError) {
- throw new MalformedResourceFileError(
- "Error parsing resource file",
- filePath,
- e.message,
- );
- }
-
- throw e;
- }
-}
-
-export class ComponentFile {
- private _filePath: string;
- private _raw = "";
- private _format: FileFormat;
- private _components: AnyPlaybillComponent[] = [];
- private _basename: string;
-
- constructor(filePath: string) {
- this._filePath = path.resolve(filePath);
-
- const extension = path.extname(filePath).slice(1).toLowerCase();
- this._basename = path.basename(filePath, `.${extension}`);
-
- switch (extension) {
- case "yaml":
- this._format = "yaml";
- break;
- case "md":
- this._format = "markdown";
- break;
- default:
- throw new Error(`Unsupported file format: ${extension}`);
- }
- }
-
- async load() {
- this._raw = await loadFileOrFail(this._filePath);
- switch (this._format) {
- case "yaml":
- this._components = parseYAMLResourceFile(this._filePath, this._raw);
- break;
- case "markdown":
- this._components = parseMarkdownResourceFile(this._filePath, this._raw);
- }
- }
-
- /**
- * An array of well-formed components defined in this file
- */
- get components() {
- return this._components;
- }
-
- /**
- * The aboslute file path of this file
- */
- get path() {
- return this._filePath;
- }
-
- get baseName() {
- return this._basename;
- }
-}
diff --git a/src/lib/errors.ts b/src/lib/errors.ts
deleted file mode 100644
index b92c44c..0000000
--- a/src/lib/errors.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-export class MuseError extends Error {
- fmt(): string {
- return this.message;
- }
-}
-
-export class FileError extends MuseError {
- filePath: string;
- details?: string;
-
- constructor(message: string, filePath: string, details?: string) {
- super(message);
- this.filePath = filePath;
- this.details = details;
- }
-
- fmt(): string {
- return `-- ${this.message} --\nFile Path: ${this.filePath}\nDetails: ${this.details}`;
- }
-}
-
-export class MalformedResourceFileError extends FileError {}
-
-export class FileNotFoundError extends FileError {
- constructor(filePath: string) {
- super("File not found", filePath);
- this.message = "File not found";
- this.filePath = filePath;
- }
-}
-
-export class CLIError extends MuseError {}
diff --git a/src/lib/index.ts b/src/lib/index.ts
deleted file mode 100644
index e5a4496..0000000
--- a/src/lib/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from "./errors";
-export * from "./pfm";
-export * from "./component-file";
-export * from "./binding";
diff --git a/src/lib/pfm.ts b/src/lib/pfm.ts
deleted file mode 100644
index 9584462..0000000
--- a/src/lib/pfm.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import rehypeStringify from "rehype-stringify";
-import remarkParse from "remark-parse";
-import remarkRehype from "remark-rehype";
-import { unified } from "unified";
-import type { BoundPlaybill } from "./binding";
-import remarkWikiLink from "remark-wiki-link";
-import { toSlug } from "./slug";
-import rehypeRaw from "rehype-raw";
-import remarkGfm from "remark-gfm";
-import rehypeShiftHeading from "rehype-shift-heading";
-import {
- parseProsceniumScript,
- type AnyPlaybillComponent,
-} from "@proscenium/playbill";
-
-export type MarkdownParserFunction = (input: string) => Promise;
-
-export function createMarkdownRenderer(
- binding: BoundPlaybill,
-): MarkdownParserFunction {
- // TODO: Allow specifying links to other files via file paths
- // In this scenario, assume its a markdown-defined file and find the ID, use that for the link
- // to allow for more natural Obsidian-like linking
- // For now, this will mostly work, but if you need to specify a unique ID, it wont work in Obsidian
- // despite being valid for Muse
-
- const parser = unified()
- .use(remarkParse)
- .use(remarkGfm)
- .use(remarkWikiLink, {
- aliasDivider: "|",
- permalinks: binding.playbill.allComponentIds,
- pageResolver: (permalink: string) => {
- return [toSlug(permalink)];
- },
- hrefTemplate: (permalink: string) => `#component/${permalink}`,
- })
- .use(remarkRehype, { allowDangerousHtml: true })
- .use(rehypeRaw)
- .use(rehypeShiftHeading, { shift: 1 })
- .use(rehypeStringify);
-
- // TODO: Add sanitization
-
- return async (input: string): Promise => {
- const parsed = await parser.process(input);
- return String(parsed);
- };
-}
-
-export async function compileMarkdownInPlaybill(
- boundPlaybill: BoundPlaybill,
-): Promise {
- const renderMarkdown = createMarkdownRenderer(boundPlaybill);
- const playbill = boundPlaybill.playbill;
-
- // Define a processor function to iterate over all components and process their markdown
- const processMarkdownInComponent = async (entry: AnyPlaybillComponent) => {
- const preparsed = parseProsceniumScript(entry.description, playbill);
- entry.description = await renderMarkdown(preparsed);
-
- if (entry._component === "resource" && entry.type === "table") {
- const newData: string[][] = [];
- for (const row of entry.data) {
- const newRow: string[] = [];
- for (const cell of row) {
- newRow.push(await renderMarkdown(cell));
- }
- newData.push(newRow);
- }
- entry.data = newData;
- }
- };
-
- // Process all components
- await playbill.processComponents(processMarkdownInComponent);
-
- return boundPlaybill;
-}
diff --git a/src/lib/slug.ts b/src/lib/slug.ts
deleted file mode 100644
index b6a0de4..0000000
--- a/src/lib/slug.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import slugify from "slugify";
-
-export function toSlug(str: string): string {
- return slugify(str, { lower: true });
-}
diff --git a/src/preload/http-plugin.js b/src/preload/http-plugin.js
new file mode 100644
index 0000000..bb7f732
--- /dev/null
+++ b/src/preload/http-plugin.js
@@ -0,0 +1,40 @@
+const rx_any = /./;
+const rx_http = /^https?:\/\//;
+const rx_relative_path = /^\.\.?\//;
+const rx_absolute_path = /^\//;
+
+async function load_http_module(href) {
+ return fetch(href).then((response) =>
+ response
+ .text()
+ .then((text) =>
+ response.ok
+ ? { contents: text, loader: "js" }
+ : Promise.reject(
+ new Error(`Failed to load module '${href}'': ${text}`),
+ ),
+ ),
+ );
+}
+
+Bun.plugin({
+ name: "http_imports",
+ setup(build) {
+ build.onResolve({ filter: rx_relative_path }, (args) => {
+ if (rx_http.test(args.importer)) {
+ return { path: new URL(args.path, args.importer).href };
+ }
+ });
+ build.onResolve({ filter: rx_absolute_path }, (args) => {
+ if (rx_http.test(args.importer)) {
+ return { path: new URL(args.path, args.importer).href };
+ }
+ });
+ build.onLoad({ filter: rx_any, namespace: "http" }, (args) =>
+ load_http_module(`http:${args.path}`),
+ );
+ build.onLoad({ filter: rx_any, namespace: "https" }, (args) =>
+ load_http_module(`https:${args.path}`),
+ );
+ },
+});
diff --git a/src/render/html/component/ability.tsx b/src/render/html/component/ability.tsx
deleted file mode 100644
index 09537da..0000000
--- a/src/render/html/component/ability.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import type { Ability } from "@proscenium/playbill";
-import { HTML } from "./base/html";
-
-interface AbilityCardProps {
- ability: Ability;
-}
-
-export function AbilityCard({ ability }: AbilityCardProps) {
- const costs: React.ReactNode[] = [];
-
- if (ability.ap > 0) {
- costs.push(
-
- {ability.ap} AP
- ,
- );
- }
-
- if (ability.hp > 0) {
- costs.push(
-
- {ability.hp} HP
- ,
- );
- }
-
- if (ability.ep > 0) {
- costs.push(
-
- {ability.ep} EP
- ,
- );
- }
-
- const damage =
- ability.damage !== null ? (
-
- {ability.damage.amount} {ability.damage.type === "phy" ? "Phy" : "Arc"}
-
- ) : null;
-
- const roll =
- ability.roll !== "none" ? (
- {ability.roll}
- ) : null;
-
- const type = (
- {ability.type}
- );
-
- const classList = `ability ability-${ability.type}`;
-
- return (
-
-
-
{ability.name}
-
- {type}
- {roll}
- {damage}
- {costs}
-
-
-
-
- );
-}
diff --git a/src/render/html/component/base/html.tsx b/src/render/html/component/base/html.tsx
deleted file mode 100644
index 761e2f1..0000000
--- a/src/render/html/component/base/html.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-interface HTMLWrapperProps {
- html: string;
- className?: string;
-}
-
-export function HTML({ html, className }: HTMLWrapperProps) {
- return (
-
- );
-}
diff --git a/src/render/html/component/base/section.tsx b/src/render/html/component/base/section.tsx
deleted file mode 100644
index 2c85f28..0000000
--- a/src/render/html/component/base/section.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import classNames from "classnames";
-import type React from "react";
-
-interface SectionProps {
- type: string;
- title: string;
- children: React.ReactNode;
- preInfo?: React.ReactNode;
- info?: React.ReactNode;
- componentId?: string;
- leftCornerTag?: React.ReactNode;
- rightCornerTag?: React.ReactNode;
-}
-
-export function Section({
- type,
- title,
- children,
- preInfo,
- info,
- componentId,
- leftCornerTag,
- rightCornerTag,
-}: SectionProps) {
- const sectionClasses = classNames("section", type);
- const headerClasses = classNames("section-header", `${type}-header`);
- const contentClasses = classNames("section-content", `${type}-content`);
-
- return (
-
-
- {preInfo}
-
{title}
- {info}
-
- {leftCornerTag}
- {rightCornerTag}
- {children}
-
- );
-}
diff --git a/src/render/html/component/glossary.tsx b/src/render/html/component/glossary.tsx
deleted file mode 100644
index a139dc4..0000000
--- a/src/render/html/component/glossary.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Section } from "./base/section";
-
-interface GlossaryTableProps {
- terms: [string, string[]][];
-}
-
-export function GlossaryTable({ terms }: GlossaryTableProps) {
- return (
-
-
-
- {terms.map(([term, defs]) => {
- return (
-
-
- {term}
- {defs.map((d, i) => (
- - {d}
- ))}
-
- );
- })}
-
-
-
- );
-}
diff --git a/src/render/html/component/header.tsx b/src/render/html/component/header.tsx
deleted file mode 100644
index 4c0535e..0000000
--- a/src/render/html/component/header.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { Playbill } from "@proscenium/playbill";
-
-interface PlaybillHeaderProps {
- playbill: Playbill;
-}
-
-export function PageHeader({ playbill }: PlaybillHeaderProps) {
- return (
-
- );
-}
diff --git a/src/render/html/component/item.tsx b/src/render/html/component/item.tsx
deleted file mode 100644
index ebe63b7..0000000
--- a/src/render/html/component/item.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { Item } from "@proscenium/playbill";
-import { capitalize } from "lodash-es";
-import { Section } from "./base/section";
-import { HTML } from "./base/html";
-
-interface ItemSectionProps {
- item: Item;
-}
-
-export function ItemCard({ item }: ItemSectionProps) {
- let itemTypeDescriptor = "";
-
- switch (item.type) {
- case "wielded":
- itemTypeDescriptor = `Wielded (${item.hands} ${item.hands === 1 ? "Hand" : "Hands"})`;
- break;
- case "worn":
- itemTypeDescriptor = `Worn (${item.slot})`;
- break;
- case "trinket":
- itemTypeDescriptor = "Trinket";
- break;
- }
-
- const infoParts: React.ReactNode[] = [];
-
- if (item.affinity !== "none") {
- infoParts.push(
-
- {item.affinity} Affinity
-
,
- );
- }
-
- if (item.damage !== null && item.damage.amount > 0) {
- infoParts.push(
-
- {item.damage.amount} {capitalize(item.damage.type)} Damage
-
,
- );
- }
-
- return (
- {infoParts}}
- rightCornerTag={capitalize(item.rarity)}
- leftCornerTag={itemTypeDescriptor}
- >
-
-
- );
-}
diff --git a/src/render/html/component/method.tsx b/src/render/html/component/method.tsx
deleted file mode 100644
index e39106b..0000000
--- a/src/render/html/component/method.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { Method, Playbill } from "@proscenium/playbill";
-import { AbilityCard } from "./ability";
-import { Section } from "./base/section";
-
-interface MethodSectionProps {
- method: Method;
- playbill: Playbill;
-}
-
-export function MethodSection({ method, playbill }: MethodSectionProps) {
- const ranks = method.abilities.map((rank, i) => {
- const gridTemplateColumns = new Array(Math.min(rank.length, 3))
- .fill("1fr")
- .join(" ");
- return (
-
-
Rank {i + 1}
-
- {rank.map((abilityId) => {
- const ability = playbill.getAbility(abilityId);
-
- if (ability === null) {
- throw new Error(`Ability not found: ${abilityId}`);
- }
-
- return
;
- })}
-
-
- );
- });
-
- return (
- {method.curator}'s Method of}
- info={
-
- }
- >
- {ranks}
-
- );
-}
diff --git a/src/render/html/component/resource.tsx b/src/render/html/component/resource.tsx
deleted file mode 100644
index cd07910..0000000
--- a/src/render/html/component/resource.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import type {
- ImageResource,
- Resource,
- TableResource,
- TextResource,
-} from "@proscenium/playbill";
-import { Section } from "./base/section";
-import { HTML } from "./base/html";
-
-interface TextResourceSectionProps {
- resource: TextResource;
-}
-
-function TextResourceSection({ resource }: TextResourceSectionProps) {
- return (
-
- {resource.categories.map((category) => (
- {category}
- ))}
-
- }
- >
-
-
- );
-}
-
-interface ImageResourceSectionProps {
- resource: ImageResource;
-}
-
-function ImageResourceSection({ resource }: ImageResourceSectionProps) {
- return (
-
- {resource.categories.map((category) => (
- {category}
- ))}
-
- }
- >
-
-
-
-
-
- );
-}
-
-interface TableResourceSectionProps {
- resource: TableResource;
-}
-
-function TableResourceSection({ resource }: TableResourceSectionProps) {
- const [headers, ...rows] = resource.data;
- return (
-
- {resource.categories.map((category) => (
- {category}
- ))}
-
- }
- >
-
-
-
- {headers.map((header) => (
- |
- ))}
-
-
-
- {rows.map((row, i) => (
-
- {row.map((cell, j) => (
- |
- ))}
-
- ))}
-
-
-
- );
-}
-
-interface ResourceSectionProps {
- resource: Resource;
-}
-
-export function ResourceSection({ resource }: ResourceSectionProps) {
- switch (resource.type) {
- case "text":
- return ;
- case "image":
- return ;
- case "table":
- return ;
- }
-}
diff --git a/src/render/html/component/rule.tsx b/src/render/html/component/rule.tsx
deleted file mode 100644
index 7971be1..0000000
--- a/src/render/html/component/rule.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Rule } from "@proscenium/playbill";
-import { Section } from "./base/section";
-import { HTML } from "./base/html";
-
-interface RuleSectionProps {
- rule: Rule;
-}
-
-export function RuleSection({ rule }: RuleSectionProps) {
- return (
-
-
- );
-}
diff --git a/src/render/html/component/species.tsx b/src/render/html/component/species.tsx
deleted file mode 100644
index 6c11831..0000000
--- a/src/render/html/component/species.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import type { Playbill, Species } from "@proscenium/playbill";
-import { Section } from "./base/section";
-import { AbilityCard } from "./ability";
-import { HTML } from "./base/html";
-
-interface SpeciesSectionProps {
- species: Species;
- playbill: Playbill;
-}
-
-export function SpeciesSection({ species, playbill }: SpeciesSectionProps) {
- const hpString = species.hp >= 0 ? `+${species.hp}` : `-${species.hp}`;
- const apString = species.ap >= 0 ? `+${species.ap}` : `-${species.ap}`;
- const epString = species.ep >= 0 ? `+${species.ep}` : `-${species.ep}`;
-
- const hasAbilities = species.abilities.length > 0;
-
- const abilitySection = hasAbilities ? (
-
-
Innate Abilities
- {species.abilities.map((abilityId) => {
- const ability = playbill.getAbility(abilityId);
-
- if (ability === null) {
- throw new Error(`Ability not found: ${abilityId}`);
- }
-
- return
;
- })}
-
- ) : (
- ""
- );
-
- const sectionContent = (
- <>
-
-
-
-
-
Muscle
-
- {species.muscle}
-
-
-
-
-
Focus
-
- {species.focus}
-
-
-
-
-
- Knowledge
-
-
- {species.knowledge}
-
-
-
-
-
Charm
-
- {species.charm}
-
-
-
-
-
Cunning
-
- {species.cunning}
-
-
-
-
-
Spark
-
- {species.spark}
-
-
-
-