@chappibunny/repolens 0.8.0 → 0.9.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/CHANGELOG.md +23 -0
- package/README.md +13 -12
- package/RELEASE.md +1 -1
- package/package.json +1 -1
- package/src/ai/generate-sections.js +179 -55
- package/src/cli.js +18 -3
- package/src/core/config-schema.js +17 -5
- package/src/docs/generate-doc-set.js +65 -5
- package/src/plugins/loader.js +128 -0
- package/src/plugins/manager.js +103 -0
- package/src/publishers/confluence.js +158 -55
- package/src/publishers/index.js +23 -1
- package/src/publishers/notion.js +184 -43
- package/src/renderers/render.js +172 -142
- package/src/renderers/renderAnalysis.js +133 -32
package/src/publishers/notion.js
CHANGED
|
@@ -172,6 +172,74 @@ export async function clearPage(pageId) {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
function parseInlineRichText(text) {
|
|
176
|
+
// Parse inline markdown: **bold**, *italic*, `code` into Notion rich_text annotations
|
|
177
|
+
const segments = [];
|
|
178
|
+
let remaining = text;
|
|
179
|
+
|
|
180
|
+
while (remaining.length > 0) {
|
|
181
|
+
// Find the earliest inline marker
|
|
182
|
+
const boldIdx = remaining.indexOf("**");
|
|
183
|
+
const codeIdx = remaining.indexOf("`");
|
|
184
|
+
const italicIdx = remaining.indexOf("*");
|
|
185
|
+
|
|
186
|
+
// Collect candidate positions (only real matches)
|
|
187
|
+
const candidates = [];
|
|
188
|
+
if (boldIdx !== -1) candidates.push({ type: "bold", idx: boldIdx });
|
|
189
|
+
if (codeIdx !== -1) candidates.push({ type: "code", idx: codeIdx });
|
|
190
|
+
if (italicIdx !== -1 && italicIdx !== boldIdx) candidates.push({ type: "italic", idx: italicIdx });
|
|
191
|
+
|
|
192
|
+
if (candidates.length === 0) {
|
|
193
|
+
// No more markers — push rest as plain text
|
|
194
|
+
if (remaining) segments.push({ type: "text", text: { content: remaining } });
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Pick the earliest marker
|
|
199
|
+
candidates.sort((a, b) => a.idx - b.idx);
|
|
200
|
+
const first = candidates[0];
|
|
201
|
+
|
|
202
|
+
// Push any text before the marker as plain
|
|
203
|
+
if (first.idx > 0) {
|
|
204
|
+
segments.push({ type: "text", text: { content: remaining.slice(0, first.idx) } });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (first.type === "bold") {
|
|
208
|
+
const endBold = remaining.indexOf("**", first.idx + 2);
|
|
209
|
+
if (endBold === -1) {
|
|
210
|
+
// Unmatched — treat as plain text
|
|
211
|
+
segments.push({ type: "text", text: { content: remaining.slice(first.idx) } });
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
const inner = remaining.slice(first.idx + 2, endBold);
|
|
215
|
+
segments.push({ type: "text", text: { content: inner }, annotations: { bold: true } });
|
|
216
|
+
remaining = remaining.slice(endBold + 2);
|
|
217
|
+
} else if (first.type === "code") {
|
|
218
|
+
const endCode = remaining.indexOf("`", first.idx + 1);
|
|
219
|
+
if (endCode === -1) {
|
|
220
|
+
segments.push({ type: "text", text: { content: remaining.slice(first.idx) } });
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
const inner = remaining.slice(first.idx + 1, endCode);
|
|
224
|
+
segments.push({ type: "text", text: { content: inner }, annotations: { code: true } });
|
|
225
|
+
remaining = remaining.slice(endCode + 1);
|
|
226
|
+
} else if (first.type === "italic") {
|
|
227
|
+
const endItalic = remaining.indexOf("*", first.idx + 1);
|
|
228
|
+
if (endItalic === -1 || remaining[first.idx + 1] === "*") {
|
|
229
|
+
// Unmatched or actually a bold marker
|
|
230
|
+
segments.push({ type: "text", text: { content: remaining.slice(first.idx, first.idx + 1) } });
|
|
231
|
+
remaining = remaining.slice(first.idx + 1);
|
|
232
|
+
} else {
|
|
233
|
+
const inner = remaining.slice(first.idx + 1, endItalic);
|
|
234
|
+
segments.push({ type: "text", text: { content: inner }, annotations: { italic: true } });
|
|
235
|
+
remaining = remaining.slice(endItalic + 1);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return segments;
|
|
241
|
+
}
|
|
242
|
+
|
|
175
243
|
function markdownToNotionBlocks(markdown) {
|
|
176
244
|
// Safety check: handle undefined/null markdown
|
|
177
245
|
if (!markdown || typeof markdown !== 'string') {
|
|
@@ -184,17 +252,18 @@ function markdownToNotionBlocks(markdown) {
|
|
|
184
252
|
let i = 0;
|
|
185
253
|
|
|
186
254
|
while (i < lines.length && blocks.length < 100) {
|
|
187
|
-
const line = lines[i]
|
|
255
|
+
const line = lines[i];
|
|
256
|
+
const trimmed = line.trim();
|
|
188
257
|
|
|
189
258
|
// Skip empty lines
|
|
190
|
-
if (!
|
|
259
|
+
if (!trimmed) {
|
|
191
260
|
i++;
|
|
192
261
|
continue;
|
|
193
262
|
}
|
|
194
263
|
|
|
195
264
|
// Handle code blocks (```language...```)
|
|
196
|
-
if (
|
|
197
|
-
const language =
|
|
265
|
+
if (trimmed.startsWith("```")) {
|
|
266
|
+
const language = trimmed.slice(3).trim() || "plain text";
|
|
198
267
|
const codeLines = [];
|
|
199
268
|
i++; // Move past opening ```
|
|
200
269
|
|
|
@@ -229,78 +298,147 @@ function markdownToNotionBlocks(markdown) {
|
|
|
229
298
|
continue;
|
|
230
299
|
}
|
|
231
300
|
|
|
232
|
-
// Handle
|
|
233
|
-
if (
|
|
301
|
+
// Handle dividers (--- or ***)
|
|
302
|
+
if (/^[-*_]{3,}$/.test(trimmed)) {
|
|
234
303
|
blocks.push({
|
|
235
304
|
object: "block",
|
|
236
|
-
type: "
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
305
|
+
type: "divider",
|
|
306
|
+
divider: {}
|
|
307
|
+
});
|
|
308
|
+
i++;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Handle tables (| header | header |)
|
|
313
|
+
if (trimmed.startsWith("|") && trimmed.endsWith("|")) {
|
|
314
|
+
const tableRows = [];
|
|
315
|
+
while (i < lines.length && lines[i].trim().startsWith("|") && lines[i].trim().endsWith("|")) {
|
|
316
|
+
const row = lines[i].trim();
|
|
317
|
+
// Skip separator rows (|---|---|)
|
|
318
|
+
if (/^\|[\s\-:|]+\|$/.test(row)) {
|
|
319
|
+
i++;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const cells = row.split("|").slice(1, -1).map(c => c.trim());
|
|
323
|
+
tableRows.push(cells);
|
|
324
|
+
i++;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (tableRows.length > 0) {
|
|
328
|
+
const columnCount = tableRows[0].length;
|
|
329
|
+
const tableBlock = {
|
|
330
|
+
object: "block",
|
|
331
|
+
type: "table",
|
|
332
|
+
table: {
|
|
333
|
+
table_width: columnCount,
|
|
334
|
+
has_column_header: true,
|
|
335
|
+
has_row_header: false,
|
|
336
|
+
children: tableRows.map((row) => ({
|
|
337
|
+
type: "table_row",
|
|
338
|
+
table_row: {
|
|
339
|
+
cells: row.slice(0, columnCount).map(cell => parseInlineRichText(cell))
|
|
243
340
|
}
|
|
244
|
-
}
|
|
245
|
-
|
|
341
|
+
}))
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
// Pad rows that have fewer cells than the header
|
|
345
|
+
for (const child of tableBlock.table.children) {
|
|
346
|
+
while (child.table_row.cells.length < columnCount) {
|
|
347
|
+
child.table_row.cells.push([{ type: "text", text: { content: "" } }]);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
blocks.push(tableBlock);
|
|
351
|
+
}
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Handle headings
|
|
356
|
+
if (trimmed.startsWith("### ")) {
|
|
357
|
+
blocks.push({
|
|
358
|
+
object: "block",
|
|
359
|
+
type: "heading_3",
|
|
360
|
+
heading_3: {
|
|
361
|
+
rich_text: parseInlineRichText(trimmed.replace(/^### /, ""))
|
|
246
362
|
}
|
|
247
363
|
});
|
|
248
364
|
i++;
|
|
249
365
|
continue;
|
|
250
366
|
}
|
|
251
367
|
|
|
252
|
-
if (
|
|
368
|
+
if (trimmed.startsWith("## ")) {
|
|
253
369
|
blocks.push({
|
|
254
370
|
object: "block",
|
|
255
371
|
type: "heading_2",
|
|
256
372
|
heading_2: {
|
|
257
|
-
rich_text:
|
|
258
|
-
{
|
|
259
|
-
type: "text",
|
|
260
|
-
text: {
|
|
261
|
-
content: line.replace(/^## /, "")
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
]
|
|
373
|
+
rich_text: parseInlineRichText(trimmed.replace(/^## /, ""))
|
|
265
374
|
}
|
|
266
375
|
});
|
|
267
376
|
i++;
|
|
268
377
|
continue;
|
|
269
378
|
}
|
|
270
379
|
|
|
271
|
-
|
|
272
|
-
|
|
380
|
+
if (trimmed.startsWith("# ")) {
|
|
381
|
+
blocks.push({
|
|
382
|
+
object: "block",
|
|
383
|
+
type: "heading_1",
|
|
384
|
+
heading_1: {
|
|
385
|
+
rich_text: parseInlineRichText(trimmed.replace(/^# /, ""))
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
i++;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Handle blockquotes (> text) → Notion callout block
|
|
393
|
+
if (trimmed.startsWith("> ")) {
|
|
394
|
+
const quoteLines = [];
|
|
395
|
+
while (i < lines.length && lines[i].trim().startsWith("> ")) {
|
|
396
|
+
quoteLines.push(lines[i].trim().replace(/^> /, ""));
|
|
397
|
+
i++;
|
|
398
|
+
}
|
|
399
|
+
blocks.push({
|
|
400
|
+
object: "block",
|
|
401
|
+
type: "callout",
|
|
402
|
+
callout: {
|
|
403
|
+
rich_text: parseInlineRichText(quoteLines.join(" ")),
|
|
404
|
+
icon: { emoji: "💡" }
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Handle numbered lists (1. text, 2. text, etc.)
|
|
411
|
+
if (/^\d+\.\s/.test(trimmed)) {
|
|
412
|
+
blocks.push({
|
|
413
|
+
object: "block",
|
|
414
|
+
type: "numbered_list_item",
|
|
415
|
+
numbered_list_item: {
|
|
416
|
+
rich_text: parseInlineRichText(trimmed.replace(/^\d+\.\s/, ""))
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
i++;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Handle bullet lists (- text or * text)
|
|
424
|
+
if (/^[-*]\s/.test(trimmed)) {
|
|
273
425
|
blocks.push({
|
|
274
426
|
object: "block",
|
|
275
427
|
type: "bulleted_list_item",
|
|
276
428
|
bulleted_list_item: {
|
|
277
|
-
rich_text: [
|
|
278
|
-
{
|
|
279
|
-
type: "text",
|
|
280
|
-
text: {
|
|
281
|
-
content: line.replace(/^- /, "")
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
]
|
|
429
|
+
rich_text: parseInlineRichText(trimmed.replace(/^[-*]\s/, ""))
|
|
285
430
|
}
|
|
286
431
|
});
|
|
287
432
|
i++;
|
|
288
433
|
continue;
|
|
289
434
|
}
|
|
290
435
|
|
|
291
|
-
// Handle regular paragraphs
|
|
436
|
+
// Handle regular paragraphs with inline rich text
|
|
292
437
|
blocks.push({
|
|
293
438
|
object: "block",
|
|
294
439
|
type: "paragraph",
|
|
295
440
|
paragraph: {
|
|
296
|
-
rich_text:
|
|
297
|
-
{
|
|
298
|
-
type: "text",
|
|
299
|
-
text: {
|
|
300
|
-
content: line
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
]
|
|
441
|
+
rich_text: parseInlineRichText(trimmed)
|
|
304
442
|
}
|
|
305
443
|
});
|
|
306
444
|
i++;
|
|
@@ -309,6 +447,9 @@ function markdownToNotionBlocks(markdown) {
|
|
|
309
447
|
return blocks;
|
|
310
448
|
}
|
|
311
449
|
|
|
450
|
+
// Exported for testing
|
|
451
|
+
export { markdownToNotionBlocks, parseInlineRichText };
|
|
452
|
+
|
|
312
453
|
export async function replacePageContent(pageId, markdown) {
|
|
313
454
|
// Ensure page is unarchived before editing
|
|
314
455
|
await unarchivePage(pageId);
|