writr 5.0.4 → 6.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/README.md +358 -43
- package/dist/writr.d.ts +365 -24
- package/dist/writr.js +382 -28
- package/package.json +17 -10
package/dist/writr.js
CHANGED
|
@@ -94,7 +94,7 @@ var WritrCache = class {
|
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
-
// src/
|
|
97
|
+
// src/types.ts
|
|
98
98
|
var WritrHooks = /* @__PURE__ */ ((WritrHooks2) => {
|
|
99
99
|
WritrHooks2["beforeRender"] = "beforeRender";
|
|
100
100
|
WritrHooks2["afterRender"] = "afterRender";
|
|
@@ -103,11 +103,362 @@ var WritrHooks = /* @__PURE__ */ ((WritrHooks2) => {
|
|
|
103
103
|
WritrHooks2["loadFromFile"] = "loadFromFile";
|
|
104
104
|
return WritrHooks2;
|
|
105
105
|
})(WritrHooks || {});
|
|
106
|
+
|
|
107
|
+
// src/writr-ai.ts
|
|
108
|
+
import { generateText, Output } from "ai";
|
|
109
|
+
import { z } from "zod";
|
|
110
|
+
|
|
111
|
+
// src/writr-ai-cache.ts
|
|
112
|
+
import { CacheableMemory as CacheableMemory2 } from "cacheable";
|
|
113
|
+
import { Hashery as Hashery2 } from "hashery";
|
|
114
|
+
var WritrAICache = class {
|
|
115
|
+
_store = new CacheableMemory2();
|
|
116
|
+
_hashStore = new CacheableMemory2();
|
|
117
|
+
_hash = new Hashery2();
|
|
118
|
+
get store() {
|
|
119
|
+
return this._store;
|
|
120
|
+
}
|
|
121
|
+
get hashStore() {
|
|
122
|
+
return this._hashStore;
|
|
123
|
+
}
|
|
124
|
+
get(key, context) {
|
|
125
|
+
const hash = this.hash(key, context);
|
|
126
|
+
return this._store.get(hash);
|
|
127
|
+
}
|
|
128
|
+
set(key, context, value) {
|
|
129
|
+
const hash = this.hash(key, context);
|
|
130
|
+
this._store.set(hash, value);
|
|
131
|
+
}
|
|
132
|
+
hash(key, context) {
|
|
133
|
+
const content = { key, context };
|
|
134
|
+
const cacheKey = JSON.stringify(content);
|
|
135
|
+
const result = this._hashStore.get(cacheKey);
|
|
136
|
+
if (result) {
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
const hash = this._hash.toHashSync(content);
|
|
140
|
+
this._hashStore.set(cacheKey, hash);
|
|
141
|
+
return hash;
|
|
142
|
+
}
|
|
143
|
+
clear() {
|
|
144
|
+
this._store.clear();
|
|
145
|
+
this._hashStore.clear();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// src/writr-ai.ts
|
|
150
|
+
var AVERAGE_WORDS_PER_MINUTE = 200;
|
|
151
|
+
var WritrAI = class {
|
|
152
|
+
/**
|
|
153
|
+
* Creates a new WritrAI instance bound to a specific Writr document.
|
|
154
|
+
*
|
|
155
|
+
* @param writr - The base Writr instance this AI helper operates on.
|
|
156
|
+
* @param options - The AI model and optional cache/prompt settings.
|
|
157
|
+
*/
|
|
158
|
+
constructor(writr, options) {
|
|
159
|
+
this.writr = writr;
|
|
160
|
+
this.model = options.model;
|
|
161
|
+
this.prompts = options.prompts ?? {};
|
|
162
|
+
this.cache = options.cache ? new WritrAICache() : void 0;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The AI SDK model used for all generation requests.
|
|
166
|
+
*/
|
|
167
|
+
model;
|
|
168
|
+
/**
|
|
169
|
+
* The prompt templates used by this WritrAI instance.
|
|
170
|
+
*/
|
|
171
|
+
prompts;
|
|
172
|
+
/**
|
|
173
|
+
* Optional in-memory cache for generated AI results.
|
|
174
|
+
*/
|
|
175
|
+
cache;
|
|
176
|
+
/**
|
|
177
|
+
* Generates metadata for the current markdown document.
|
|
178
|
+
*
|
|
179
|
+
* @param options - Controls which metadata fields should be generated.
|
|
180
|
+
* @returns A metadata object for the current document.
|
|
181
|
+
*/
|
|
182
|
+
async getMetadata(options) {
|
|
183
|
+
const cacheKey = `metadata:${JSON.stringify(options ?? {})}`;
|
|
184
|
+
const cached = this.cache?.get(cacheKey, this.writr.content);
|
|
185
|
+
if (cached) {
|
|
186
|
+
return cached;
|
|
187
|
+
}
|
|
188
|
+
const fields = this.resolveMetadataFields(options);
|
|
189
|
+
const result = {};
|
|
190
|
+
if (fields.includes("wordCount")) {
|
|
191
|
+
result.wordCount = this.computeWordCount();
|
|
192
|
+
}
|
|
193
|
+
if (fields.includes("readingTime")) {
|
|
194
|
+
result.readingTime = this.computeReadingTime();
|
|
195
|
+
}
|
|
196
|
+
const aiFields = fields.filter(
|
|
197
|
+
(f) => f !== "wordCount" && f !== "readingTime"
|
|
198
|
+
);
|
|
199
|
+
if (aiFields.length > 0) {
|
|
200
|
+
const schema = this.buildMetadataSchema(aiFields);
|
|
201
|
+
const prompt = this.prompts.metadata ?? "Analyze the following markdown document and generate metadata for it. Be concise and accurate.";
|
|
202
|
+
const { output } = await generateText({
|
|
203
|
+
model: this.model,
|
|
204
|
+
output: Output.object({ schema }),
|
|
205
|
+
prompt: `${prompt}
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
${this.writr.content}`
|
|
210
|
+
});
|
|
211
|
+
Object.assign(result, output);
|
|
212
|
+
}
|
|
213
|
+
this.cache?.set(cacheKey, this.writr.content, result);
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Generates SEO metadata for the current markdown document.
|
|
218
|
+
*
|
|
219
|
+
* @param options - Controls which SEO fields should be generated.
|
|
220
|
+
* @returns An SEO metadata object for the current document.
|
|
221
|
+
*/
|
|
222
|
+
async getSEO(options) {
|
|
223
|
+
const cacheKey = `seo:${JSON.stringify(options ?? {})}`;
|
|
224
|
+
const cached = this.cache?.get(cacheKey, this.writr.content);
|
|
225
|
+
if (cached) {
|
|
226
|
+
return cached;
|
|
227
|
+
}
|
|
228
|
+
const fields = this.resolveSEOFields(options);
|
|
229
|
+
const schema = this.buildSEOSchema(fields);
|
|
230
|
+
const prompt = this.prompts.seo ?? "Analyze the following markdown document and generate SEO metadata for it. Be concise and accurate.";
|
|
231
|
+
const { output } = await generateText({
|
|
232
|
+
model: this.model,
|
|
233
|
+
output: Output.object({ schema }),
|
|
234
|
+
prompt: `${prompt}
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
${this.writr.content}`
|
|
239
|
+
});
|
|
240
|
+
const result = output;
|
|
241
|
+
this.cache?.set(cacheKey, this.writr.content, result);
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Generates a translated version of the current document.
|
|
246
|
+
*
|
|
247
|
+
* @param options - Translation settings including target locale.
|
|
248
|
+
* @returns A new translated Writr instance.
|
|
249
|
+
*/
|
|
250
|
+
async getTranslation(options) {
|
|
251
|
+
const cacheKey = `translation:${JSON.stringify(options)}`;
|
|
252
|
+
const cached = this.cache?.get(cacheKey, this.writr.content);
|
|
253
|
+
if (cached) {
|
|
254
|
+
return new Writr(cached);
|
|
255
|
+
}
|
|
256
|
+
const fromClause = options.from ? ` from ${options.from}` : "";
|
|
257
|
+
const frontMatterClause = options.translateFrontMatter ? " Also translate any string values in the YAML frontmatter." : " Preserve the YAML frontmatter exactly as-is without translating it.";
|
|
258
|
+
const prompt = this.prompts.translation ?? `Translate the following markdown document${fromClause} to ${options.to}.${frontMatterClause} Preserve all markdown formatting, links, code blocks, and structure. Return only the translated markdown document with no additional commentary.`;
|
|
259
|
+
let { text } = await generateText({
|
|
260
|
+
model: this.model,
|
|
261
|
+
prompt: `${prompt}
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
${this.writr.content}`
|
|
266
|
+
});
|
|
267
|
+
text = this.stripCodeFence(text);
|
|
268
|
+
this.cache?.set(cacheKey, this.writr.content, text);
|
|
269
|
+
return new Writr(text);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Generates metadata and applies it to the document frontmatter.
|
|
273
|
+
*
|
|
274
|
+
* @param options - Controls generation, overwrite behavior, and field mapping.
|
|
275
|
+
* @returns A result object describing what metadata was generated and applied.
|
|
276
|
+
*/
|
|
277
|
+
async applyMetadata(options) {
|
|
278
|
+
const generated = await this.getMetadata(options?.generate);
|
|
279
|
+
const frontMatter = { ...this.writr.frontMatter };
|
|
280
|
+
const fieldMap = options?.fieldMap ?? {};
|
|
281
|
+
const overwrite = options?.overwrite;
|
|
282
|
+
const applied = [];
|
|
283
|
+
const overwritten = [];
|
|
284
|
+
const skipped = [];
|
|
285
|
+
const overwriteSet = Array.isArray(overwrite) ? new Set(overwrite) : void 0;
|
|
286
|
+
for (const key of Object.keys(generated)) {
|
|
287
|
+
const value = generated[key];
|
|
288
|
+
if (value === void 0) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const frontMatterKey = fieldMap[key] ?? key;
|
|
292
|
+
const exists = frontMatterKey in frontMatter;
|
|
293
|
+
if (!exists) {
|
|
294
|
+
frontMatter[frontMatterKey] = value;
|
|
295
|
+
applied.push(key);
|
|
296
|
+
} else if (overwrite === true || overwriteSet?.has(key)) {
|
|
297
|
+
frontMatter[frontMatterKey] = value;
|
|
298
|
+
overwritten.push(key);
|
|
299
|
+
} else {
|
|
300
|
+
skipped.push(key);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
this.writr.frontMatter = frontMatter;
|
|
304
|
+
return {
|
|
305
|
+
writr: this.writr,
|
|
306
|
+
generated,
|
|
307
|
+
applied,
|
|
308
|
+
overwritten,
|
|
309
|
+
skipped
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
resolveMetadataFields(options) {
|
|
313
|
+
const allFields = [
|
|
314
|
+
"title",
|
|
315
|
+
"tags",
|
|
316
|
+
"keywords",
|
|
317
|
+
"description",
|
|
318
|
+
"preview",
|
|
319
|
+
"summary",
|
|
320
|
+
"category",
|
|
321
|
+
"topic",
|
|
322
|
+
"audience",
|
|
323
|
+
"difficulty",
|
|
324
|
+
"readingTime",
|
|
325
|
+
"wordCount"
|
|
326
|
+
];
|
|
327
|
+
if (!options) {
|
|
328
|
+
return allFields;
|
|
329
|
+
}
|
|
330
|
+
return allFields.filter(
|
|
331
|
+
(field) => options[field] === true
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
resolveSEOFields(options) {
|
|
335
|
+
const allFields = ["slug", "canonical", "openGraph"];
|
|
336
|
+
if (!options) {
|
|
337
|
+
return allFields;
|
|
338
|
+
}
|
|
339
|
+
return allFields.filter(
|
|
340
|
+
(field) => options[field] === true
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic schema construction
|
|
344
|
+
buildMetadataSchema(fields) {
|
|
345
|
+
const fieldSet = new Set(fields);
|
|
346
|
+
const entries = [];
|
|
347
|
+
if (fieldSet.has("title")) {
|
|
348
|
+
entries.push([
|
|
349
|
+
"title",
|
|
350
|
+
z.string().describe("The best-fit title for the document")
|
|
351
|
+
]);
|
|
352
|
+
}
|
|
353
|
+
if (fieldSet.has("tags")) {
|
|
354
|
+
entries.push([
|
|
355
|
+
"tags",
|
|
356
|
+
z.array(z.string()).describe("Human-friendly labels for organizing the document")
|
|
357
|
+
]);
|
|
358
|
+
}
|
|
359
|
+
if (fieldSet.has("keywords")) {
|
|
360
|
+
entries.push([
|
|
361
|
+
"keywords",
|
|
362
|
+
z.array(z.string()).describe("Search-oriented terms related to the document")
|
|
363
|
+
]);
|
|
364
|
+
}
|
|
365
|
+
if (fieldSet.has("description")) {
|
|
366
|
+
entries.push([
|
|
367
|
+
"description",
|
|
368
|
+
z.string().describe("A concise meta-style description of the document")
|
|
369
|
+
]);
|
|
370
|
+
}
|
|
371
|
+
if (fieldSet.has("preview")) {
|
|
372
|
+
entries.push([
|
|
373
|
+
"preview",
|
|
374
|
+
z.string().describe("A short teaser or preview of the content")
|
|
375
|
+
]);
|
|
376
|
+
}
|
|
377
|
+
if (fieldSet.has("summary")) {
|
|
378
|
+
entries.push([
|
|
379
|
+
"summary",
|
|
380
|
+
z.string().describe("A slightly longer overview of the document")
|
|
381
|
+
]);
|
|
382
|
+
}
|
|
383
|
+
if (fieldSet.has("category")) {
|
|
384
|
+
entries.push([
|
|
385
|
+
"category",
|
|
386
|
+
z.string().describe('A broad grouping such as "docs", "guide", or "blog"')
|
|
387
|
+
]);
|
|
388
|
+
}
|
|
389
|
+
if (fieldSet.has("topic")) {
|
|
390
|
+
entries.push([
|
|
391
|
+
"topic",
|
|
392
|
+
z.string().describe("The primary subject the document is about")
|
|
393
|
+
]);
|
|
394
|
+
}
|
|
395
|
+
if (fieldSet.has("audience")) {
|
|
396
|
+
entries.push([
|
|
397
|
+
"audience",
|
|
398
|
+
z.string().describe(
|
|
399
|
+
'The intended audience such as "developers" or "beginners"'
|
|
400
|
+
)
|
|
401
|
+
]);
|
|
402
|
+
}
|
|
403
|
+
if (fieldSet.has("difficulty")) {
|
|
404
|
+
entries.push([
|
|
405
|
+
"difficulty",
|
|
406
|
+
z.enum(["beginner", "intermediate", "advanced"]).describe(
|
|
407
|
+
"The estimated skill level required to understand the document"
|
|
408
|
+
)
|
|
409
|
+
]);
|
|
410
|
+
}
|
|
411
|
+
return z.object(Object.fromEntries(entries));
|
|
412
|
+
}
|
|
413
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic schema construction
|
|
414
|
+
buildSEOSchema(fields) {
|
|
415
|
+
const fieldSet = new Set(fields);
|
|
416
|
+
const entries = [];
|
|
417
|
+
if (fieldSet.has("slug")) {
|
|
418
|
+
entries.push([
|
|
419
|
+
"slug",
|
|
420
|
+
z.string().describe("A URL-safe identifier for the document")
|
|
421
|
+
]);
|
|
422
|
+
}
|
|
423
|
+
if (fieldSet.has("canonical")) {
|
|
424
|
+
entries.push([
|
|
425
|
+
"canonical",
|
|
426
|
+
z.string().describe("The preferred canonical URL for the document")
|
|
427
|
+
]);
|
|
428
|
+
}
|
|
429
|
+
if (fieldSet.has("openGraph")) {
|
|
430
|
+
entries.push([
|
|
431
|
+
"openGraph",
|
|
432
|
+
z.object({
|
|
433
|
+
title: z.string().describe("The social sharing title"),
|
|
434
|
+
description: z.string().describe("The social sharing description"),
|
|
435
|
+
image: z.string().describe("The image URL for social sharing").nullable()
|
|
436
|
+
}).describe("Open Graph metadata")
|
|
437
|
+
]);
|
|
438
|
+
}
|
|
439
|
+
return z.object(Object.fromEntries(entries));
|
|
440
|
+
}
|
|
441
|
+
computeWordCount() {
|
|
442
|
+
const text = this.writr.body;
|
|
443
|
+
const words = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`]*`/g, "").replace(/[#*_[\]()>~|-]/g, " ").split(/\s+/).filter((word) => word.length > 0);
|
|
444
|
+
return words.length;
|
|
445
|
+
}
|
|
446
|
+
computeReadingTime() {
|
|
447
|
+
const wordCount = this.computeWordCount();
|
|
448
|
+
return Math.max(1, Math.ceil(wordCount / AVERAGE_WORDS_PER_MINUTE));
|
|
449
|
+
}
|
|
450
|
+
stripCodeFence(text) {
|
|
451
|
+
const trimmed = text.trim();
|
|
452
|
+
const match = /^```\w*\n([\s\S]*?)```$/.exec(trimmed);
|
|
453
|
+
return match ? match[1].trim() : text;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// src/writr.ts
|
|
106
458
|
var Writr = class extends Hookified {
|
|
107
459
|
engine = unified().use(remarkParse).use(remarkGfm).use(remarkToc).use(remarkEmoji).use(remarkRehype).use(rehypeSlug).use(remarkMath).use(rehypeKatex).use(rehypeHighlight).use(rehypeStringify);
|
|
108
460
|
// Stringify HTML
|
|
109
461
|
_options = {
|
|
110
|
-
throwErrors: false,
|
|
111
462
|
renderOptions: {
|
|
112
463
|
emoji: true,
|
|
113
464
|
toc: true,
|
|
@@ -121,6 +472,7 @@ var Writr = class extends Hookified {
|
|
|
121
472
|
};
|
|
122
473
|
_content = "";
|
|
123
474
|
_cache = new WritrCache();
|
|
475
|
+
_ai;
|
|
124
476
|
/**
|
|
125
477
|
* Initialize Writr. Accepts a string or options object.
|
|
126
478
|
* @param {string | WritrOptions} [arguments1] If you send in a string, it will be used as the markdown content. If you send in an object, it will be used as the options.
|
|
@@ -130,7 +482,13 @@ var Writr = class extends Hookified {
|
|
|
130
482
|
* const writr = new Writr('Hello, world!', {caching: false});
|
|
131
483
|
*/
|
|
132
484
|
constructor(arguments1, arguments2) {
|
|
133
|
-
|
|
485
|
+
const options = typeof arguments1 === "object" ? arguments1 : arguments2;
|
|
486
|
+
super({
|
|
487
|
+
throwOnEmitError: options?.throwOnEmitError,
|
|
488
|
+
throwOnHookError: options?.throwOnHookError,
|
|
489
|
+
throwOnEmptyListeners: options?.throwOnEmptyListeners,
|
|
490
|
+
eventLogger: options?.eventLogger
|
|
491
|
+
});
|
|
134
492
|
if (typeof arguments1 === "string") {
|
|
135
493
|
this._content = arguments1;
|
|
136
494
|
} else if (arguments1) {
|
|
@@ -145,6 +503,9 @@ var Writr = class extends Hookified {
|
|
|
145
503
|
this.engine = this.createProcessor(this._options.renderOptions);
|
|
146
504
|
}
|
|
147
505
|
}
|
|
506
|
+
if (this._options.ai) {
|
|
507
|
+
this._ai = new WritrAI(this, this._options.ai);
|
|
508
|
+
}
|
|
148
509
|
}
|
|
149
510
|
/**
|
|
150
511
|
* Get the options.
|
|
@@ -153,6 +514,13 @@ var Writr = class extends Hookified {
|
|
|
153
514
|
get options() {
|
|
154
515
|
return this._options;
|
|
155
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Get the WritrAI instance if AI options were provided.
|
|
519
|
+
* @type {WritrAI | undefined}
|
|
520
|
+
*/
|
|
521
|
+
get ai() {
|
|
522
|
+
return this._ai;
|
|
523
|
+
}
|
|
156
524
|
/**
|
|
157
525
|
* Get the Content. This is the markdown content and front matter if it exists.
|
|
158
526
|
* @type {WritrOptions}
|
|
@@ -290,8 +658,8 @@ ${yamlString}---
|
|
|
290
658
|
return resultData.result;
|
|
291
659
|
} catch (error) {
|
|
292
660
|
this.emit("error", error);
|
|
293
|
-
throw new Error(`Failed to render markdown: ${error.message}`);
|
|
294
661
|
}
|
|
662
|
+
return "";
|
|
295
663
|
}
|
|
296
664
|
/**
|
|
297
665
|
* Render the markdown content to HTML synchronously.
|
|
@@ -333,8 +701,8 @@ ${yamlString}---
|
|
|
333
701
|
return resultData.result;
|
|
334
702
|
} catch (error) {
|
|
335
703
|
this.emit("error", error);
|
|
336
|
-
throw new Error(`Failed to render markdown: ${error.message}`);
|
|
337
704
|
}
|
|
705
|
+
return "";
|
|
338
706
|
}
|
|
339
707
|
/**
|
|
340
708
|
* Validate the markdown content by attempting to render it.
|
|
@@ -363,7 +731,6 @@ ${yamlString}---
|
|
|
363
731
|
}
|
|
364
732
|
return { valid: true };
|
|
365
733
|
} catch (error) {
|
|
366
|
-
this.emit("error", error);
|
|
367
734
|
if (content !== void 0) {
|
|
368
735
|
this._content = originalContent;
|
|
369
736
|
}
|
|
@@ -423,9 +790,6 @@ ${yamlString}---
|
|
|
423
790
|
await writeFile(data.filePath, data.content);
|
|
424
791
|
} catch (error) {
|
|
425
792
|
this.emit("error", error);
|
|
426
|
-
if (this._options.throwErrors) {
|
|
427
|
-
throw error;
|
|
428
|
-
}
|
|
429
793
|
}
|
|
430
794
|
}
|
|
431
795
|
/**
|
|
@@ -446,9 +810,6 @@ ${yamlString}---
|
|
|
446
810
|
fs.writeFileSync(data.filePath, data.content);
|
|
447
811
|
} catch (error) {
|
|
448
812
|
this.emit("error", error);
|
|
449
|
-
if (this._options.throwErrors) {
|
|
450
|
-
throw error;
|
|
451
|
-
}
|
|
452
813
|
}
|
|
453
814
|
}
|
|
454
815
|
/**
|
|
@@ -463,8 +824,8 @@ ${yamlString}---
|
|
|
463
824
|
return parse(html, reactParseOptions);
|
|
464
825
|
} catch (error) {
|
|
465
826
|
this.emit("error", error);
|
|
466
|
-
throw new Error(`Failed to render React: ${error.message}`);
|
|
467
827
|
}
|
|
828
|
+
return "";
|
|
468
829
|
}
|
|
469
830
|
/**
|
|
470
831
|
* Render the markdown content to React synchronously.
|
|
@@ -478,8 +839,8 @@ ${yamlString}---
|
|
|
478
839
|
return parse(html, reactParseOptions);
|
|
479
840
|
} catch (error) {
|
|
480
841
|
this.emit("error", error);
|
|
481
|
-
throw new Error(`Failed to render React: ${error.message}`);
|
|
482
842
|
}
|
|
843
|
+
return "";
|
|
483
844
|
}
|
|
484
845
|
/**
|
|
485
846
|
* Load markdown content from a file.
|
|
@@ -497,9 +858,6 @@ ${yamlString}---
|
|
|
497
858
|
this._content = data.content;
|
|
498
859
|
} catch (error) {
|
|
499
860
|
this.emit("error", error);
|
|
500
|
-
if (this._options.throwErrors) {
|
|
501
|
-
throw error;
|
|
502
|
-
}
|
|
503
861
|
}
|
|
504
862
|
}
|
|
505
863
|
/**
|
|
@@ -517,9 +875,6 @@ ${yamlString}---
|
|
|
517
875
|
this._content = data.content;
|
|
518
876
|
} catch (error) {
|
|
519
877
|
this.emit("error", error);
|
|
520
|
-
if (this._options.throwErrors) {
|
|
521
|
-
throw error;
|
|
522
|
-
}
|
|
523
878
|
}
|
|
524
879
|
}
|
|
525
880
|
/**
|
|
@@ -540,9 +895,6 @@ ${yamlString}---
|
|
|
540
895
|
await writeFile(data.filePath, data.content);
|
|
541
896
|
} catch (error) {
|
|
542
897
|
this.emit("error", error);
|
|
543
|
-
if (this._options.throwErrors) {
|
|
544
|
-
throw error;
|
|
545
|
-
}
|
|
546
898
|
}
|
|
547
899
|
}
|
|
548
900
|
/**
|
|
@@ -562,19 +914,19 @@ ${yamlString}---
|
|
|
562
914
|
fs.writeFileSync(data.filePath, data.content);
|
|
563
915
|
} catch (error) {
|
|
564
916
|
this.emit("error", error);
|
|
565
|
-
if (this._options.throwErrors) {
|
|
566
|
-
throw error;
|
|
567
|
-
}
|
|
568
917
|
}
|
|
569
918
|
}
|
|
570
919
|
mergeOptions(current, options) {
|
|
571
|
-
if (options.
|
|
572
|
-
|
|
920
|
+
if (options.throwOnEmitError !== void 0) {
|
|
921
|
+
this.throwOnEmitError = options.throwOnEmitError;
|
|
573
922
|
}
|
|
574
923
|
if (options.renderOptions) {
|
|
575
924
|
current.renderOptions ??= {};
|
|
576
925
|
this.mergeRenderOptions(current.renderOptions, options.renderOptions);
|
|
577
926
|
}
|
|
927
|
+
if (options.ai) {
|
|
928
|
+
current.ai = options.ai;
|
|
929
|
+
}
|
|
578
930
|
return current;
|
|
579
931
|
}
|
|
580
932
|
isCacheEnabled(options) {
|
|
@@ -642,6 +994,8 @@ ${yamlString}---
|
|
|
642
994
|
};
|
|
643
995
|
export {
|
|
644
996
|
Writr,
|
|
997
|
+
WritrAI,
|
|
998
|
+
WritrAICache,
|
|
645
999
|
WritrHooks
|
|
646
1000
|
};
|
|
647
1001
|
/* v8 ignore next -- @preserve */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "writr",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Markdown Rendering Simplified",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/writr.js",
|
|
@@ -49,9 +49,10 @@
|
|
|
49
49
|
"markdown-to-react"
|
|
50
50
|
],
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"
|
|
52
|
+
"ai": "^6.0.116",
|
|
53
|
+
"cacheable": "^2.3.3",
|
|
53
54
|
"hashery": "^1.5.0",
|
|
54
|
-
"hookified": "^1.
|
|
55
|
+
"hookified": "^2.1.0",
|
|
55
56
|
"html-react-parser": "^5.2.17",
|
|
56
57
|
"js-yaml": "^4.1.1",
|
|
57
58
|
"react": "^19.2.4",
|
|
@@ -61,31 +62,36 @@
|
|
|
61
62
|
"rehype-stringify": "^10.0.1",
|
|
62
63
|
"remark-emoji": "^5.0.2",
|
|
63
64
|
"remark-gfm": "^4.0.1",
|
|
64
|
-
"remark-github-blockquote-alert": "^2.0
|
|
65
|
+
"remark-github-blockquote-alert": "^2.1.0",
|
|
65
66
|
"remark-math": "^6.0.0",
|
|
66
67
|
"remark-mdx": "^3.1.1",
|
|
67
68
|
"remark-parse": "^11.0.0",
|
|
68
69
|
"remark-rehype": "^11.1.2",
|
|
69
70
|
"remark-toc": "^9.0.0",
|
|
70
|
-
"unified": "^11.0.5"
|
|
71
|
+
"unified": "^11.0.5",
|
|
72
|
+
"zod": "^4.3.6"
|
|
71
73
|
},
|
|
72
74
|
"devDependencies": {
|
|
73
|
-
"@
|
|
75
|
+
"@ai-sdk/anthropic": "^3.0.58",
|
|
76
|
+
"@ai-sdk/google": "^3.0.43",
|
|
77
|
+
"@ai-sdk/openai": "^3.0.41",
|
|
78
|
+
"@biomejs/biome": "^2.4.7",
|
|
74
79
|
"@monstermann/tinybench-pretty-printer": "^0.3.0",
|
|
75
80
|
"@types/js-yaml": "^4.0.9",
|
|
76
81
|
"@types/markdown-it": "^14.1.2",
|
|
77
|
-
"@types/node": "^25.
|
|
82
|
+
"@types/node": "^25.5.0",
|
|
78
83
|
"@types/react": "^19.2.14",
|
|
79
|
-
"@vitest/coverage-v8": "^4.0
|
|
84
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
80
85
|
"docula": "^0.40.0",
|
|
86
|
+
"dotenv": "^17.3.1",
|
|
81
87
|
"markdown-it": "^14.1.1",
|
|
82
|
-
"marked": "^17.0.
|
|
88
|
+
"marked": "^17.0.4",
|
|
83
89
|
"rimraf": "^6.1.3",
|
|
84
90
|
"tinybench": "^6.0.0",
|
|
85
91
|
"tsup": "^8.5.1",
|
|
86
92
|
"tsx": "^4.21.0",
|
|
87
93
|
"typescript": "^5.9.3",
|
|
88
|
-
"vitest": "^4.0
|
|
94
|
+
"vitest": "^4.1.0"
|
|
89
95
|
},
|
|
90
96
|
"files": [
|
|
91
97
|
"dist",
|
|
@@ -99,6 +105,7 @@
|
|
|
99
105
|
"benchmark": "tsx benchmark/benchmark-minimal.ts && tsx benchmark/benchmark-standard.ts",
|
|
100
106
|
"test": "pnpm lint && vitest run --coverage",
|
|
101
107
|
"test:ci": "biome check --error-on-warnings && vitest run --coverage",
|
|
108
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
102
109
|
"website:build": "rimraf ./site/README.md ./site/dist && npx docula build -s ./site -o ./site/dist",
|
|
103
110
|
"website:serve": "rimraf ./site/README.md ./site/dist && npx docula serve -s ./site -o ./site/dist"
|
|
104
111
|
}
|