@lutrin/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/design/layouts/before-after.json +6 -0
- package/design/layouts/funnel.json +6 -0
- package/design/layouts/journey.json +5 -0
- package/design/layouts/key-message.json +5 -0
- package/design/layouts/portfolio.json +8 -0
- package/design/layouts/priority-matrix.json +7 -0
- package/design/layouts/pros-cons.json +6 -0
- package/design/layouts/pyramid.json +6 -0
- package/design/layouts/risk-map.json +8 -0
- package/design/layouts/roadmap.json +6 -0
- package/design/themes/default.json +159 -0
- package/package.json +57 -0
- package/src/cli.mjs +1114 -0
- package/src/deck/assets.mjs +910 -0
- package/src/deck/chart.mjs +424 -0
- package/src/deck/context.mjs +62 -0
- package/src/deck/highlight.mjs +206 -0
- package/src/deck/kit.mjs +342 -0
- package/src/deck/layout.mjs +1599 -0
- package/src/deck/parse.mjs +776 -0
- package/src/deck/suggest.mjs +33 -0
- package/src/deck/theme.mjs +1135 -0
- package/src/deck/tokens.mjs +268 -0
- package/src/deck/validate.mjs +737 -0
- package/src/html/render.mjs +1586 -0
- package/src/kit/archive.mjs +448 -0
- package/src/pptx/anim.mjs +196 -0
- package/src/pptx/fonts.mjs +204 -0
- package/src/pptx/morph.mjs +103 -0
- package/src/pptx/render.mjs +1379 -0
- package/src/vendor.mjs +281 -0
- package/src/worker/protocol.d.ts +82 -0
- package/src/worker/worker.mjs +145 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Julien Riel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @lutrin/core — the compiler
|
|
2
|
+
|
|
3
|
+
The engine behind [Lutrin](../../README.md): enriched Markdown (a DSL) →
|
|
4
|
+
PowerPoint `.pptx` or standalone HTML, with the page layout decided by the
|
|
5
|
+
engine. It provides the `lutrin` CLI, and serves as a library for the editor
|
|
6
|
+
hosts (VS Code extension, Obsidian plugin).
|
|
7
|
+
|
|
8
|
+
Neither output format is privileged: both renderers consume the **same**
|
|
9
|
+
geometric **scene**, in pixels on a 1280 × 720 grid.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
The package is not published on npm yet. From a clone of the repository:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install # at the root of the monorepo
|
|
17
|
+
npx lutrin --help
|
|
18
|
+
|
|
19
|
+
npm link -w lutrin # to call "lutrin" from anywhere
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Node ≥ 22 (`engines.node`). The sources are executed as they are (ESM) — no
|
|
23
|
+
build step.
|
|
24
|
+
|
|
25
|
+
## CLI
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
lutrin build <deck.md> [-o output.pptx|output.html] [--kit <ref>] [--vendor-assets] [--force] [--verbose]
|
|
29
|
+
lutrin preview <deck.md> [--port 4321] [--kit <ref>]
|
|
30
|
+
lutrin validate <deck.md> [--json] [--kit <ref>]
|
|
31
|
+
lutrin vendor <deck.md> [--kit <ref>]
|
|
32
|
+
lutrin inspect <deck.md> [--kit <ref>]
|
|
33
|
+
lutrin config [--kit <ref>] [--unset]
|
|
34
|
+
lutrin kit install <file.deckkit|https://…> [--force] [--name <name>]
|
|
35
|
+
lutrin kit list | remove <name> | create <directory> [-o <file.deckkit>]
|
|
36
|
+
lutrin capabilities [<deck.md>] [--kit <ref>] [--json]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The output format is deduced from the extension of `-o`. `--kit` takes
|
|
40
|
+
precedence over the deck's frontmatter; a bare name designates an installed
|
|
41
|
+
kit, everything else is a path resolved against the current directory.
|
|
42
|
+
|
|
43
|
+
**`build` does not deliver a deck in error.** If validation returns at least
|
|
44
|
+
one diagnostic of severity `error` — unknown directive, non-existent layout,
|
|
45
|
+
kit requested EXPLICITLY (`--kit` or the frontmatter's `kit:`) but
|
|
46
|
+
unresolvable — the command prints the errors, exits with **exit code 1** and
|
|
47
|
+
writes no file. `--force` compiles anyway, errors on screen, and exits with
|
|
48
|
+
code 0: that is for a draft one wants to see. A kit coming from an
|
|
49
|
+
**implicit** default (project, user, host) and not found stays a mere
|
|
50
|
+
warning, and does not prevent compilation.
|
|
51
|
+
|
|
52
|
+
Exit codes: `0` success; `1` error — invalid argument or flag, input file not
|
|
53
|
+
found, explicit kit unresolvable, deck carrying `error` diagnostics (except
|
|
54
|
+
`build --force`). `validate` exits with 1 as soon as one `error` remains;
|
|
55
|
+
`inspect`, `vendor` and `capabilities` do not open the deck's diagnostics and
|
|
56
|
+
exit with 0 — with the one reservation that an explicit unresolvable kit
|
|
57
|
+
stops them too, with 1.
|
|
58
|
+
|
|
59
|
+
The DSL is documented in [docs/dsl.md](../../docs/dsl.md). `lutrin
|
|
60
|
+
capabilities` reports as JSON what the **installed** engine actually supports
|
|
61
|
+
(layouts, parameters, directives, chart types, diagnostic codes): it is the
|
|
62
|
+
source to query rather than to guess at.
|
|
63
|
+
|
|
64
|
+
Its scope depends on what it is given:
|
|
65
|
+
|
|
66
|
+
| Form | What is published |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `lutrin capabilities` | the **bare** engine — built-in layouts and the official catalog; `userLayouts` always empty |
|
|
69
|
+
| `lutrin capabilities <deck.md>` | in addition: the frontmatter's `kit:` honoured, its layouts, and the `layouts/*.json` next to the deck |
|
|
70
|
+
| `lutrin capabilities --kit <ref>` | the catalog of that brand, with the current directory serving as the base |
|
|
71
|
+
|
|
72
|
+
`--kit` takes precedence over the frontmatter's `kit:`, as it does for every
|
|
73
|
+
other command (`--theme` remains a deprecated alias). A kit requested
|
|
74
|
+
explicitly and unresolvable exits with **exit code 1** without writing
|
|
75
|
+
anything to stdout — never a generic catalog delivered in silence. A
|
|
76
|
+
`layouts/*.json` that could not be read produces a warning on **stderr**:
|
|
77
|
+
stdout stays pure JSON, usable by `jq`. `--json` is the explicit form of the
|
|
78
|
+
default, and has no effect.
|
|
79
|
+
|
|
80
|
+
## API
|
|
81
|
+
|
|
82
|
+
The package is ESM and exposes subpath entry points:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
import { compileHtml } from '@lutrin/core/html';
|
|
86
|
+
|
|
87
|
+
const { html, stats } = await compileHtml(markdown, { baseDir });
|
|
88
|
+
// { fragment: true } → { slides, css, fontsCss, … } for a webview
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| Subpath | Contents |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `@lutrin/core`, `/html` | `renderDeckHtml`, `compileHtml` — standalone HTML or fragment |
|
|
94
|
+
| `/pptx` | `renderDeck` — writing the `.pptx` |
|
|
95
|
+
| `/parse` | `parseDeck` — Markdown → IR |
|
|
96
|
+
| `/layout` | `buildScenes`, `blockHeight`, the layout registry |
|
|
97
|
+
| `/validate` | `validateDeck`, `capabilities` |
|
|
98
|
+
| `/context` | `prepareDeckContext` — kit + layouts, to be called before `buildScenes` |
|
|
99
|
+
| `/theme` | `resolveTheme`, `applyTheme`, WCAG contrast |
|
|
100
|
+
| `/tokens` | the living design tokens |
|
|
101
|
+
|
|
102
|
+
Mandatory call order for a host: `parseDeck` →
|
|
103
|
+
`prepareDeckContext(meta, { baseDir, themePath, defaultTheme })` →
|
|
104
|
+
`buildScenes` → renderer. The tokens and the layout registry are module state
|
|
105
|
+
mutated in place; `prepareDeckContext` resets them on every compilation,
|
|
106
|
+
which makes a warm process (worker, preview server) safe between two decks.
|
|
107
|
+
|
|
108
|
+
## The pipeline
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
Markdown → AST (markdown-it) → IR → layout engine → scene → renderer
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| File | Role |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `src/deck/parse.mjs` | Markdown → IR (`deck → slides → sections → blocks`, with the source line) |
|
|
117
|
+
| `src/deck/layout.mjs` | layout inference, placement in regions, pagination; the layout registry |
|
|
118
|
+
| `src/deck/validate.mjs` | positioned diagnostics (`validateDeck`) and `capabilities()` |
|
|
119
|
+
| `src/deck/context.mjs` | the single insertion point for the kit and the layouts |
|
|
120
|
+
| `src/deck/theme.mjs` | resolution and validation of a theme, WCAG contrast |
|
|
121
|
+
| `src/deck/kit.mjs` | the `kit.json` manifest — reading, validation, internal paths |
|
|
122
|
+
| `src/deck/tokens.mjs` | design tokens of the generic design (mirror of `design/themes/default.json`) |
|
|
123
|
+
| `src/deck/chart.mjs` | `chart` blocks → SVG in the theme's style |
|
|
124
|
+
| `src/deck/assets.mjs` | remote images, Lucide icons, LaTeX, Mermaid (cache `~/.cache/lutrin/`) |
|
|
125
|
+
| `src/deck/highlight.mjs` | syntax highlighting of code blocks |
|
|
126
|
+
| `src/deck/suggest.mjs` | did you mean…? (edit distance) |
|
|
127
|
+
| `src/pptx/render.mjs` | scene → PptxGenJS |
|
|
128
|
+
| `src/pptx/fonts.mjs` | embedding the theme's TTFs into the `.pptx` |
|
|
129
|
+
| `src/pptx/anim.mjs` | native animations (`<p:timing>`, one effect per block type) |
|
|
130
|
+
| `src/pptx/morph.mjs` | Morph transition of "(cont.)" slides |
|
|
131
|
+
| `src/html/render.mjs` | scene → standalone HTML document (+ fragment mode) |
|
|
132
|
+
| `src/kit/archive.mjs` | `.deckkit` archives — package, download, install |
|
|
133
|
+
| `src/worker/worker.mjs` | IPC worker of the editor hosts (types in `protocol.d.ts`) |
|
|
134
|
+
| `src/vendor.mjs` | `lutrin vendor` — freezing the deck's external dependencies |
|
|
135
|
+
| `design/themes/default.json` | canonical mirror of the default theme, a template to copy |
|
|
136
|
+
| `design/layouts/*.json` | the catalog of the ten official layouts |
|
|
137
|
+
|
|
138
|
+
`src/deck/` is the core: it knows no output format and imports no backend
|
|
139
|
+
library — `test/boundary.test.mjs` verifies it.
|
|
140
|
+
|
|
141
|
+
## Tests
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm test -w @lutrin/core
|
|
145
|
+
UPDATE_GOLDEN=1 npm test # from the root, after an intended engine change
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
A `node:test` harness, with no test dependency. See
|
|
149
|
+
[CONTRIBUTING.md](https://github.com/julien-riel/lutrin/blob/main/CONTRIBUTING.md).
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT. Third-party dependencies:
|
|
154
|
+
[THIRD-PARTY-NOTICES.md](https://github.com/julien-riel/lutrin/blob/main/THIRD-PARTY-NOTICES.md).
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Slate (generic default theme)",
|
|
3
|
+
"colors": {
|
|
4
|
+
"primary": "1D4ED8",
|
|
5
|
+
"primaryLighter": "60A5FA",
|
|
6
|
+
"primaryDarker": "1E3A8A",
|
|
7
|
+
"brand": "1D4ED8",
|
|
8
|
+
"brandBlack": "111827",
|
|
9
|
+
"neutralPrimary": "212529",
|
|
10
|
+
"neutralSecondary": "637381",
|
|
11
|
+
"neutralTertiary": "ADB2BD",
|
|
12
|
+
"neutralStroke": "CED4DA",
|
|
13
|
+
"underground1": "F8F9FA",
|
|
14
|
+
"underground2": "DEE2E6",
|
|
15
|
+
"ground": "FFFFFF",
|
|
16
|
+
"highlightLight": "EFF6FF",
|
|
17
|
+
"highlightStrong": "DBEAFE",
|
|
18
|
+
"informative": "0079C4",
|
|
19
|
+
"informativeDark": "004B7B",
|
|
20
|
+
"informativeLight": "E6F5F9",
|
|
21
|
+
"positive": "0DA566",
|
|
22
|
+
"positiveDark": "025D29",
|
|
23
|
+
"positiveLight": "E7F6F0",
|
|
24
|
+
"warning": "FFB833",
|
|
25
|
+
"warningDark": "6C4600",
|
|
26
|
+
"warningLight": "FEFAE6",
|
|
27
|
+
"negative": "D3310A",
|
|
28
|
+
"negativeDark": "851A00",
|
|
29
|
+
"negativeLight": "FFEBE6"
|
|
30
|
+
},
|
|
31
|
+
"fonts": {
|
|
32
|
+
"body": "Arial",
|
|
33
|
+
"mono": "Courier New"
|
|
34
|
+
},
|
|
35
|
+
"type": {
|
|
36
|
+
"coverTitle": 40,
|
|
37
|
+
"coverSubtitle": 20,
|
|
38
|
+
"sectionTitle": 32,
|
|
39
|
+
"slideTitle": 26,
|
|
40
|
+
"sectionHeading": 16,
|
|
41
|
+
"lead": 16,
|
|
42
|
+
"body": 14,
|
|
43
|
+
"bullet": 14,
|
|
44
|
+
"bulletNested": 13,
|
|
45
|
+
"small": 11,
|
|
46
|
+
"caption": 9,
|
|
47
|
+
"code": 11,
|
|
48
|
+
"metricValue": 44,
|
|
49
|
+
"metricLabel": 12,
|
|
50
|
+
"quote": 22,
|
|
51
|
+
"tableBody": 12,
|
|
52
|
+
"tableHeader": 12
|
|
53
|
+
},
|
|
54
|
+
"space": {
|
|
55
|
+
"xs": 8,
|
|
56
|
+
"sm": 16,
|
|
57
|
+
"md": 24,
|
|
58
|
+
"lg": 32,
|
|
59
|
+
"xl": 40,
|
|
60
|
+
"xxl": 48
|
|
61
|
+
},
|
|
62
|
+
"page": {
|
|
63
|
+
"margin": 48,
|
|
64
|
+
"gutter": 24,
|
|
65
|
+
"titleHeight": 96,
|
|
66
|
+
"footerHeight": 32
|
|
67
|
+
},
|
|
68
|
+
"rounded": {
|
|
69
|
+
"sm": 2,
|
|
70
|
+
"md": 4,
|
|
71
|
+
"lg": 8,
|
|
72
|
+
"pill": 64
|
|
73
|
+
},
|
|
74
|
+
"chrome": {
|
|
75
|
+
"cover": {
|
|
76
|
+
"barY": 280,
|
|
77
|
+
"barW": 96,
|
|
78
|
+
"barH": 6,
|
|
79
|
+
"titleY": 304,
|
|
80
|
+
"titleH": 120,
|
|
81
|
+
"subtitleY": 424,
|
|
82
|
+
"subtitleH": 72,
|
|
83
|
+
"bylineBottom": 80,
|
|
84
|
+
"bylineH": 32,
|
|
85
|
+
"logoH": 44
|
|
86
|
+
},
|
|
87
|
+
"section": {
|
|
88
|
+
"titleY": 288,
|
|
89
|
+
"titleH": 144,
|
|
90
|
+
"logoH": 32
|
|
91
|
+
},
|
|
92
|
+
"title": {
|
|
93
|
+
"accentW": 64,
|
|
94
|
+
"accentH": 4
|
|
95
|
+
},
|
|
96
|
+
"footer": {
|
|
97
|
+
"textW": 600,
|
|
98
|
+
"h": 24,
|
|
99
|
+
"numW": 64
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"chartColors": [
|
|
103
|
+
"0A8A76",
|
|
104
|
+
"B87F00",
|
|
105
|
+
"0079C4",
|
|
106
|
+
"D3310A",
|
|
107
|
+
"005E99",
|
|
108
|
+
"8A5C00"
|
|
109
|
+
],
|
|
110
|
+
"layerShades": [
|
|
111
|
+
{
|
|
112
|
+
"fill": "1E3A8A",
|
|
113
|
+
"ink": "FFFFFF"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"fill": "1D4ED8",
|
|
117
|
+
"ink": "FFFFFF"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"fill": "60A5FA",
|
|
121
|
+
"ink": "212529"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"fill": "DBEAFE",
|
|
125
|
+
"ink": "1E3A8A"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"fill": "EFF6FF",
|
|
129
|
+
"ink": "1E3A8A"
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
"trendInk": {
|
|
133
|
+
"positive": "025D29",
|
|
134
|
+
"negative": "851A00",
|
|
135
|
+
"neutral": "637381"
|
|
136
|
+
},
|
|
137
|
+
"semantic": {
|
|
138
|
+
"info": {
|
|
139
|
+
"fill": "E6F5F9",
|
|
140
|
+
"text": "004B7B",
|
|
141
|
+
"label": "Info"
|
|
142
|
+
},
|
|
143
|
+
"success": {
|
|
144
|
+
"fill": "E7F6F0",
|
|
145
|
+
"text": "025D29",
|
|
146
|
+
"label": "Key point"
|
|
147
|
+
},
|
|
148
|
+
"warning": {
|
|
149
|
+
"fill": "FEFAE6",
|
|
150
|
+
"text": "6C4600",
|
|
151
|
+
"label": "Caution"
|
|
152
|
+
},
|
|
153
|
+
"danger": {
|
|
154
|
+
"fill": "FFEBE6",
|
|
155
|
+
"text": "851A00",
|
|
156
|
+
"label": "Important"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lutrin/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Markdown → PowerPoint / HTML presentation compiler, themable: a generic default theme, organization brand kits distributed as .deckkit archives.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --import ./test/setup.mjs --test \"test/*.test.mjs\""
|
|
8
|
+
},
|
|
9
|
+
"files": ["src", "design", "README.md"],
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./src/html/render.mjs",
|
|
15
|
+
"./cli": "./src/cli.mjs",
|
|
16
|
+
"./html": "./src/html/render.mjs",
|
|
17
|
+
"./parse": "./src/deck/parse.mjs",
|
|
18
|
+
"./layout": "./src/deck/layout.mjs",
|
|
19
|
+
"./pptx": "./src/pptx/render.mjs",
|
|
20
|
+
"./validate": "./src/deck/validate.mjs",
|
|
21
|
+
"./tokens": "./src/deck/tokens.mjs",
|
|
22
|
+
"./theme": "./src/deck/theme.mjs",
|
|
23
|
+
"./context": "./src/deck/context.mjs"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
27
|
+
"jszip": "^3.10.1",
|
|
28
|
+
"lucide-static": "^1.24.0",
|
|
29
|
+
"markdown-it": "^14.3.0",
|
|
30
|
+
"markdown-it-container": "^4.0.0",
|
|
31
|
+
"mathjax-full": "^3.2.1",
|
|
32
|
+
"pptxgenjs": "4.0.1"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@mermaid-js/mermaid-cli": "^11.16.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@mermaid-js/mermaid-cli": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"keywords": ["markdown", "powerpoint", "pptx", "slides", "presentation", "deck", "html"],
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/julien-riel/lutrin.git",
|
|
46
|
+
"directory": "packages/core"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/julien-riel/lutrin/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/julien-riel/lutrin#readme",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=22"
|
|
54
|
+
},
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"author": "Julien Riel"
|
|
57
|
+
}
|