@0dep/toc 1.0.1 → 2.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/index.js CHANGED
@@ -1,64 +1,67 @@
1
1
  export const TOC_START = '<!-- toc -->';
2
2
  export const TOC_END = '<!-- /toc -->';
3
3
  const DEFAULT_SUMMARY = 'Table of contents';
4
- const KNOWN_OPTIONS = ['collapsible', 'collapsed'];
4
+ const KNOWN_OPTIONS = ['collapsible', 'collapsed', 'levels'];
5
+ const LEVELS = /^(?:([1-6])|([1-6])-([1-6])|([1-6])-|-([1-6]))$/;
5
6
  const NUL = String.fromCharCode(0);
7
+ /** @type {Map<string, ReturnType<typeof analyse>>} */
8
+ const cache = new Map();
9
+ const LINK = /\[([^\]]*)\]\(#([^)\s]+)/g;
6
10
 
7
11
  /**
8
- * Return the markdown with the toc between every `<!-- toc -->` and `<!-- /toc -->` pair regenerated. Each
9
- * pair lists every heading below its own start marker. A pair is left alone when it is unbalanced, has a
10
- * problem on its start marker, or has no headings below it, and nothing outside the pairs is ever touched.
11
- * The start marker line is kept as written, options included.
12
+ * The markdown with the toc between every `<!-- toc -->` and `<!-- /toc -->` pair regenerated.
12
13
  * @param {string} source
13
14
  * @returns {string}
14
15
  */
15
16
  export function buildToc(source) {
16
- const { lines, eol } = splitLines(source);
17
- const { headlines, markers } = scan(lines);
17
+ const { bom, lines, eol, headlines, markers } = analyse(source);
18
18
  /** @type {string[]} */
19
19
  const out = [];
20
20
  let cursor = 0;
21
21
  for (const { start, end, options, problem } of markers) {
22
22
  if (start === -1 || end === -1 || problem) continue;
23
- const listed = headlines.filter((h) => h.line > start);
23
+ const listed = headingsBelow(headlines, start, options);
24
24
  if (listed.length === 0) continue;
25
25
  out.push(...lines.slice(cursor, start), renderBlock(listed, lines[start], options, eol));
26
26
  cursor = end + 1;
27
27
  }
28
28
  out.push(...lines.slice(cursor));
29
- return out.join(eol);
29
+ return bom + out.join(eol);
30
30
  }
31
31
 
32
32
  /**
33
- * Return the toc block, markers included, for the headings below `fromLine`, by default every heading so the
34
- * block can be pasted into a document without markers. Returns an empty string when there is nothing to list.
33
+ * The toc block, markers included, for the headings below `fromLine`, empty when there is nothing to list.
35
34
  * @param {string} source
36
- * @param {number} [fromLine] zero based line number, typically the start marker's
37
- * @param {TocOptions} [options] rendered into the start marker, e.g. `{ collapsed: 'Contents' }`
35
+ * @param {number} [fromLine] zero based
36
+ * @param {TocOptions} [options]
38
37
  * @returns {string}
39
38
  */
40
39
  export function renderToc(source, fromLine = -1, options = {}) {
41
- const { lines, eol } = splitLines(source);
42
- const listed = scan(lines).headlines.filter((h) => h.line > fromLine);
40
+ const { eol, headlines } = analyse(source);
41
+ const listed = headingsBelow(headlines, fromLine, options);
43
42
  return listed.length === 0 ? '' : renderBlock(listed, formatMarker(options), options, eol);
44
43
  }
45
44
 
46
45
  /**
47
- * Every marker pair in document order as zero based line numbers, outside fenced code blocks. A start marker
48
- * pairs with the first end marker after it. A missing side is -1: a start marker without an end marker, or an
49
- * end marker with no open start marker before it. `options` holds the recognised options written on the start
50
- * marker and `problem`, only present when there is one, says why the marker cannot be used.
46
+ * Every marker pair in document order, shared with later calls for the same source.
51
47
  * @param {string} source
52
48
  * @returns {Marker[]}
53
49
  */
54
50
  export function findMarkers(source) {
55
- return scan(splitLines(source).lines).markers;
51
+ return analyse(source).markers;
56
52
  }
57
53
 
58
54
  /**
59
- * Slug a heading's rendered text the way github-slugger does: lowercase, drop everything that is not a
60
- * letter, number, mark, space, hyphen or underscore, then turn each space into a hyphen. Nothing is trimmed
61
- * or collapsed.
55
+ * Every link to an anchor in the document, in order, shared with later calls for the same source.
56
+ * @param {string} source
57
+ * @returns {Anchor[]}
58
+ */
59
+ export function findAnchors(source) {
60
+ return analyse(source).anchors;
61
+ }
62
+
63
+ /**
64
+ * The GitHub anchor slug of a heading's rendered text.
62
65
  * @param {string} text
63
66
  * @returns {string}
64
67
  */
@@ -70,7 +73,7 @@ export function slugify(text) {
70
73
  }
71
74
 
72
75
  /**
73
- * Reduce a heading's inline markdown to the text GitHub renders and slugs.
76
+ * The text GitHub renders for a heading's inline markdown.
74
77
  * @param {string} markdown
75
78
  * @returns {string}
76
79
  */
@@ -85,33 +88,51 @@ export function headingText(markdown) {
85
88
  }
86
89
 
87
90
  /**
88
- * Split the source into lines and remember its line ending, CRLF when the source has any, so the toc is
89
- * written the way the rest of the file is and a Windows authored file does not end up with mixed endings.
91
+ * The lines and the scan of the source.
90
92
  * @param {string} source
91
- * @returns {{ lines: string[], eol: string }}
93
+ * @returns {{ bom: string, lines: string[], eol: string, headlines: Headline[], markers: Marker[], anchors: Anchor[] }}
94
+ */
95
+ function analyse(source) {
96
+ let result = cache.get(source);
97
+ if (!result) {
98
+ const { bom, lines, eol } = splitLines(source);
99
+ result = { bom, lines, eol, ...scan(lines) };
100
+ cache.clear();
101
+ cache.set(source, result);
102
+ }
103
+ return result;
104
+ }
105
+
106
+ /**
107
+ * The lines of the source, its byte order mark if any, and its line ending.
108
+ * @param {string} source
109
+ * @returns {{ bom: string, lines: string[], eol: string }}
92
110
  */
93
111
  function splitLines(source) {
94
- return { lines: source.split(/\r?\n/), eol: source.includes('\r\n') ? '\r\n' : '\n' };
112
+ const bom = source.startsWith('\uFEFF') ? '\uFEFF' : '';
113
+ return { bom, lines: source.slice(bom.length).split(/\r?\n/), eol: source.includes('\r\n') ? '\r\n' : '\n' };
95
114
  }
96
115
 
97
116
  /**
98
- * Scan the lines for every marker pair and every ATX and setext heading, all outside fenced code blocks. A
99
- * marker is a line holding nothing but the comment, indented at most three spaces like a heading, since four
100
- * make an indented code block. A start marker, `<!-- toc -->` with optional options before the closing `-->`,
101
- * opens a pair that the first end marker after it closes; a start marker inside an open pair is content and an end marker outside a pair is
102
- * reported with start -1.
117
+ * The headings, marker pairs and anchor links of the lines.
103
118
  * @param {string[]} lines
104
- * @returns {{ headlines: Array<{ line: number, level: number, markdown: string }>, markers: Marker[] }}
119
+ * @returns {{ headlines: Headline[], markers: Marker[], anchors: Anchor[] }}
105
120
  */
106
121
  function scan(lines) {
107
- /** @type {Array<{ line: number, level: number, markdown: string }>} */
122
+ /** @type {Headline[]} */
108
123
  const headlines = [];
109
124
  /** @type {Marker[]} */
110
125
  const markers = [];
126
+ /** @type {Array<{ line: number, text: string, anchor: string }>} */
127
+ const links = [];
128
+ /** @type {Set<string>} */
129
+ const ids = new Set();
111
130
  /** @type {{ char: string, length: number } | null} */
112
131
  let fence = null;
113
132
  /** @type {Marker | null} */
114
133
  let open = null;
134
+ let paragraph = false;
135
+ let comment = false;
115
136
 
116
137
  for (const [i, line] of lines.entries()) {
117
138
  const fenceMatch = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
@@ -123,8 +144,32 @@ function scan(lines) {
123
144
  }
124
145
  if (fenceMatch && !(fenceMatch[1][0] === '`' && fenceMatch[2].includes('`'))) {
125
146
  fence = { char: fenceMatch[1][0], length: fenceMatch[1].length };
147
+ paragraph = false;
126
148
  continue;
127
149
  }
150
+ if (!paragraph && /^(?: {4}|\t)/.test(line)) continue;
151
+ paragraph = line.trim() !== '';
152
+
153
+ const { text: inline, restore } = protect(line);
154
+ let visible = inline;
155
+ if (comment) {
156
+ const close = visible.indexOf('-->');
157
+ if (close === -1) continue;
158
+ visible = visible.slice(close + 3);
159
+ comment = false;
160
+ }
161
+ visible = visible.replace(/<!--[\s\S]*?-->/g, '');
162
+ const start = visible.indexOf('<!--');
163
+ if (start !== -1) {
164
+ visible = visible.slice(0, start);
165
+ comment = true;
166
+ }
167
+ for (const [, tag, attributes] of visible.matchAll(/<([a-zA-Z][a-zA-Z0-9-]*)\b([^>]*)>/g)) {
168
+ for (const [, name, quoted, single] of attributes.matchAll(/(?:^|\s)(id|name)=(?:"([^"]*)"|'([^']*)')/g)) {
169
+ if (name === 'id' || tag.toLowerCase() === 'a') ids.add(quoted ?? single);
170
+ }
171
+ }
172
+ for (const [, text, anchor] of visible.matchAll(LINK)) links.push({ line: i, text: restore(text), anchor: restore(anchor) });
128
173
 
129
174
  const startMatch = /^ {0,3}<!--\s*toc(?:\s+(.*?))?\s*-->\s*$/.exec(line);
130
175
  if (startMatch) {
@@ -140,42 +185,102 @@ function scan(lines) {
140
185
 
141
186
  const atx = /^ {0,3}(#{1,6})\s+(.+?)\s*$/.exec(line);
142
187
  if (atx) {
143
- headlines.push({ line: i, level: atx[1].length, markdown: atx[2] });
188
+ headlines.push({ line: i, level: atx[1].length, markdown: atx[2], slug: '' });
144
189
  continue;
145
190
  }
146
191
 
147
192
  const setext = /^ {0,3}(=+|-+)\s*$/.exec(line);
148
193
  if (setext && i > 0 && isParagraphText(lines[i - 1])) {
149
- headlines.push({ line: i - 1, level: setext[1][0] === '=' ? 1 : 2, markdown: lines[i - 1].trim() });
194
+ headlines.push({ line: i - 1, level: setext[1][0] === '=' ? 1 : 2, markdown: lines[i - 1].trim(), slug: '' });
150
195
  }
151
196
  }
152
- return { headlines, markers };
197
+ assignSlugs(headlines);
198
+ return { headlines, markers, anchors: resolveAnchors(links, headlines, ids) };
153
199
  }
154
200
 
155
201
  /**
156
- * The toc block for the given headings, wrapped in the given start marker line and the end marker. Indentation
157
- * is relative to the shallowest level listed so far, so the list never starts indented, which markdown would
158
- * render flat anyway, and duplicate slugs get github style `-1`, `-2` suffixes. With
159
- * `collapsible` or `collapsed` the list goes inside a details element, open or closed to start with, blank
160
- * lines around it so it renders as markdown, and any other attributes from the marker on the summary element.
161
- * @param {Array<{ level: number, markdown: string }>} headlines
162
- * @param {string} startLine the start marker as written in the document
163
- * @param {TocOptions} options
164
- * @param {string} [eol] line ending, LF by default
202
+ * @param {Array<{ line: number, text: string, anchor: string }>} links
203
+ * @param {Headline[]} headlines
204
+ * @param {Set<string>} ids
205
+ * @returns {Anchor[]}
165
206
  */
166
- function renderBlock(headlines, startLine, options, eol = '\n') {
207
+ function resolveAnchors(links, headlines, ids) {
208
+ const targets = new Set([...headlines.map((h) => h.slug), ...ids]);
209
+ return links.map(({ line, text, anchor }) => {
210
+ const decoded = decodeAnchor(anchor);
211
+ if (targets.has(anchor) || targets.has(decoded)) return { line, text, anchor, valid: true };
212
+ const candidates = new Set([slugify(headingText(decoded)), slugify(headingText(text))].filter((c) => targets.has(c)));
213
+ if (candidates.size !== 1) return { line, text, anchor, valid: false };
214
+ return { line, text, anchor, valid: false, suggestion: [...candidates][0] };
215
+ });
216
+ }
217
+
218
+ /** @param {string} anchor */
219
+ function decodeAnchor(anchor) {
220
+ try {
221
+ return decodeURIComponent(anchor);
222
+ } catch {
223
+ return anchor;
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Set the slug of every heading.
229
+ * @param {Headline[]} headlines
230
+ */
231
+ function assignSlugs(headlines) {
167
232
  /** @type {Record<string, number>} */
168
233
  const occurrences = {};
169
- let minLevel = Infinity;
170
- const tocLines = headlines.map(({ level, markdown }) => {
171
- minLevel = Math.min(minLevel, level);
172
- const base = slugify(headingText(markdown));
234
+ for (const headline of headlines) {
235
+ const base = slugify(headingText(headline.markdown));
173
236
  let slug = base;
174
237
  while (Object.hasOwn(occurrences, slug)) {
175
238
  occurrences[base]++;
176
239
  slug = `${base}-${occurrences[base]}`;
177
240
  }
178
241
  occurrences[slug] = 0;
242
+ headline.slug = slug;
243
+ }
244
+ }
245
+
246
+ /**
247
+ * The headings after `fromLine` within the levels the options allow.
248
+ * @param {Headline[]} headlines
249
+ * @param {number} fromLine
250
+ * @param {TocOptions} options
251
+ * @returns {Headline[]}
252
+ */
253
+ function headingsBelow(headlines, fromLine, options) {
254
+ const [min, max] = levelRange(options.levels) ?? [1, 6];
255
+ return headlines.filter((h) => h.line > fromLine && h.level >= min && h.level <= max);
256
+ }
257
+
258
+ /**
259
+ * The lowest and highest heading level a levels option allows, every level when absent, undefined when broken.
260
+ * @param {string} [levels]
261
+ * @returns {[number, number] | undefined}
262
+ */
263
+ function levelRange(levels) {
264
+ if (levels === undefined) return [1, 6];
265
+ const match = LEVELS.exec(levels);
266
+ if (!match) return undefined;
267
+ const [, only, from, to, fromOpen, toOpen] = match;
268
+ const min = Number(only ?? from ?? fromOpen ?? 1);
269
+ const max = Number(only ?? to ?? toOpen ?? 6);
270
+ return min <= max ? [min, max] : undefined;
271
+ }
272
+
273
+ /**
274
+ * The toc block for the headings, between the start marker line and the end marker.
275
+ * @param {Headline[]} headlines
276
+ * @param {string} startLine
277
+ * @param {TocOptions} options
278
+ * @param {string} [eol]
279
+ */
280
+ function renderBlock(headlines, startLine, options, eol = '\n') {
281
+ let minLevel = Infinity;
282
+ const tocLines = headlines.map(({ level, markdown, slug }) => {
283
+ minLevel = Math.min(minLevel, level);
179
284
  return `${' '.repeat(level - minLevel)}- [${headingLabel(markdown)}](#${slug})`;
180
285
  });
181
286
  const details = options.collapsible ?? options.collapsed;
@@ -189,12 +294,9 @@ function renderBlock(headlines, startLine, options, eol = '\n') {
189
294
  }
190
295
 
191
296
  /**
192
- * Parse what is written on a start marker: the options as bare names (`collapsed`) or quoted values
193
- * (`collapsed="Contents"`), and any other well-formed `name="value"` as an attribute for the summary element.
194
- * Anything else, bare names that are not options, unquoted values, malformed names, is a problem, as is a
195
- * combination that makes no sense. A marker with a problem is never used.
297
+ * The options written on a start marker, with a problem when they cannot be used and a warning when one is ignored.
196
298
  * @param {string | undefined} text
197
- * @returns {{ options: TocOptions, problem?: string }}
299
+ * @returns {{ options: TocOptions, problem?: string, warning?: string }}
198
300
  */
199
301
  function parseOptions(text) {
200
302
  /** @type {TocOptions} */
@@ -204,24 +306,29 @@ function parseOptions(text) {
204
306
  /** @type {string[]} */
205
307
  const unknown = [];
206
308
  for (const [token, name, value] of (text ?? '').matchAll(/([^\s="]+)(?:="([^"]*)")?(?=\s|$)|\S+/g)) {
207
- if (name && KNOWN_OPTIONS.includes(name)) options[/** @type {'collapsible' | 'collapsed'} */ (name)] = value ?? true;
309
+ if (name === 'levels') options.levels = value ?? '';
310
+ else if (name && KNOWN_OPTIONS.includes(name)) options[/** @type {'collapsible' | 'collapsed'} */ (name)] = value ?? true;
208
311
  else if (name && value !== undefined && /^[a-zA-Z][\w:.-]*$/.test(name)) attributes[name] = value;
209
312
  else unknown.push(token);
210
313
  }
211
314
  const names = Object.keys(attributes);
212
315
  if (names.length) options.attributes = attributes;
213
- if (unknown.length) return { options, problem: `unknown TOC option ${unknown.join(' ')}` };
214
- if (options.collapsible !== undefined && options.collapsed !== undefined) {
215
- return { options, problem: 'TOC options collapsible and collapsed exclude each other' };
316
+ /** @type {{ options: TocOptions, problem?: string, warning?: string }} */
317
+ const parsed = { options };
318
+ if (options.levels !== undefined && !levelRange(options.levels)) {
319
+ parsed.warning = `TOC option levels "${options.levels}" is not a level or a range like 2-3, ignored`;
216
320
  }
217
- if (names.length && options.collapsible === undefined && options.collapsed === undefined) {
218
- return { options, problem: `TOC attributes ${names.join(', ')} need collapsible or collapsed` };
321
+ if (unknown.length) parsed.problem = `unknown TOC option ${unknown.join(' ')}`;
322
+ else if (options.collapsible !== undefined && options.collapsed !== undefined) {
323
+ parsed.problem = 'TOC options collapsible and collapsed exclude each other';
324
+ } else if (names.length && options.collapsible === undefined && options.collapsed === undefined) {
325
+ parsed.problem = `TOC attributes ${names.join(', ')} need collapsible or collapsed`;
219
326
  }
220
- return { options };
327
+ return parsed;
221
328
  }
222
329
 
223
330
  /**
224
- * The start marker line for the given options, the inverse of `parseOptions`: options first, then attributes.
331
+ * The start marker line for the options.
225
332
  * @param {TocOptions} options
226
333
  */
227
334
  function formatMarker(options) {
@@ -235,15 +342,14 @@ function formatMarker(options) {
235
342
 
236
343
  /**
237
344
  * @param {Record<string, string>} attributes
238
- * @returns {string[]} `name="value"` in the order given
345
+ * @returns {string[]}
239
346
  */
240
347
  function formatAttributes(attributes) {
241
348
  return Object.entries(attributes).map(([name, value]) => `${name}="${value}"`);
242
349
  }
243
350
 
244
351
  /**
245
- * The label used in the toc: the heading's own markdown, except that links become their text since a link
246
- * cannot nest inside the toc link.
352
+ * The link text used in the toc for a heading.
247
353
  * @param {string} markdown
248
354
  * @returns {string}
249
355
  */
@@ -253,8 +359,7 @@ function headingLabel(markdown) {
253
359
  }
254
360
 
255
361
  /**
256
- * Replace code spans (with their content, or the whole span when `keepCodeSpans` is set) and backslash escapes
257
- * (with the escaped character) by placeholders so the inline markdown passes leave them alone.
362
+ * The markdown with code spans and backslash escapes swapped for placeholders, and a function to put them back.
258
363
  * @param {string} markdown
259
364
  * @param {{ keepCodeSpans?: boolean }} [options]
260
365
  */
@@ -274,8 +379,7 @@ function protect(markdown, { keepCodeSpans = false } = {}) {
274
379
  }
275
380
 
276
381
  /**
277
- * Strip one leading and one trailing space from code span content when both are present and the content is
278
- * not only spaces, as CommonMark does.
382
+ * The code span content unpadded as CommonMark renders it.
279
383
  * @param {string} code
280
384
  */
281
385
  function unpadCodeSpan(code) {
@@ -289,8 +393,7 @@ function stripClosingHashes(markdown) {
289
393
  }
290
394
 
291
395
  /**
292
- * Links, images and reference links become their text, autolinks their url. Used for both the slug text and
293
- * the toc label since a link cannot nest inside the toc link.
396
+ * The text with links, images and autolinks flattened to their text or url.
294
397
  * @param {string} text
295
398
  */
296
399
  function stripLinks(text) {
@@ -308,15 +411,22 @@ function isParagraphText(line) {
308
411
  }
309
412
 
310
413
  /**
311
- * Options written on a start marker. `collapsible` wraps the list in a details element that starts open,
312
- * `collapsed` in one that starts closed. Each is `true` for the default summary "Table of contents" or a
313
- * string for a custom one. `attributes` are the other `name="value"` pairs on the marker, rendered on the
314
- * summary element in the order written, only present when there are any.
315
- * @typedef {{ collapsible?: true | string, collapsed?: true | string, attributes?: Record<string, string> }} TocOptions
414
+ * Options written on a start marker, each `true` or a summary text, and the other attributes for the summary element.
415
+ * @typedef {{ collapsible?: true | string, collapsed?: true | string, levels?: string, attributes?: Record<string, string> }} TocOptions
416
+ */
417
+
418
+ /**
419
+ * A heading with its zero based line, level, inline markdown and GitHub slug.
420
+ * @typedef {{ line: number, level: number, markdown: string, slug: string }} Headline
421
+ */
422
+
423
+ /**
424
+ * A link to an anchor with its zero based line, text, anchor as written, whether it has a target and, when it
425
+ * has none and one heading clearly matches, a suggestion.
426
+ * @typedef {{ line: number, text: string, anchor: string, valid: boolean, suggestion?: string }} Anchor
316
427
  */
317
428
 
318
429
  /**
319
- * A marker pair. `start` and `end` are zero based line numbers, -1 when that side is missing. `problem` is
320
- * only present when the start marker cannot be used: an unknown option or an impossible combination.
321
- * @typedef {{ start: number, end: number, options: TocOptions, problem?: string }} Marker
430
+ * A marker pair as zero based lines, -1 for a missing side, with its options, a problem when they cannot be used and a warning when one is ignored.
431
+ * @typedef {{ start: number, end: number, options: TocOptions, problem?: string, warning?: string }} Marker
322
432
  */
package/package.json CHANGED
@@ -1,36 +1,34 @@
1
1
  {
2
2
  "name": "@0dep/toc",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "Generate a GitHub flavoured markdown table of contents",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "module": "./index.js",
8
- "main": "./index.cjs",
9
8
  "types": "./types/index.d.ts",
10
9
  "exports": {
11
10
  ".": {
12
11
  "types": "./types/index.d.ts",
13
- "require": "./index.cjs",
14
- "import": "./index.js"
12
+ "default": "./index.js"
15
13
  }
16
14
  },
17
15
  "bin": {
18
16
  "toc": "bin/toc.js"
19
17
  },
20
18
  "engines": {
21
- "node": ">=20"
19
+ "node": ">=22.12"
22
20
  },
23
21
  "scripts": {
24
- "pretest": "npm run toc && npm run dist",
22
+ "pretest": "npm run toc && npm run types",
25
23
  "test": "mocha",
26
24
  "posttest": "npm run lint && npm run tsc && npm run test:md",
27
25
  "test:md": "texample",
28
- "toc": "node bin/toc.js README.md",
29
- "posttoc": "prettier --write README.md",
26
+ "toc": "node bin/toc.js --check README.md CHANGELOG.md",
27
+ "posttoc": "prettier --write README.md CHANGELOG.md",
30
28
  "lint": "eslint . --cache && prettier . --check --cache",
31
29
  "tsc": "tsc -p test",
32
- "dist": "rollup -c && dts-buddy",
33
- "prepack": "npm run dist",
30
+ "types": "tsc",
31
+ "prepack": "npm run types",
34
32
  "cov:html": "c8 -r html -r text mocha",
35
33
  "test:lcov": "c8 -r lcov mocha && npm run lint"
36
34
  },
@@ -46,7 +44,8 @@
46
44
  "markdown",
47
45
  "readme",
48
46
  "github",
49
- "slug"
47
+ "slug",
48
+ "glob"
50
49
  ],
51
50
  "author": {
52
51
  "name": "Zerodep AB",
@@ -63,26 +62,22 @@
63
62
  "license": "MIT",
64
63
  "devDependencies": {
65
64
  "@eslint/js": "^10.0.1",
66
- "@rollup/plugin-commonjs": "^29.0.3",
67
65
  "@types/chai": "^5.2.3",
68
66
  "@types/mocha": "^10.0.10",
69
67
  "@types/node": "^22.20.2",
70
68
  "c8": "^12.0.0",
71
69
  "chai": "^6.2.2",
72
- "dts-buddy": "^0.8.3",
73
70
  "eslint": "^10.10.0",
74
71
  "globals": "^17.12.0",
75
72
  "mocha": "^12.0.0",
76
73
  "prettier": "^3.9.6",
77
- "rollup": "^4.63.1",
78
74
  "texample": "^1.0.2",
79
75
  "typescript": "^6.0.3"
80
76
  },
81
77
  "files": [
82
78
  "bin/",
83
79
  "index.js",
84
- "index.cjs",
85
- "types/index.d.ts*",
80
+ "types/",
86
81
  "CHANGELOG.md"
87
82
  ]
88
83
  }