@nkardaz/typography-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 +368 -0
- package/dist/factory.d.ts +7 -0
- package/dist/index.cjs +185 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.mjs +171 -0
- package/dist/types.d.ts +9 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yalla Nkardaz
|
|
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,368 @@
|
|
|
1
|
+
# @nkardaz/typography-core
|
|
2
|
+
|
|
3
|
+
Framework-agnostic core for building typography plugins.
|
|
4
|
+
Provides rule initialisation, string-phase processing, locale resolution,
|
|
5
|
+
and a plugin factory — with no knowledge of any AST or framework.
|
|
6
|
+
|
|
7
|
+
Used internally by [@nkardaz/remark-typography](https://github.com/DemerNkardaz/remark-typography)
|
|
8
|
+
and intended as the foundation for any custom typography plugin built on
|
|
9
|
+
[@nkardaz/typography-rules](https://github.com/DemerNkardaz/typography-rules).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i -D @nkardaz/typography-core
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> **Requires Node.js ≥ 24.0.0**
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
`@nkardaz/typography-core` does three things:
|
|
26
|
+
|
|
27
|
+
- **Initialises rules** — registers built-in and custom rule sets via `initRules`
|
|
28
|
+
- **Processes strings** — applies all string-phase rules to a text value via `applyRules`
|
|
29
|
+
- **Creates plugins** — provides `createTypographyPlugin` as a generic factory for building framework-specific plugins with a standardised config contract
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## API
|
|
34
|
+
|
|
35
|
+
### `initRules(config: ResolvedCoreConfig): void`
|
|
36
|
+
|
|
37
|
+
Registers rule sets based on the resolved config. Called once per plugin instantiation.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { initRules } from '@nkardaz/typography-core';
|
|
41
|
+
|
|
42
|
+
initRules({
|
|
43
|
+
initTypographyRules: true,
|
|
44
|
+
initMarkupRules: false,
|
|
45
|
+
locale: 'en',
|
|
46
|
+
plugins: [myCustomRules],
|
|
47
|
+
logs: false,
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Behaviour:
|
|
52
|
+
- If `initTypographyRules` is `true` — calls `initTypographyRules()` from `@nkardaz/typography-rules`
|
|
53
|
+
- If `initMarkupRules` is `true` — calls `initMarkupRules()` from `@nkardaz/typography-rules`
|
|
54
|
+
- Runs each plugin in `plugins` as `plugin()()`
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### `applyRules(text, locale, config): string`
|
|
59
|
+
|
|
60
|
+
Applies all string-phase rules (`replace`, `transform`, `function→string`) to a text value.
|
|
61
|
+
Protected regions (URLs, emails, code spans, etc.) are shielded before rules run and restored after.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { applyRules } from '@nkardaz/typography-core';
|
|
65
|
+
|
|
66
|
+
const result = applyRules('Hello -- world', 'en', { logs: false });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Node-type rules (`kind: 'node'`) are skipped here — they require AST access and are handled
|
|
70
|
+
by the framework-specific plugin layer.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### `applyNodeRules(textNodes, parent, locale, config): void`
|
|
75
|
+
|
|
76
|
+
Applies node-phase rules (`kind === 'node'` and `function`→`Node[]`) to a flat list of
|
|
77
|
+
text nodes belonging to a single parent element, mutating `parent.children` in-place.
|
|
78
|
+
|
|
79
|
+
Operates on one level of the tree only — it does not recurse. Traversal and locale
|
|
80
|
+
switching across element boundaries is the caller's responsibility (see `processElement`).
|
|
81
|
+
|
|
82
|
+
Must be called **after** `applyRules` + `joinNodes`/`splitNodes` have already handled
|
|
83
|
+
string-phase processing on the same text nodes.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { applyRules, applyNodeRules } from '@nkardaz/typography-core';
|
|
87
|
+
import { joinNodes, splitNodes } from '@nkardaz/typography-rules/helpers';
|
|
88
|
+
import type { ElementNode, TextNode } from '@nkardaz/typography-rules';
|
|
89
|
+
|
|
90
|
+
const textNodes = element.children.filter((c): c is TextNode => c.type === 'text');
|
|
91
|
+
|
|
92
|
+
if (textNodes.length > 0) {
|
|
93
|
+
// Phase 1 — string rules
|
|
94
|
+
const combined = joinNodes(textNodes);
|
|
95
|
+
const transformed = applyRules(combined, locale, { logs: false });
|
|
96
|
+
splitNodes(transformed, textNodes);
|
|
97
|
+
|
|
98
|
+
// Phase 2 — node rules (mutates element.children in-place)
|
|
99
|
+
applyNodeRules(textNodes, element, locale, { logs: false });
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Parameter | Type | Description |
|
|
104
|
+
| ----------- | ---------------------------------- | -------------------------------------------------------- |
|
|
105
|
+
| `textNodes` | `TextNode[]` | Direct text-node children of `parent`, pre-filtered |
|
|
106
|
+
| `parent` | `ElementNode` | The element whose `children` array is mutated on expansion |
|
|
107
|
+
| `locale` | `string` | Active locale for rule selection |
|
|
108
|
+
| `config` | `Pick<ResolvedCoreConfig, 'logs'>` | Only `logs` is used — controls warning output |
|
|
109
|
+
|
|
110
|
+
Rules of `kind: 'function'` are only expanded here if they return `Node[]`.
|
|
111
|
+
If a function rule returns a `string`, it is silently skipped (already handled by `applyRules`).
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### `processElement(element, locale, config): void`
|
|
116
|
+
|
|
117
|
+
Recursively processes an element and all its descendants, applying both string-phase and
|
|
118
|
+
node-phase typography rules. Owns traversal, locale inheritance, and coordinates the
|
|
119
|
+
two-phase pipeline at each level of the tree.
|
|
120
|
+
|
|
121
|
+
Locale is resolved per element: if the element carries a `lang`, `language`, or `locale`
|
|
122
|
+
attribute, that value is used for the element's subtree; otherwise the parent's locale is
|
|
123
|
+
inherited. This mirrors the `lang` attribute semantics of HTML.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { processElement } from '@nkardaz/typography-core';
|
|
127
|
+
import type { ElementNode } from '@nkardaz/typography-rules';
|
|
128
|
+
|
|
129
|
+
// Process an entire element tree with a base locale
|
|
130
|
+
processElement(rootElement, 'en', { logs: false });
|
|
131
|
+
|
|
132
|
+
// Mixed-locale content is handled automatically via attributes:
|
|
133
|
+
// <p lang="ru">Привет — мир</p> → processed with Russian rules
|
|
134
|
+
// <p>Hello -- world</p> → processed with inherited locale
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| Parameter | Type | Description |
|
|
138
|
+
| --------- | ---------------------------------- | ------------------------------------------------------------ |
|
|
139
|
+
| `element` | `ElementNode` | Root of the subtree to process; `children` mutated in-place |
|
|
140
|
+
| `locale` | `string` | Inherited locale from the parent scope |
|
|
141
|
+
| `config` | `Pick<ResolvedCoreConfig, 'logs'>` | Only `logs` is used — controls warning output |
|
|
142
|
+
|
|
143
|
+
Locale attribute priority: `lang` → `language` → `locale` → inherited.
|
|
144
|
+
|
|
145
|
+
> `processElement` works with `ElementNode` from `@nkardaz/typography-rules`. For vanilla DOM,
|
|
146
|
+
> write a thin adapter that reads `element.getAttribute('lang')` and maps DOM nodes to
|
|
147
|
+
> `ElementNode` — the pipeline logic is identical.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### `getFrontmatterLocale(data): string | undefined`
|
|
152
|
+
|
|
153
|
+
Resolves a locale string from a parsed frontmatter object.
|
|
154
|
+
Checks keys in order: `locale` → `lang` → `language`.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { getFrontmatterLocale } from '@nkardaz/typography-core';
|
|
158
|
+
|
|
159
|
+
const locale = getFrontmatterLocale({ lang: 'ru' }); // → 'ru'
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### `warning(message, showLogs): void`
|
|
165
|
+
|
|
166
|
+
Emits a prefixed `console.warn` if `showLogs` is `true`.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { warning } from '@nkardaz/typography-core';
|
|
170
|
+
|
|
171
|
+
warning('No rules registered for locale “is”', config.logs);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### `createTypographyPlugin(factory): (options?) => handler`
|
|
177
|
+
|
|
178
|
+
Generic factory for building framework-specific typography plugins.
|
|
179
|
+
Handles config merging, default resolution, and `initRules` — so the plugin
|
|
180
|
+
author only provides the handler logic.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { createTypographyPlugin } from '@nkardaz/typography-core';
|
|
184
|
+
|
|
185
|
+
export const myPlugin = createTypographyPlugin({
|
|
186
|
+
defaultOptions: {
|
|
187
|
+
locale: 'de',
|
|
188
|
+
},
|
|
189
|
+
createHandler: (config) => (tree) => {
|
|
190
|
+
// your AST traversal here
|
|
191
|
+
// config is fully resolved: ResolvedCoreConfig & TOptions
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The returned `myPlugin` is a standard two-call plugin function:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
myPlugin() // default options
|
|
200
|
+
myPlugin({ locale: 'fr' }) // override options
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Config resolution order (last wins):
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
factory defaults ← createTypographyPlugin defaultOptions ← user options
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Exports
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Functions
|
|
215
|
+
export { initRules } from '@nkardaz/typography-core';
|
|
216
|
+
export { applyRules } from '@nkardaz/typography-core';
|
|
217
|
+
export { applyNodeRules } from '@nkardaz/typography-core';
|
|
218
|
+
export { processElement } from '@nkardaz/typography-core';
|
|
219
|
+
export { getFrontmatterLocale } from '@nkardaz/typography-core';
|
|
220
|
+
export { warning } from '@nkardaz/typography-core';
|
|
221
|
+
export { createTypographyPlugin } from '@nkardaz/typography-core';
|
|
222
|
+
|
|
223
|
+
// Types
|
|
224
|
+
export type { TypographyCoreOptions, ResolvedCoreConfig, PluginFactory } from '@nkardaz/typography-core';
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Types
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import type {
|
|
233
|
+
TypographyCoreOptions,
|
|
234
|
+
ResolvedCoreConfig,
|
|
235
|
+
PluginFactory,
|
|
236
|
+
} from '@nkardaz/typography-core';
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### `TypographyCoreOptions`
|
|
240
|
+
|
|
241
|
+
Options accepted by any plugin built with `createTypographyPlugin`.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
export interface TypographyCoreOptions {
|
|
245
|
+
initTypographyRules?: boolean;
|
|
246
|
+
initMarkupRules?: boolean;
|
|
247
|
+
locale?: string;
|
|
248
|
+
plugins?: (() => () => void)[];
|
|
249
|
+
logs?: boolean;
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
| Option | Type | Default | Description |
|
|
254
|
+
| --------------------- | ---------------------- | ------- | --------------------------------------------------------------------------- |
|
|
255
|
+
| `initTypographyRules` | `boolean` | `true` | Register built-in typography rules from `@nkardaz/typography-rules` |
|
|
256
|
+
| `initMarkupRules` | `boolean` | `false` | Register built-in markup rules from `@nkardaz/typography-rules` |
|
|
257
|
+
| `locale` | `string` | `'en'` | Default locale for rule selection |
|
|
258
|
+
| `plugins` | `(() => () => void)[]` | `[]` | Custom rule plugins. Each is a factory returning a thunk: `() => () => void` |
|
|
259
|
+
| `logs` | `boolean` | `false` | Emit warnings for missing locales and rule errors |
|
|
260
|
+
|
|
261
|
+
### `ResolvedCoreConfig`
|
|
262
|
+
|
|
263
|
+
`Required<TypographyCoreOptions>` — all fields guaranteed present. This is what
|
|
264
|
+
`createHandler` receives.
|
|
265
|
+
|
|
266
|
+
### `PluginFactory<TOptions, TTree>`
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
export interface PluginFactory<TOptions extends TypographyCoreOptions, TTree> {
|
|
270
|
+
defaultOptions?: Partial<TOptions>;
|
|
271
|
+
createHandler: (config: ResolvedCoreConfig & TOptions) => (tree: TTree) => void;
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Building a Custom Plugin
|
|
278
|
+
|
|
279
|
+
Extend `TypographyCoreOptions` with your own fields and pass a typed factory:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { createTypographyPlugin, type TypographyCoreOptions } from '@nkardaz/typography-core';
|
|
283
|
+
import { myRules } from './rules';
|
|
284
|
+
|
|
285
|
+
interface MyPluginOptions extends TypographyCoreOptions {
|
|
286
|
+
strictMode?: boolean;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export const myTypographyPlugin = createTypographyPlugin<MyPluginOptions, MyTree>({
|
|
290
|
+
defaultOptions: {
|
|
291
|
+
locale: 'fr',
|
|
292
|
+
plugins: [myRules],
|
|
293
|
+
strictMode: false,
|
|
294
|
+
},
|
|
295
|
+
createHandler: (config) => (tree) => {
|
|
296
|
+
if (config.strictMode) {
|
|
297
|
+
// strict processing
|
|
298
|
+
}
|
|
299
|
+
// traverse tree, call applyRules, etc.
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The factory automatically calls `initRules` with the resolved config before
|
|
305
|
+
invoking `createHandler`. No need to call it manually.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Two-Phase Processing Pattern
|
|
310
|
+
|
|
311
|
+
Typography processing is split into two sequential phases. Both must run in order for full rule coverage.
|
|
312
|
+
|
|
313
|
+
**Phase 1 — string rules** (`applyRules`): handles `replace`, `transform`, and `function`→`string` rules.
|
|
314
|
+
Operates on raw text; protected regions (URLs, code, etc.) are shielded automatically.
|
|
315
|
+
|
|
316
|
+
**Phase 2 — node rules** (`applyNodeRules`): handles `node` and `function`→`Node[]` rules.
|
|
317
|
+
Expands text nodes into mixed text/element trees (e.g. wrapping `H[^2]O` into `H<sup>2</sup>O`).
|
|
318
|
+
Must run after phase 1 because node expansion on unsettled text produces incorrect results.
|
|
319
|
+
|
|
320
|
+
The helpers `joinNodes` / `splitNodes` from `@nkardaz/typography-rules/helpers` bridge sibling text
|
|
321
|
+
nodes into a single string before phase 1, so rules can see context across node boundaries.
|
|
322
|
+
|
|
323
|
+
For most cases `processElement` handles both phases and full traversal automatically:
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { processElement } from '@nkardaz/typography-core';
|
|
327
|
+
|
|
328
|
+
processElement(rootElement, 'en', { logs: false });
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
When building a custom plugin that needs finer control, the two phases can be called directly.
|
|
332
|
+
This is exactly what `processElement` does internally at each level:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import { joinNodes, splitNodes } from '@nkardaz/typography-rules/helpers';
|
|
336
|
+
import { applyRules, applyNodeRules } from '@nkardaz/typography-core';
|
|
337
|
+
import type { ElementNode, TextNode } from '@nkardaz/typography-rules';
|
|
338
|
+
|
|
339
|
+
function processLevel(element: ElementNode, locale: string, config: ResolvedCoreConfig) {
|
|
340
|
+
const lang =
|
|
341
|
+
element.attrs?.['lang'] ??
|
|
342
|
+
element.attrs?.['language'] ??
|
|
343
|
+
element.attrs?.['locale'] ??
|
|
344
|
+
locale;
|
|
345
|
+
|
|
346
|
+
const textNodes = element.children.filter((c): c is TextNode => c.type === 'text');
|
|
347
|
+
|
|
348
|
+
if (textNodes.length > 0) {
|
|
349
|
+
// Phase 1 — join siblings → string rules → split back
|
|
350
|
+
const combined = joinNodes(textNodes);
|
|
351
|
+
const transformed = applyRules(combined, lang, config);
|
|
352
|
+
splitNodes(transformed, textNodes);
|
|
353
|
+
|
|
354
|
+
// Phase 2 — node-expanding rules
|
|
355
|
+
applyNodeRules(textNodes, element, lang, config);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Recurse into non-text children with updated locale
|
|
359
|
+
for (const child of element.children) {
|
|
360
|
+
if (child.type !== 'text') {
|
|
361
|
+
processLevel(child as ElementNode, lang, config);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
> `applyNodeRules` operates on a single level only — it does not recurse.
|
|
368
|
+
> Traversal and locale propagation are always the caller's responsibility.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ResolvedCoreConfig, TypographyCoreOptions } from './types';
|
|
2
|
+
export interface PluginFactory<TOptions extends TypographyCoreOptions, TTree> {
|
|
3
|
+
defaultOptions?: Partial<TOptions>;
|
|
4
|
+
createHandler: (config: ResolvedCoreConfig & TOptions) => (tree: TTree) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function createTypographyPlugin<TOptions extends TypographyCoreOptions, TTree>(factory: PluginFactory<TOptions, TTree>): (options?: Partial<TOptions>) => (tree: TTree) => void;
|
|
7
|
+
//# sourceMappingURL=factory.d.ts.map
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
applyNodeRules: () => applyNodeRules,
|
|
24
|
+
applyRules: () => applyRules,
|
|
25
|
+
createTypographyPlugin: () => createTypographyPlugin,
|
|
26
|
+
getFrontmatterLocale: () => getFrontmatterLocale,
|
|
27
|
+
initRules: () => initRules,
|
|
28
|
+
processElement: () => processElement,
|
|
29
|
+
warning: () => warning
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
var import_typography_rules2 = require("@nkardaz/typography-rules");
|
|
33
|
+
var import_helpers = require("@nkardaz/typography-rules/helpers");
|
|
34
|
+
|
|
35
|
+
// src/factory.ts
|
|
36
|
+
var import_typography_rules = require("@nkardaz/typography-rules");
|
|
37
|
+
function createTypographyPlugin(factory) {
|
|
38
|
+
return function plugin(options = {}) {
|
|
39
|
+
const resolved = {
|
|
40
|
+
initTypographyRules: true,
|
|
41
|
+
initMarkupRules: false,
|
|
42
|
+
logs: false,
|
|
43
|
+
locale: "en",
|
|
44
|
+
plugins: [],
|
|
45
|
+
...factory.defaultOptions,
|
|
46
|
+
...options
|
|
47
|
+
};
|
|
48
|
+
resolved.locale = import_typography_rules.ALIAS.resolve(resolved.locale) ?? resolved.locale;
|
|
49
|
+
initRules(resolved);
|
|
50
|
+
return factory.createHandler(resolved);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/index.ts
|
|
55
|
+
function initRules(config) {
|
|
56
|
+
if (config.initTypographyRules) {
|
|
57
|
+
(0, import_typography_rules2.initTypographyRules)();
|
|
58
|
+
}
|
|
59
|
+
if (config.initMarkupRules) {
|
|
60
|
+
(0, import_typography_rules2.initMarkupRules)();
|
|
61
|
+
}
|
|
62
|
+
config.plugins?.forEach((plugin) => plugin()());
|
|
63
|
+
}
|
|
64
|
+
function warning(message, showLogs) {
|
|
65
|
+
if (showLogs) {
|
|
66
|
+
console.warn(`[@nkardaz/typography] ${message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function getFrontmatterLocale(data) {
|
|
70
|
+
if (!data) return void 0;
|
|
71
|
+
return (typeof data["locale"] === "string" ? data["locale"] : void 0) ?? (typeof data["lang"] === "string" ? data["lang"] : void 0) ?? (typeof data["language"] === "string" ? data["language"] : void 0);
|
|
72
|
+
}
|
|
73
|
+
function applyRules(text, locale, config) {
|
|
74
|
+
const key = import_typography_rules2.ALIAS.resolve(locale) ?? locale;
|
|
75
|
+
const rules = (0, import_typography_rules2.getWeightedRules)(key);
|
|
76
|
+
if (rules.length === 0) return text;
|
|
77
|
+
const [initialProtectedValue, protectedMatches] = (0, import_helpers.protect)(text, key);
|
|
78
|
+
let value = initialProtectedValue;
|
|
79
|
+
for (const item of rules) {
|
|
80
|
+
if (!item?.kind) {
|
|
81
|
+
if (config.logs) console.warn("[@nkardaz/typography] Skipping invalid rule:", item);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (item.label && (0, import_typography_rules2.isRuleDisabled)(item.label)) continue;
|
|
85
|
+
if (item.kind === "node") continue;
|
|
86
|
+
try {
|
|
87
|
+
switch (item.kind) {
|
|
88
|
+
case "function": {
|
|
89
|
+
const funcItem = item;
|
|
90
|
+
const result = funcItem.rule(value, ...funcItem.args ?? []);
|
|
91
|
+
if (typeof result === "string") value = result;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "transform": {
|
|
95
|
+
const transformItem = item;
|
|
96
|
+
value = value.replace(transformItem.rule, (match, ...groups) => {
|
|
97
|
+
const regexArray = [match, ...groups];
|
|
98
|
+
return transformItem.transform(regexArray);
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "replace": {
|
|
103
|
+
const replaceItem = item;
|
|
104
|
+
value = value.replace(replaceItem.rule, replaceItem.replacement);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (config.logs)
|
|
110
|
+
console.warn("[@nkardaz/typography] Rule threw an error, skipping:", item, err);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return (0, import_helpers.unprotect)(value, protectedMatches);
|
|
114
|
+
}
|
|
115
|
+
function applyNodeRules(textNodes, parent, locale, config) {
|
|
116
|
+
const key = import_typography_rules2.ALIAS.resolve(locale) ?? locale;
|
|
117
|
+
const rules = (0, import_typography_rules2.getWeightedRules)(key).filter(
|
|
118
|
+
(r) => r.kind === "node" || r.kind === "function"
|
|
119
|
+
);
|
|
120
|
+
if (rules.length === 0) return;
|
|
121
|
+
for (const textNode of textNodes) {
|
|
122
|
+
let current = [textNode];
|
|
123
|
+
for (const rule of rules) {
|
|
124
|
+
if (rule.label && (0, import_typography_rules2.isRuleDisabled)(rule.label)) continue;
|
|
125
|
+
const next = [];
|
|
126
|
+
for (const node of current) {
|
|
127
|
+
if (node.type !== "text") {
|
|
128
|
+
next.push(node);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const textValue = node.value;
|
|
132
|
+
let nodeList;
|
|
133
|
+
try {
|
|
134
|
+
if (rule.kind === "node") {
|
|
135
|
+
const nodeRule = rule;
|
|
136
|
+
nodeList = (0, import_typography_rules2.htmlNode)(textValue, {
|
|
137
|
+
expression: nodeRule.rule,
|
|
138
|
+
nodes: nodeRule.nodes
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
const funcRule = rule;
|
|
142
|
+
const result = funcRule.rule(textValue, ...funcRule.args ?? []);
|
|
143
|
+
if (typeof result === "string" || !Array.isArray(result)) {
|
|
144
|
+
next.push(node);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
nodeList = result;
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
if (config.logs)
|
|
151
|
+
console.warn("[@nkardaz/typography] Node rule threw an error, skipping:", rule, err);
|
|
152
|
+
next.push(node);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (nodeList.length === 1 && nodeList[0].type === "text" && nodeList[0].value === textValue) {
|
|
156
|
+
next.push(node);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
for (const n of nodeList) next.push(n);
|
|
160
|
+
}
|
|
161
|
+
current = next;
|
|
162
|
+
}
|
|
163
|
+
if (current.length === 1 && current[0] === textNode) continue;
|
|
164
|
+
const index = parent.children.indexOf(textNode);
|
|
165
|
+
if (index !== -1) {
|
|
166
|
+
parent.children.splice(index, 1, ...current);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function processElement(element, locale, config) {
|
|
171
|
+
const lang = element.attrs?.["lang"] ?? element.attrs?.["language"] ?? element.attrs?.["locale"] ?? locale;
|
|
172
|
+
const textNodes = element.children.filter((c) => c.type === "text");
|
|
173
|
+
if (textNodes.length > 0) {
|
|
174
|
+
const combined = (0, import_helpers.joinNodes)(textNodes);
|
|
175
|
+
const transformed = applyRules(combined, lang, config);
|
|
176
|
+
(0, import_helpers.splitNodes)(transformed, textNodes);
|
|
177
|
+
applyNodeRules(textNodes, element, lang, config);
|
|
178
|
+
}
|
|
179
|
+
for (const child of element.children) {
|
|
180
|
+
if (child.type !== "text") {
|
|
181
|
+
processElement(child, lang, config);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type ElementNode, type TextNode } from '@nkardaz/typography-rules';
|
|
2
|
+
import type { ResolvedCoreConfig } from './types';
|
|
3
|
+
export * from './factory';
|
|
4
|
+
export type * from './types';
|
|
5
|
+
export declare function initRules(config: ResolvedCoreConfig): void;
|
|
6
|
+
export declare function warning(message: string, showLogs: boolean): void;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve locale from a parsed frontmatter data object.
|
|
9
|
+
* Checks keys in order: `locale` → `lang` → `language`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getFrontmatterLocale(data: Record<string, unknown> | null): string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Apply all string-phase rules (replace / transform / function→string) to `text`.
|
|
14
|
+
* Protected regions (URLs, emails, code spans, …) are shielded before rules run.
|
|
15
|
+
*
|
|
16
|
+
* This is the only processing function that is truly framework-agnostic:
|
|
17
|
+
* it operates purely on strings and has no knowledge of any AST.
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyRules(text: string, locale: string, config: Pick<ResolvedCoreConfig, 'logs'>): string;
|
|
20
|
+
/**
|
|
21
|
+
* Apply node-phase rules (kind === 'node' | 'function'→Node[]) to a flat list
|
|
22
|
+
* of text nodes belonging to a single parent element, mutating the parent's
|
|
23
|
+
* children array in-place.
|
|
24
|
+
*
|
|
25
|
+
* This function operates on one level of the tree only — it does not recurse.
|
|
26
|
+
* Traversal and locale switching across element boundaries is the caller's
|
|
27
|
+
* responsibility (see `processElement`).
|
|
28
|
+
*
|
|
29
|
+
* Must be called *after* `applyRules` + `joinNodes`/`splitNodes` have already
|
|
30
|
+
* handled string-phase processing on the same text nodes.
|
|
31
|
+
*
|
|
32
|
+
* @param textNodes - Direct text-node children of `parent`, pre-filtered by the caller
|
|
33
|
+
* @param parent - The element whose `children` array will be mutated on expansion
|
|
34
|
+
* @param locale - Active locale for rule selection
|
|
35
|
+
* @param config - Core config; only `logs` is used
|
|
36
|
+
*/
|
|
37
|
+
export declare function applyNodeRules(textNodes: TextNode[], parent: ElementNode, locale: string, config: Pick<ResolvedCoreConfig, 'logs'>): void;
|
|
38
|
+
/**
|
|
39
|
+
* Recursively processes an element and all its descendants, applying both
|
|
40
|
+
* string-phase and node-phase typography rules.
|
|
41
|
+
*
|
|
42
|
+
* Mirrors the role of `processNode` in the remark plugin: it owns the locale
|
|
43
|
+
* stack, handles `lang`/`language`/`locale` attribute switching on child
|
|
44
|
+
* elements, and coordinates the two-phase pipeline for each level:
|
|
45
|
+
* 1. `joinNodes` → `applyRules` → `splitNodes` (string phase)
|
|
46
|
+
* 2. `applyNodeRules` (node phase)
|
|
47
|
+
*
|
|
48
|
+
* @param element - The element to process; its `children` array is mutated in-place
|
|
49
|
+
* @param locale - Inherited locale from the parent scope
|
|
50
|
+
* @param config - Core config; only `logs` is used
|
|
51
|
+
*/
|
|
52
|
+
export declare function processElement(element: ElementNode, locale: string, config: Pick<ResolvedCoreConfig, 'logs'>): void;
|
|
53
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
getWeightedRules,
|
|
4
|
+
initTypographyRules,
|
|
5
|
+
initMarkupRules,
|
|
6
|
+
isRuleDisabled,
|
|
7
|
+
htmlNode,
|
|
8
|
+
ALIAS as ALIAS2
|
|
9
|
+
} from "@nkardaz/typography-rules";
|
|
10
|
+
import { joinNodes, protect, splitNodes, unprotect } from "@nkardaz/typography-rules/helpers";
|
|
11
|
+
|
|
12
|
+
// src/factory.ts
|
|
13
|
+
import { ALIAS } from "@nkardaz/typography-rules";
|
|
14
|
+
function createTypographyPlugin(factory) {
|
|
15
|
+
return function plugin(options = {}) {
|
|
16
|
+
const resolved = {
|
|
17
|
+
initTypographyRules: true,
|
|
18
|
+
initMarkupRules: false,
|
|
19
|
+
logs: false,
|
|
20
|
+
locale: "en",
|
|
21
|
+
plugins: [],
|
|
22
|
+
...factory.defaultOptions,
|
|
23
|
+
...options
|
|
24
|
+
};
|
|
25
|
+
resolved.locale = ALIAS.resolve(resolved.locale) ?? resolved.locale;
|
|
26
|
+
initRules(resolved);
|
|
27
|
+
return factory.createHandler(resolved);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
function initRules(config) {
|
|
33
|
+
if (config.initTypographyRules) {
|
|
34
|
+
initTypographyRules();
|
|
35
|
+
}
|
|
36
|
+
if (config.initMarkupRules) {
|
|
37
|
+
initMarkupRules();
|
|
38
|
+
}
|
|
39
|
+
config.plugins?.forEach((plugin) => plugin()());
|
|
40
|
+
}
|
|
41
|
+
function warning(message, showLogs) {
|
|
42
|
+
if (showLogs) {
|
|
43
|
+
console.warn(`[@nkardaz/typography] ${message}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function getFrontmatterLocale(data) {
|
|
47
|
+
if (!data) return void 0;
|
|
48
|
+
return (typeof data["locale"] === "string" ? data["locale"] : void 0) ?? (typeof data["lang"] === "string" ? data["lang"] : void 0) ?? (typeof data["language"] === "string" ? data["language"] : void 0);
|
|
49
|
+
}
|
|
50
|
+
function applyRules(text, locale, config) {
|
|
51
|
+
const key = ALIAS2.resolve(locale) ?? locale;
|
|
52
|
+
const rules = getWeightedRules(key);
|
|
53
|
+
if (rules.length === 0) return text;
|
|
54
|
+
const [initialProtectedValue, protectedMatches] = protect(text, key);
|
|
55
|
+
let value = initialProtectedValue;
|
|
56
|
+
for (const item of rules) {
|
|
57
|
+
if (!item?.kind) {
|
|
58
|
+
if (config.logs) console.warn("[@nkardaz/typography] Skipping invalid rule:", item);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (item.label && isRuleDisabled(item.label)) continue;
|
|
62
|
+
if (item.kind === "node") continue;
|
|
63
|
+
try {
|
|
64
|
+
switch (item.kind) {
|
|
65
|
+
case "function": {
|
|
66
|
+
const funcItem = item;
|
|
67
|
+
const result = funcItem.rule(value, ...funcItem.args ?? []);
|
|
68
|
+
if (typeof result === "string") value = result;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "transform": {
|
|
72
|
+
const transformItem = item;
|
|
73
|
+
value = value.replace(transformItem.rule, (match, ...groups) => {
|
|
74
|
+
const regexArray = [match, ...groups];
|
|
75
|
+
return transformItem.transform(regexArray);
|
|
76
|
+
});
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case "replace": {
|
|
80
|
+
const replaceItem = item;
|
|
81
|
+
value = value.replace(replaceItem.rule, replaceItem.replacement);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (config.logs)
|
|
87
|
+
console.warn("[@nkardaz/typography] Rule threw an error, skipping:", item, err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return unprotect(value, protectedMatches);
|
|
91
|
+
}
|
|
92
|
+
function applyNodeRules(textNodes, parent, locale, config) {
|
|
93
|
+
const key = ALIAS2.resolve(locale) ?? locale;
|
|
94
|
+
const rules = getWeightedRules(key).filter(
|
|
95
|
+
(r) => r.kind === "node" || r.kind === "function"
|
|
96
|
+
);
|
|
97
|
+
if (rules.length === 0) return;
|
|
98
|
+
for (const textNode of textNodes) {
|
|
99
|
+
let current = [textNode];
|
|
100
|
+
for (const rule of rules) {
|
|
101
|
+
if (rule.label && isRuleDisabled(rule.label)) continue;
|
|
102
|
+
const next = [];
|
|
103
|
+
for (const node of current) {
|
|
104
|
+
if (node.type !== "text") {
|
|
105
|
+
next.push(node);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const textValue = node.value;
|
|
109
|
+
let nodeList;
|
|
110
|
+
try {
|
|
111
|
+
if (rule.kind === "node") {
|
|
112
|
+
const nodeRule = rule;
|
|
113
|
+
nodeList = htmlNode(textValue, {
|
|
114
|
+
expression: nodeRule.rule,
|
|
115
|
+
nodes: nodeRule.nodes
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
const funcRule = rule;
|
|
119
|
+
const result = funcRule.rule(textValue, ...funcRule.args ?? []);
|
|
120
|
+
if (typeof result === "string" || !Array.isArray(result)) {
|
|
121
|
+
next.push(node);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
nodeList = result;
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (config.logs)
|
|
128
|
+
console.warn("[@nkardaz/typography] Node rule threw an error, skipping:", rule, err);
|
|
129
|
+
next.push(node);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (nodeList.length === 1 && nodeList[0].type === "text" && nodeList[0].value === textValue) {
|
|
133
|
+
next.push(node);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
for (const n of nodeList) next.push(n);
|
|
137
|
+
}
|
|
138
|
+
current = next;
|
|
139
|
+
}
|
|
140
|
+
if (current.length === 1 && current[0] === textNode) continue;
|
|
141
|
+
const index = parent.children.indexOf(textNode);
|
|
142
|
+
if (index !== -1) {
|
|
143
|
+
parent.children.splice(index, 1, ...current);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function processElement(element, locale, config) {
|
|
148
|
+
const lang = element.attrs?.["lang"] ?? element.attrs?.["language"] ?? element.attrs?.["locale"] ?? locale;
|
|
149
|
+
const textNodes = element.children.filter((c) => c.type === "text");
|
|
150
|
+
if (textNodes.length > 0) {
|
|
151
|
+
const combined = joinNodes(textNodes);
|
|
152
|
+
const transformed = applyRules(combined, lang, config);
|
|
153
|
+
splitNodes(transformed, textNodes);
|
|
154
|
+
applyNodeRules(textNodes, element, lang, config);
|
|
155
|
+
}
|
|
156
|
+
for (const child of element.children) {
|
|
157
|
+
if (child.type !== "text") {
|
|
158
|
+
processElement(child, lang, config);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export {
|
|
163
|
+
applyNodeRules,
|
|
164
|
+
applyRules,
|
|
165
|
+
createTypographyPlugin,
|
|
166
|
+
getFrontmatterLocale,
|
|
167
|
+
initRules,
|
|
168
|
+
processElement,
|
|
169
|
+
warning
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=index.mjs.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface TypographyCoreOptions {
|
|
2
|
+
initTypographyRules?: boolean;
|
|
3
|
+
initMarkupRules?: boolean;
|
|
4
|
+
locale?: string;
|
|
5
|
+
plugins?: (() => () => void)[];
|
|
6
|
+
logs?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export type ResolvedCoreConfig = Required<TypographyCoreOptions>;
|
|
9
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nkardaz/typography-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Your package description here",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Yalla Nkardaz",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"typography",
|
|
9
|
+
"typographic",
|
|
10
|
+
"text-formatting",
|
|
11
|
+
"typesetting",
|
|
12
|
+
"english",
|
|
13
|
+
"russian",
|
|
14
|
+
"text"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/DemerNkardaz/typography-core.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/DemerNkardaz/typography-core#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/DemerNkardaz/typography-core/issues"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/index.cjs",
|
|
26
|
+
"module": "./dist/index.mjs",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.mjs",
|
|
32
|
+
"require": "./dist/index.cjs"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"bundleSizeLimit": 20480,
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=24.0.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build:js": "node esbuild.config.mjs",
|
|
42
|
+
"build:types": "tsc --project tsconfig.build.json --emitDeclarationOnly",
|
|
43
|
+
"build": "npm run build:js && npm run build:types",
|
|
44
|
+
"dev": "esbuild --watch",
|
|
45
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
46
|
+
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
47
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
|
|
48
|
+
"type-check": "tsc --noEmit",
|
|
49
|
+
"test": "vitest --run",
|
|
50
|
+
"test:coverage": "vitest --run --coverage",
|
|
51
|
+
"prepublishOnly": "npm run build && npm run lint && npm run type-check",
|
|
52
|
+
"knip": "knip"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@eslint/js": "^10.0.1",
|
|
56
|
+
"@types/node": "^25.9.1",
|
|
57
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
58
|
+
"esbuild": "^0.28.0",
|
|
59
|
+
"eslint": "^10.4.1",
|
|
60
|
+
"eslint-config-prettier": "^10.1.8",
|
|
61
|
+
"knip": "^6.15.0",
|
|
62
|
+
"prettier": "^3.0.0",
|
|
63
|
+
"typescript": "^6.0.3",
|
|
64
|
+
"typescript-eslint": "^8.60.1",
|
|
65
|
+
"vite-tsconfig-paths": "^6.1.1",
|
|
66
|
+
"vitest": "^4.1.8"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@nkardaz/typography-rules": "1.0.0"
|
|
70
|
+
}
|
|
71
|
+
}
|