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 ( -
-

{playbill.name}

-

A Playbill by {playbill.author}

-

- {" "} - Version {playbill.version} -

-
- ); -} 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}
  • - ))} - - } - > -
    - {resource.name} -
    -
    -
    - ); -} - -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 = ( - <> -
    -
    -

    - HP -

    -

    {hpString}

    -
    - -
    -

    - AP -

    -

    {apString}

    -
    - -
    -

    - EP -

    -

    {epString}

    -
    -
    - -
    -
    -

    Muscle

    -

    - {species.muscle} -

    -
    - -
    -

    Focus

    -

    - {species.focus} -

    -
    - -
    -

    - Knowledge -

    -

    - {species.knowledge} -

    -
    - -
    -

    Charm

    -

    - {species.charm} -

    -
    - -
    -

    Cunning

    -

    - {species.cunning} -

    -
    - -
    -

    Spark

    -

    - {species.spark} -

    -
    -
    - - {abilitySection} - - ); - - return ( -
    Species

    } - > - {sectionContent} -
    - ); -} diff --git a/src/render/html/default.css b/src/render/html/default.css deleted file mode 100644 index f7c5ab5..0000000 --- a/src/render/html/default.css +++ /dev/null @@ -1,505 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap"); - -:root { - /* Spacing and sizing */ - --max-width: 800px; - --padding: 0.5rem; - --margin: 1rem; - - /* Typography */ - --line-height: 1.5; - --font-size: 18px; - --font-body: "Open Sans", sans-serif; - --font-header: "Urbanist", sans-serif; - - /* Page Colors */ - --color-background: hsl(34deg 18 15); - --color-background--dark: hsl(34deg 18 10); - --color-background--light: hsl(34deg 18 20); - --color-text: hsl(35deg 37 73); - --color-text--dim: hsl(35deg 17 50); - --color-heading: hsl(32deg 48 85); - --color-emphasis: hsl(56deg 59 56); - --color-strong: hsl(4deg 88 61); - - /* Concept Colors */ - --color-muscle: var(--color-heading); - --color-focus: var(--color-heading); - --color-knowledge: var(--color-heading); - --color-charm: var(--color-heading); - --color-cunning: var(--color-heading); - --color-spark: var(--color-heading); - - --color-novice: gray; - --color-adept: pink; - --color-master: papayawhip; - --color-theatrical: red; - - --color-phy: red; - --color-arc: hsl(56deg 59 56); - - --color-ap: hsl(215deg 91 75); - --color-hp: hsl(15deg 91 75); - --color-ep: hsl(115deg 40 75); - --color-xp: hsl(0deg 0 45); -} - -/* Normalize box sizing */ -*, -*::before, -*::after { - box-sizing: border-box; -} - -/* Strip default margins */ -* { - margin: 0; -} - -/* Global style classes */ -.phy { - color: var(--color-phy); -} - -.arc { - color: var(--color-arc); -} - -.ap { - color: var(--color-ap); -} - -.hp { - color: var(--color-hp); -} - -.ep { - color: var(--color-ep); -} - -.muscle { - color: var(--color-muscle); -} - -.focus { - color: var(--color-focus); -} - -.knowledge { - color: var(--color-knowledge); -} - -.charm { - color: var(--color-charm); -} - -.cunning { - color: var(--color-cunning); -} - -.spark { - color: var(--color-spark); -} - -.novice { - color: var(--color-novice); -} - -.adept { - color: var(--color-adept); -} - -.master { - color: var(--color-master); -} - -.theatrical { - color: var(--color-theatrical); -} - -/* Sections */ - -section { - position: relative; - max-width: var(--max-width); - margin: 0 auto var(--margin); - border: 2px solid var(--color-background--dark); -} - -.section-header { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - min-height: 8rem; - - text-align: center; - background-color: var(--color-background--dark); -} - -.section-header p { - margin: 0; -} - -.section-content { - padding: var(--padding); -} - -.section-content :last-child { - margin-bottom: 0; -} - -.section-tag { - position: absolute; - top: var(--padding); - color: var(--color-text--dim); - font-weight: bold; - max-width: 45%; -} - -.section-tag--left { - left: var(--padding); -} - -.section-tag--right { - right: var(--padding); -} - -body { - background-color: var(--color-background); - color: var(--color-text); - font-family: var(--font-body); - font-size: var(--font-size); -} - -p { - margin-bottom: var(--margin); - line-height: var(--line-height); -} - -em { - color: var(--color-emphasis); -} - -strong { - color: var(--color-strong); -} - -/* Basic Typography */ -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: var(--font-header); - color: var(--color-heading); -} - -h1 { - font-size: 4rem; - text-align: center; -} - -h2 { - font-size: 2rem; -} - -h3 { - font-size: 1.75rem; -} - -h4 { - font-size: 1.5rem; -} - -h5 { - font-size: 1.25rem; -} - -h6 { - font-weight: bold; - font-size: 1rem; -} - -table { - width: 100%; - margin-bottom: var(--margin); -} - -thead { - background-color: var(--color-background--light); -} - -td { - padding: var(--padding); - text-align: left; - vertical-align: top; -} - -tr:nth-child(even) { - background-color: var(--color-background--light); -} - -tr:hover { - background-color: var(--color-background--dark); -} - -article blockquote { - font-style: italic; - border-left: 3px solid var(--color-emphasis); - background-color: var(--color-background--light); - margin-bottom: var(--margin); - padding: 0.5rem 0.5rem 0.5rem 2rem; -} - -article blockquote p:last-child { - margin-bottom: 0; -} - -article img { - display: block; - margin: 0 auto; - max-width: 70%; - max-height: 35vh; -} - -@media screen and (min-width: 480px) { - article img { - float: right; - margin-left: var(--margin); - margin-right: 0; - max-width: 45%; - } - - article img.left { - float: left; - float: left; - margin-right: var(--margin); - } -} - -/* Main Header */ -header { - min-height: 20vh; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.header-byline { - font-size: 1.2rem; -} - -/* Overview */ -.overview { - padding: 1rem; - background-color: var(--color-background--dark); - text-align: center; -} - -.overview p { - margin: 0 auto; -} - -/* Nav */ - -nav { - max-width: var(--max-width); - padding: var(--padding); - margin: 0 auto; - display: flex; - flex-direction: row; - justify-content: space-between; - flex-wrap: wrap; - gap: var(--padding); -} - -nav button { - padding: var(--padding); - font-size: inherit; - color: inherit; - background-color: var(--color-background--light); - border: 1px solid var(--color-background--dark); - cursor: pointer; - flex: 1; -} - -nav button:hover { - background-color: var(--color-background--dark); -} - -/* Ability */ - -.ability { - padding: var(--padding); - background-color: var(--color-background--light); -} - -.ability-details { - display: flex; - flex-direction: row; - gap: var(--padding); - flex-wrap: wrap; - text-transform: capitalize; - font-weight: 300; - font-style: italic; -} - -.ability-header { - position: relative; - margin-bottom: var(--margin); -} - -/* Method */ -.method-rank { - margin-bottom: var(--margin); - overflow-x: auto; -} - -.method-curator { - font-style: italic; -} - -.method-description { - margin-top: var(--margin); -} - -.method-rank-content { - display: grid; - gap: var(--padding); -} - -.method-rank-content .ability { - width: 100%; - height: 100%; -} - -/* Species */ - -.species { - margin-bottom: var(--margin); -} - -.species-stats { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - text-align: center; - gap: var(--padding); -} - -.species-talents .adept { - font-weight: 600; -} - -.species-talents .master { - font-weight: 700; -} - -.species-stats h4, -.species-talents h4 { - font-size: var(--font-size); -} - -.species-talents { - text-transform: capitalize; -} - -.species-talents { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - gap: var(--padding); - text-align: center; - margin-bottom: var(--margin); -} - -.species-abilities .ability-cost-xp { - display: none; -} - -/* Items */ -.item { - position: relative; - background-color: var(--color-background--light); -} - -.item-info { - display: flex; - flex-direction: row; - gap: var(--padding); -} - -.item-affinity { - text-transform: capitalize; -} - -.item-rarity { - position: absolute; - top: var(--padding); - right: var(--padding); - text-transform: capitalize; -} - -/* Glossary */ - -.glossary-content > dl > div { - margin-bottom: var(--margin); -} - -dt { - color: var(--color-strong); - font-weight: bold; -} - -dd { - margin-left: 2ch; -} - -dd + dd { - margin-top: var(--padding); -} - -dd + dt { - margin-top: var(--margin); -} - -/* Resources */ -.resource-categories { - list-style: none; - padding: 0; - display: flex; - flex-direction: row; - text-transform: capitalize; - gap: var(--padding); - color: var(--color-text--dim); -} - -.resource-categories li { - font-style: italic; -} - -.resource-categories li:before { - content: "#"; - opacity: 0.5; -} - -.resource-image { - display: block; -} - -.resource-image img { - float: none; - margin: 0 auto; - - max-width: 90%; - max-height: 100vh; - - margin-bottom: var(--padding); -} - -.resource-image figcaption { - font-style: italic; - text-align: center; -} diff --git a/src/render/html/index.tsx b/src/render/html/index.tsx deleted file mode 100644 index bb26ddd..0000000 --- a/src/render/html/index.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { Playbill } from "@proscenium/playbill"; -import { compileMarkdownInPlaybill, type BoundPlaybill } from "#lib"; -import { GlossaryTable } from "./component/glossary"; -import { PageHeader } from "./component/header"; -import { ItemCard } from "./component/item"; -import { MethodSection } from "./component/method"; -import { ResourceSection } from "./component/resource"; -import { RuleSection } from "./component/rule"; -import { SpeciesSection } from "./component/species"; -import { renderToString } from "react-dom/server"; -import defaultCSS from "./default.css" with { type: "text" }; - -export async function renderPlaybillToHTML( - boundPlaybill: BoundPlaybill, -): Promise { - // Make a copy of the playbill to avoid modifying the original - // and compile all description markdown - const playbill = Playbill.fromSerialized(boundPlaybill.playbill.serialize()); - await compileMarkdownInPlaybill(boundPlaybill); - - // Prepare stylesheet - const cssParts: string[] = []; - - if (!boundPlaybill.omitDefaultStyles) { - cssParts.push(``); - } - - if (boundPlaybill.styles.length > 0) { - cssParts.push(``); - } - - const css = cssParts.join("\n"); - - const body = renderToString( - <> -
    - {playbill.allSpecies.map((species) => ( - - ))} -
    - -
    - {playbill.allMethods.map((method) => ( - - ))} -
    - -
    - {playbill.allItems.map((item) => ( - - ))} -
    - -
    - {playbill.allRules.map((rule) => ( - - ))} -
    - -
    - {} -
    - -
    - {playbill.allResources.map((resource) => ( - - ))} -
    - , - ); - - // Main document - const doc = ` - - - - - - Playbill: ${playbill.name} - ${css} - - - - - ${renderToString()} -

    ${playbill.description}

    - - ${body} - - - `; - - return doc; -} diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..0c84a67 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,27 @@ +import { normalize } from "node:path"; + +/** + * Given a file path, ensure it is a file that exists + * Otherwise, return null + * + * @param filePath - The path to the file + * @returns A promise which resolves to the resolved file path or null + */ +export async function resolveFilePath( + filePath: string, +): Promise { + const normalized = normalize(filePath); + const file = Bun.file(normalized); + const exists = await file.exists(); + + if (!exists) { + return null; + } + + const stat = await file.stat(); + if (stat.isDirectory()) { + return null; + } + + return normalized; +} diff --git a/src/util/args.ts b/src/util/args.ts deleted file mode 100644 index 25d2d0f..0000000 --- a/src/util/args.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { parseArgs } from "node:util"; -import { z } from "zod"; -import { CLIError, MuseError } from "#lib"; -import chalk from "chalk"; -import { version } from "../../package.json" with { type: "json" }; - -export const USAGE = ` -${chalk.bold("muse")} - Compile and validate Playbills for Proscenium -${chalk.dim(`v${version}`)} - -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 - --markdown Compile markdown in descriptions when rendering to JSON - --help, -h Show this help message -`.trim(); - -const Renderers = ["json", "html"] as const; -const RendererSchema = z.enum(Renderers); -type Renderer = z.infer; - -/** - * A shape representing the arguments passed to the CLI - */ -export interface CLIArguments { - inputFilePath: string; - - options: { - check: boolean; - outfile: string; - help: boolean; - renderer: Renderer; - watch: boolean; - markdown: boolean; - }; -} - -/** - * 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 - */ -export function parseCLIArguments(argv: string[]): CLIArguments { - const { values: options, positionals: args } = parseArgs({ - args: argv, - options: { - check: { - short: "c", - type: "boolean", - default: false, - }, - outfile: { - short: "o", - type: "string", - default: "", - }, - help: { - short: "h", - default: false, - type: "boolean", - }, - renderer: { - short: "r", - default: "json", - type: "string", - }, - watch: { - default: false, - type: "boolean", - }, - markdown: { - default: false, - type: "boolean", - }, - }, - strict: true, - allowPositionals: true, - }); - - // -- ARG VALIDATION -- // - if (options.check && options.outfile !== "") { - throw new CLIError("Cannot use --check and --outfile together"); - } - - const parsedRenderer = RendererSchema.safeParse(options.renderer); - - if (!parsedRenderer.success) { - throw new MuseError(`Invalid renderer: ${parsedRenderer.data}`); - } - - return { - inputFilePath: args[0] ?? "./binding.yaml", - - options: { - check: options.check, - outfile: options.outfile, - help: options.help, - renderer: parsedRenderer.data, - watch: options.watch, - markdown: options.markdown, - }, - }; -} diff --git a/src/util/files.ts b/src/util/files.ts deleted file mode 100644 index cd6c865..0000000 --- a/src/util/files.ts +++ /dev/null @@ -1,65 +0,0 @@ -import path from "node:path"; -import YAML from "yaml"; -import { FileError, FileNotFoundError } from "../lib/errors"; - -/** - * Load a file from the filesystem or throw an error if it does not exist. - * @param filePath The path to the file to load - * @returns The contents of the file as a string - * - * @throws {FileNotFoundError} if the file does not exist - */ -export async function loadFileOrFail(filePath: string): Promise { - const fileToLoad = Bun.file(filePath); - const fileExists = await fileToLoad.exists(); - - if (!fileExists) { - throw new FileNotFoundError(`File not found: ${filePath}`); - } - - return await fileToLoad.text(); -} - -/** - * Load and parse a YAML file or throw an error if the file doesnt exist - * or the yaml fails to parse - * @param filePath The path to the file to load - * @returns The parsed contents of the file - * - * @throws {FileNotFoundError} if the file does not exist - * @throws {FileError} if the file is not valid YAML - */ -export async function loadYAMLFileOrFail(filePath: string): Promise { - try { - return YAML.parse(await loadFileOrFail(filePath)); - } catch (error) { - throw new FileError("Failed to parse YAML file", filePath, `${error}`); - } -} - -/** - * Load and parse a JSON file or throw an error if the file doesnt exist - * or the JSON fails to parse - * @param filePath The path to the file to load - * @returns The parsed contents of the file - * - * @throws {FileNotFoundError} if the file does not exist - * @throws {FileError} if the file is not valid JSON - */ -export async function loadJSONFileOrFail(filePath: string): Promise { - try { - return JSON.parse(await loadFileOrFail(filePath)); - } catch (error) { - throw new FileError("Failed to parse JSON file", filePath, `${error}`); - } -} - -/** - * Returns the absolute path of the directory containing the given file - * - * @param filePath The path to the file - * @returns The absolute path of the directory containing the file - */ -export function getAbsoluteDirname(filePath: string): string { - return path.resolve(path.dirname(filePath)); -} diff --git a/src/util/index.ts b/src/util/index.ts deleted file mode 100644 index 1d10193..0000000 --- a/src/util/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./args"; -export * from "./files"; diff --git a/tsconfig.json b/tsconfig.json index cc9ee32..a964c9c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,8 +13,10 @@ // Bundler mode "moduleResolution": "bundler", "allowArbitraryExtensions": true, + "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, - "noEmit": true, + "declaration": true, + "emitDeclarationOnly": true, // Best practices "strict": true, "skipLibCheck": true, @@ -25,20 +27,21 @@ "noPropertyAccessFromIndexSignature": false, "baseUrl": "./src", "paths": { - "#lib": [ - "./lib/index.ts" - ], - "#render/*": [ - "./render/*" + "#core/*": [ + "./core/*.ts" ], "#util": [ - "./util/index.ts" + "./util.ts" ], - } + "#errors": [ + "./errors.ts" + ] + }, + "outDir": "./dist", }, "include": [ "src/**/*.ts", "src/**/*.tsx", - "src/**/*.css", + "src/preload/http-plugin.js", ] }