Compare commits
14 commits
Author | SHA1 | Date | |
---|---|---|---|
2d3ba356b5 | |||
dfc65dacfa | |||
ffaeb4841e | |||
5b90a8e1b3 | |||
ae1b9e262e | |||
504c1e6f3c | |||
16660823ea | |||
3682a7a763 | |||
b5136c5d02 | |||
afbb0cbaa5 | |||
f834accb8d | |||
435d555394 | |||
6a3157762a | |||
c1166680a8 |
46 changed files with 970 additions and 2778 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -174,5 +174,3 @@ dist
|
|||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
|
||||
demo-data
|
||||
demo.html
|
||||
|
|
256
README.md
256
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>
|
||||
|
||||
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: <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: <damage type> # Type of damage dealt by this ability (default phy)
|
||||
roll: <roll type> # 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<Binding> {}
|
||||
```
|
||||
|
||||
### 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: <prowess> # Starting muscle prowess (default novice)
|
||||
focus: <prowess> # Starting focus prowess (default novice)
|
||||
knowledge: <prowess> # Starting knowledge prowess (default novice)
|
||||
charm: <prowess> # Starting charm prowess (default novice)
|
||||
cunning: <prowess> # Starting cunning prowess (default novice)
|
||||
spark: <prowess> # Starting spark prowess (default novice)
|
||||
```
|
||||
|
||||
### Item Definition
|
||||
|
||||
```yaml
|
||||
$define: item
|
||||
type: <item type> # Defaults to wielded
|
||||
rarity: <rarity> # Defaults to common
|
||||
affinity: <roll type> # Defaults to none
|
||||
damage: 0 # Defaults to 0
|
||||
damageType: <damage type> # Defaults to phy
|
||||
hands: 1 | 2 # Only for wielded items
|
||||
slot: <item 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: <resource type> # Defaults to text
|
||||
url: https://url.com/image.jpeg # Only for image resources
|
||||
data: <a 2-d array of strings> # 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: <prowess> # Defaults to novice
|
||||
focus: <prowess> # Defaults to novice
|
||||
knowledge: <prowess> # Defaults to novice
|
||||
charm: <prowess> # Defaults to novice
|
||||
cunning: <prowess> # Defaults to novice
|
||||
spark: <prowess> # 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<Binding> {
|
||||
const newBinding = { ...binding };
|
||||
newBinding.options.someOption = "newValue";
|
||||
return {
|
||||
...binding,
|
||||
meta: {
|
||||
...binding.meta,
|
||||
customValue: true,
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
|
@ -7,10 +7,7 @@
|
|||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": [
|
||||
"*.d.ts",
|
||||
"*.json"
|
||||
],
|
||||
"ignore": ["*.d.ts", "*.json"],
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./src/**/*.tsx",
|
||||
|
|
296
bun.lock
296
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=="],
|
||||
}
|
||||
}
|
||||
|
|
1
bunfig.toml
Normal file
1
bunfig.toml
Normal file
|
@ -0,0 +1 @@
|
|||
preload = ["./src/preload/http-plugin.js"]
|
14
demo/main/binding.md
Normal file
14
demo/main/binding.md
Normal file
|
@ -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!
|
19
demo/main/processors/announce-slam-dunks.js
Normal file
19
demo/main/processors/announce-slam-dunks.js
Normal file
|
@ -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,
|
||||
};
|
||||
};
|
17
demo/main/processors/scream.js
Normal file
17
demo/main/processors/scream.js
Normal file
|
@ -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
|
||||
};
|
||||
}
|
2
demo/main/stats/statistics.toml
Normal file
2
demo/main/stats/statistics.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
player = "Flynn"
|
||||
dunkaroos = 100
|
5
demo/main/stories/a-neat-story.md
Normal file
5
demo/main/stories/a-neat-story.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
published: true
|
||||
---
|
||||
|
||||
This is a neat story I wrote!
|
47
package.json
47
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"
|
||||
}
|
||||
}
|
||||
|
|
152
src/cli/index.ts
Normal file
152
src/cli/index.ts
Normal file
|
@ -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>
|
||||
|
||||
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<number> {
|
||||
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);
|
||||
}
|
145
src/core/binding.ts
Normal file
145
src/core/binding.ts
Normal file
|
@ -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<MetaShape = UnknownRecord> {
|
||||
/** 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<string> {
|
||||
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<Binding> {
|
||||
// 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: {},
|
||||
};
|
||||
}
|
34
src/core/files.ts
Normal file
34
src/core/files.ts
Normal file
|
@ -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<LoadedFile> {
|
||||
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,
|
||||
};
|
||||
}
|
52
src/core/plugins.ts
Normal file
52
src/core/plugins.ts
Normal file
|
@ -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<Binding>;
|
||||
|
||||
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<MusePlugin> {
|
||||
// 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"),
|
||||
};
|
||||
}
|
107
src/core/records.ts
Normal file
107
src/core/records.ts
Normal file
|
@ -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<string, unknown>;
|
||||
|
||||
/**
|
||||
* 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}`);
|
||||
}
|
172
src/core/sources.ts
Normal file
172
src/core/sources.ts
Normal file
|
@ -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<typeof SourceSchema>;
|
||||
|
||||
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<MetaShape = UnknownRecord> {
|
||||
_raw: string;
|
||||
location: string;
|
||||
data: Record<string, unknown>;
|
||||
meta: MetaShape;
|
||||
source: MuseSource;
|
||||
}
|
||||
|
||||
export async function parseMuseFile(
|
||||
rawFilePath: string,
|
||||
{ contentKey = "content" }: SourceOptions,
|
||||
): Promise<MuseEntry[]> {
|
||||
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<MuseEntry[]> {
|
||||
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<MuseEntry[]> {
|
||||
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<MuseEntry[]> {
|
||||
switch (source.type) {
|
||||
case "file":
|
||||
return loadFromFileSource(source, options);
|
||||
case "url":
|
||||
return loadFromURLSource(source, options);
|
||||
default:
|
||||
throw new Error(`Unsupported source type: ${source.type}`);
|
||||
}
|
||||
}
|
2
src/css.d.ts
vendored
2
src/css.d.ts
vendored
|
@ -1,2 +0,0 @@
|
|||
declare module '*.css' {
|
||||
}
|
|
@ -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}`);
|
||||
}
|
||||
}
|
41
src/errors.ts
Normal file
41
src/errors.ts
Normal file
|
@ -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 {}
|
12
src/exports.ts
Normal file
12
src/exports.ts
Normal file
|
@ -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";
|
120
src/index.ts
120
src/index.ts
|
@ -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<number> {
|
||||
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);
|
||||
}
|
|
@ -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<string> {
|
||||
// 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<BoundPlaybill> {
|
||||
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<void>[] = [];
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {}
|
|
@ -1,4 +0,0 @@
|
|||
export * from "./errors";
|
||||
export * from "./pfm";
|
||||
export * from "./component-file";
|
||||
export * from "./binding";
|
|
@ -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<string>;
|
||||
|
||||
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<string> => {
|
||||
const parsed = await parser.process(input);
|
||||
return String(parsed);
|
||||
};
|
||||
}
|
||||
|
||||
export async function compileMarkdownInPlaybill(
|
||||
boundPlaybill: BoundPlaybill,
|
||||
): Promise<BoundPlaybill> {
|
||||
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;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import slugify from "slugify";
|
||||
|
||||
export function toSlug(str: string): string {
|
||||
return slugify(str, { lower: true });
|
||||
}
|
40
src/preload/http-plugin.js
Normal file
40
src/preload/http-plugin.js
Normal file
|
@ -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}`),
|
||||
);
|
||||
},
|
||||
});
|
|
@ -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(
|
||||
<span key="ap" className="ability-cost ap">
|
||||
{ability.ap} AP
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
|
||||
if (ability.hp > 0) {
|
||||
costs.push(
|
||||
<span key="hp" className="ability-cost hp">
|
||||
{ability.hp} HP
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
|
||||
if (ability.ep > 0) {
|
||||
costs.push(
|
||||
<span key="ep" className="ability-cost ep">
|
||||
{ability.ep} EP
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
|
||||
const damage =
|
||||
ability.damage !== null ? (
|
||||
<div className={`ability-damage ${ability.damage.type}`}>
|
||||
{ability.damage.amount} {ability.damage.type === "phy" ? "Phy" : "Arc"}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const roll =
|
||||
ability.roll !== "none" ? (
|
||||
<div className={`ability-roll ${ability.roll}`}>{ability.roll}</div>
|
||||
) : null;
|
||||
|
||||
const type = (
|
||||
<span className={`ability-type ${ability.type}`}>{ability.type}</span>
|
||||
);
|
||||
|
||||
const classList = `ability ability-${ability.type}`;
|
||||
|
||||
return (
|
||||
<div className={classList} data-component-id={ability.id}>
|
||||
<div className="ability-header">
|
||||
<h4>{ability.name}</h4>
|
||||
<div className="ability-details">
|
||||
{type}
|
||||
{roll}
|
||||
{damage}
|
||||
{costs}
|
||||
</div>
|
||||
</div>
|
||||
<HTML className="ability-content" html={ability.description} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
interface HTMLWrapperProps {
|
||||
html: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function HTML({ html, className }: HTMLWrapperProps) {
|
||||
return (
|
||||
<div className={className} dangerouslySetInnerHTML={{ __html: html }} />
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<section className={sectionClasses} data-component-id={componentId}>
|
||||
<div className={headerClasses}>
|
||||
{preInfo}
|
||||
<h2>{title}</h2>
|
||||
{info}
|
||||
</div>
|
||||
<div className="section-tag section-tag--left">{leftCornerTag}</div>
|
||||
<div className="section-tag section-tag--right">{rightCornerTag}</div>
|
||||
<div className={contentClasses}>{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import { Section } from "./base/section";
|
||||
|
||||
interface GlossaryTableProps {
|
||||
terms: [string, string[]][];
|
||||
}
|
||||
|
||||
export function GlossaryTable({ terms }: GlossaryTableProps) {
|
||||
return (
|
||||
<Section type="glossary" componentId="glossary" title="Glossary">
|
||||
<div className="glossary-content">
|
||||
<dl>
|
||||
{terms.map(([term, defs]) => {
|
||||
return (
|
||||
<div key={term}>
|
||||
<dt>{term}</dt>
|
||||
{defs.map((d, i) => (
|
||||
<dd key={i}>{d}</dd>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</dl>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
import type { Playbill } from "@proscenium/playbill";
|
||||
|
||||
interface PlaybillHeaderProps {
|
||||
playbill: Playbill;
|
||||
}
|
||||
|
||||
export function PageHeader({ playbill }: PlaybillHeaderProps) {
|
||||
return (
|
||||
<header>
|
||||
<h1 className="header-title">{playbill.name}</h1>
|
||||
<p className="header-byline">A Playbill by {playbill.author} </p>
|
||||
<p>
|
||||
{" "}
|
||||
<small className="header-info"> Version {playbill.version}</small>
|
||||
</p>
|
||||
</header>
|
||||
);
|
||||
}
|
|
@ -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(
|
||||
<p key="affinity" className={`item-affinity ${item.affinity}`}>
|
||||
{item.affinity} Affinity
|
||||
</p>,
|
||||
);
|
||||
}
|
||||
|
||||
if (item.damage !== null && item.damage.amount > 0) {
|
||||
infoParts.push(
|
||||
<p key="damage" className={`item-damage ${item.damage.type}`}>
|
||||
{item.damage.amount} {capitalize(item.damage.type)} Damage
|
||||
</p>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Section
|
||||
type="item"
|
||||
componentId={item.id}
|
||||
title={item.name}
|
||||
info={<div className="item-info">{infoParts}</div>}
|
||||
rightCornerTag={capitalize(item.rarity)}
|
||||
leftCornerTag={itemTypeDescriptor}
|
||||
>
|
||||
<HTML html={item.description} />
|
||||
</Section>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<div className="method-rank" key={i}>
|
||||
<h3>Rank {i + 1}</h3>
|
||||
<div className="method-rank-content" style={{ gridTemplateColumns }}>
|
||||
{rank.map((abilityId) => {
|
||||
const ability = playbill.getAbility(abilityId);
|
||||
|
||||
if (ability === null) {
|
||||
throw new Error(`Ability not found: ${abilityId}`);
|
||||
}
|
||||
|
||||
return <AbilityCard ability={ability} key={abilityId} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Section
|
||||
type="method"
|
||||
componentId={method.id}
|
||||
title={method.name}
|
||||
preInfo={<p className="method-curator">{method.curator}'s Method of</p>}
|
||||
info={
|
||||
<p
|
||||
className="method-description"
|
||||
dangerouslySetInnerHTML={{ __html: method.description }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="method-ranks">{ranks}</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<Section
|
||||
type="resource"
|
||||
componentId={resource.id}
|
||||
title={resource.name}
|
||||
info={
|
||||
<ul className="resource-categories">
|
||||
{resource.categories.map((category) => (
|
||||
<li key={category}>{category}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<HTML html={resource.description} />
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
interface ImageResourceSectionProps {
|
||||
resource: ImageResource;
|
||||
}
|
||||
|
||||
function ImageResourceSection({ resource }: ImageResourceSectionProps) {
|
||||
return (
|
||||
<Section
|
||||
type="resource"
|
||||
componentId={resource.id}
|
||||
title={resource.name}
|
||||
info={
|
||||
<ul className="resource-categories">
|
||||
{resource.categories.map((category) => (
|
||||
<li key={category}>{category}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<figure className="resource-image">
|
||||
<img src={resource.url} alt={resource.name} />
|
||||
<figcaption
|
||||
dangerouslySetInnerHTML={{ __html: resource.description }}
|
||||
/>
|
||||
</figure>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
interface TableResourceSectionProps {
|
||||
resource: TableResource;
|
||||
}
|
||||
|
||||
function TableResourceSection({ resource }: TableResourceSectionProps) {
|
||||
const [headers, ...rows] = resource.data;
|
||||
return (
|
||||
<Section
|
||||
type="resource"
|
||||
componentId={resource.id}
|
||||
title={resource.name}
|
||||
info={
|
||||
<ul className="resource-categories">
|
||||
{resource.categories.map((category) => (
|
||||
<li key={category}>{category}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<table className="resource-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{headers.map((header) => (
|
||||
<th key={header} dangerouslySetInnerHTML={{ __html: header }} />
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, i) => (
|
||||
<tr key={i}>
|
||||
{row.map((cell, j) => (
|
||||
<td key={j} dangerouslySetInnerHTML={{ __html: cell }} />
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
interface ResourceSectionProps {
|
||||
resource: Resource;
|
||||
}
|
||||
|
||||
export function ResourceSection({ resource }: ResourceSectionProps) {
|
||||
switch (resource.type) {
|
||||
case "text":
|
||||
return <TextResourceSection resource={resource} />;
|
||||
case "image":
|
||||
return <ImageResourceSection resource={resource} />;
|
||||
case "table":
|
||||
return <TableResourceSection resource={resource} />;
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<Section title={rule.name} componentId={rule.id} type="rule">
|
||||
<HTML html={rule.description} />
|
||||
</Section>
|
||||
);
|
||||
}
|
|
@ -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 ? (
|
||||
<div className="species-abilities">
|
||||
<h3>Innate Abilities</h3>
|
||||
{species.abilities.map((abilityId) => {
|
||||
const ability = playbill.getAbility(abilityId);
|
||||
|
||||
if (ability === null) {
|
||||
throw new Error(`Ability not found: ${abilityId}`);
|
||||
}
|
||||
|
||||
return <AbilityCard ability={ability} key={abilityId} />;
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
);
|
||||
|
||||
const sectionContent = (
|
||||
<>
|
||||
<div className="species-stats">
|
||||
<div className="species-stat-hp">
|
||||
<h4 className="hp species-stat-hp-title" title="Health Points">
|
||||
HP
|
||||
</h4>
|
||||
<p className="species-stat-hp-value">{hpString}</p>
|
||||
</div>
|
||||
|
||||
<div className="species-stat-ap">
|
||||
<h4 className="ap species-stat-ap-title" title="Action Points">
|
||||
AP
|
||||
</h4>
|
||||
<p className="species-stat-ap-value">{apString}</p>
|
||||
</div>
|
||||
|
||||
<div className="species-stat-ep">
|
||||
<h4 className="ep species-stat-ep-title" title="Exhaustion Points">
|
||||
EP
|
||||
</h4>
|
||||
<p className="species-stat-ep-value">{epString}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="species-talents">
|
||||
<div className="species-talent-muscle">
|
||||
<h4 className="species-talent-muscle-title muscle">Muscle</h4>
|
||||
<p className={`species-talent-muscle-value ${species.muscle}`}>
|
||||
{species.muscle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="species-talent-focus">
|
||||
<h4 className="species-talent-focus-title focus">Focus</h4>
|
||||
<p className={`species-talent-focus-value ${species.focus}`}>
|
||||
{species.focus}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="species-talent-knowledge">
|
||||
<h4 className="species-talent-knowledge-title knowledge">
|
||||
Knowledge
|
||||
</h4>
|
||||
<p className={`species-talent-knowledge-value ${species.knowledge}`}>
|
||||
{species.knowledge}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="species-talent-charm">
|
||||
<h4 className="species-talent-charm-title charm">Charm</h4>
|
||||
<p className={`species-talent-charm-value ${species.charm}`}>
|
||||
{species.charm}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="species-talent-cunning">
|
||||
<h4 className="species-talent-cunning-title cunning">Cunning</h4>
|
||||
<p className={`species-talent-cunning-value ${species.cunning}`}>
|
||||
{species.cunning}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="species-talent-spark">
|
||||
<h4 className="species-talent-spark-title spark">Spark</h4>
|
||||
<p className={`species-talent-spark-value ${species.spark}`}>
|
||||
{species.spark}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<HTML className="species-content" html={species.description} />
|
||||
{abilitySection}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Section
|
||||
type="species"
|
||||
componentId={species.id}
|
||||
title={species.name}
|
||||
info={<p>Species</p>}
|
||||
>
|
||||
{sectionContent}
|
||||
</Section>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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<string> {
|
||||
// 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(`<style>${defaultCSS}</style>`);
|
||||
}
|
||||
|
||||
if (boundPlaybill.styles.length > 0) {
|
||||
cssParts.push(`<style>${boundPlaybill.styles}</style>`);
|
||||
}
|
||||
|
||||
const css = cssParts.join("\n");
|
||||
|
||||
const body = renderToString(
|
||||
<>
|
||||
<article id="species" className="view">
|
||||
{playbill.allSpecies.map((species) => (
|
||||
<SpeciesSection
|
||||
key={species.id}
|
||||
species={species}
|
||||
playbill={playbill}
|
||||
/>
|
||||
))}
|
||||
</article>
|
||||
|
||||
<article id="methods" className="view" style={{ display: "none" }}>
|
||||
{playbill.allMethods.map((method) => (
|
||||
<MethodSection key={method.id} method={method} playbill={playbill} />
|
||||
))}
|
||||
</article>
|
||||
|
||||
<article id="items" className="view" style={{ display: "none" }}>
|
||||
{playbill.allItems.map((item) => (
|
||||
<ItemCard key={item.id} item={item} />
|
||||
))}
|
||||
</article>
|
||||
|
||||
<article id="rules" className="view" style={{ display: "none" }}>
|
||||
{playbill.allRules.map((rule) => (
|
||||
<RuleSection key={rule.id} rule={rule} />
|
||||
))}
|
||||
</article>
|
||||
|
||||
<article id="glossary" className="view" style={{ display: "none" }}>
|
||||
{<GlossaryTable terms={playbill.allDefinitions} />}
|
||||
</article>
|
||||
|
||||
<article id="resources" className="view" style={{ display: "none" }}>
|
||||
{playbill.allResources.map((resource) => (
|
||||
<ResourceSection key={resource.id} resource={resource} />
|
||||
))}
|
||||
</article>
|
||||
</>,
|
||||
);
|
||||
|
||||
// Main document
|
||||
const doc = `
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Playbill: ${playbill.name}</title>
|
||||
${css}
|
||||
|
||||
<script>
|
||||
function removeHash() {
|
||||
history.pushState("", document.title, window.location.pathname);
|
||||
}
|
||||
|
||||
function setHash(hash) {
|
||||
if (hash.length > 0) {
|
||||
history.pushState(null, null, "#" + hash);
|
||||
} else {
|
||||
removeHash();
|
||||
}
|
||||
}
|
||||
|
||||
function hideAllViews() {
|
||||
document.querySelectorAll(".view").forEach((view) => {
|
||||
view.style.display = "none";
|
||||
});
|
||||
}
|
||||
|
||||
function showAllViews() {
|
||||
document.querySelectorAll(".view").forEach((view) => {
|
||||
view.style.display = "block";
|
||||
});
|
||||
setHash("");
|
||||
}
|
||||
|
||||
function showView(viewId) {
|
||||
document.getElementById(viewId).style.display = "block";
|
||||
setHash(viewId);
|
||||
}
|
||||
|
||||
|
||||
function hideAllAndShow(viewId) {
|
||||
hideAllViews();
|
||||
showView(viewId);
|
||||
}
|
||||
|
||||
function navigateFromHash() {
|
||||
const hash = window.location.hash.slice(1);
|
||||
if (hash.length > 0) {
|
||||
const view = document.getElementById(hash);
|
||||
|
||||
if (view !== null) {
|
||||
hideAllAndShow(hash);
|
||||
} else {
|
||||
showAllViews();
|
||||
}
|
||||
} else {
|
||||
showAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", navigateFromHash());
|
||||
window.addEventListener("hashchange", navigateFromHash())
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
${renderToString(<PageHeader playbill={playbill} />)}
|
||||
<blockquote class="overview"><p>${playbill.description}</p></blockquote>
|
||||
<nav>
|
||||
<button onclick="hideAllAndShow('species')" id="species-nav">Species</button>
|
||||
<button onclick="hideAllAndShow('methods')" id="methods-nav">Methods</button>
|
||||
<button onclick="hideAllAndShow('items')" id="items-nav">Items</button>
|
||||
<button onclick="hideAllAndShow('rules')" id="rules-nav">Rulebook</button>
|
||||
<button onclick="hideAllAndShow('glossary')" id="glossary-nav">Glossary</button>
|
||||
<button onclick="hideAllAndShow('resources')" id="resources-nav">Resources</button>
|
||||
<button onclick="showAllViews()" id="all-nav">Show All</button>
|
||||
</nav>
|
||||
${body}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
return doc;
|
||||
}
|
27
src/util.ts
Normal file
27
src/util.ts
Normal file
|
@ -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<string | null> {
|
||||
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;
|
||||
}
|
111
src/util/args.ts
111
src/util/args.ts
|
@ -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>
|
||||
|
||||
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<typeof RendererSchema>;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -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<string> {
|
||||
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<unknown> {
|
||||
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<unknown> {
|
||||
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));
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./args";
|
||||
export * from "./files";
|
|
@ -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",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue