@enhansome/core 1.10.1 → 1.10.2
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/dist/first-seen.d.ts +3 -0
- package/dist/first-seen.js +20 -0
- package/dist/markdown.d.ts +2 -1
- package/dist/markdown.js +29 -58
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function firstSeenFor(previous, now) {
|
|
2
|
+
const index = new Map();
|
|
3
|
+
if (previous) {
|
|
4
|
+
for (const section of previous.items) {
|
|
5
|
+
collect(section.items, index);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return repoId => index.get(repoId) ?? now.toISOString();
|
|
9
|
+
}
|
|
10
|
+
function collect(nodes, index) {
|
|
11
|
+
for (const node of nodes) {
|
|
12
|
+
if (node.node_type === 'item' && node.first_seen) {
|
|
13
|
+
const existing = index.get(node.repo_info.id);
|
|
14
|
+
if (existing === undefined || node.first_seen < existing) {
|
|
15
|
+
index.set(node.repo_info.id, node.first_seen);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
collect(node.children, index);
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/markdown.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare function toRepoInfo(details: RepoInfoDetails): RepoInfo;
|
|
|
30
30
|
export interface JsonItem {
|
|
31
31
|
children: JsonNode[];
|
|
32
32
|
description: null | string;
|
|
33
|
+
first_seen?: string;
|
|
33
34
|
node_type: 'item';
|
|
34
35
|
repo_info: RepoInfo;
|
|
35
36
|
title: string;
|
|
@@ -54,7 +55,7 @@ export interface JsonSection {
|
|
|
54
55
|
items: JsonNode[];
|
|
55
56
|
title: string;
|
|
56
57
|
}
|
|
57
|
-
export declare function processMarkdownContent(originalContent: string, token: string, replacements?: ReplacementRule[], sortOptions?: SortOptions, relativeLinkPrefix?: string, enhancedRepository?: string, enhancedRepositoryDescription?: string, originalRepositorySha?: string, originalRepositoryInfo?: null | RepoInfoDetails, now?: Date, log?: Logger): Promise<{
|
|
58
|
+
export declare function processMarkdownContent(originalContent: string, token: string, replacements?: ReplacementRule[], sortOptions?: SortOptions, relativeLinkPrefix?: string, enhancedRepository?: string, enhancedRepositoryDescription?: string, originalRepositorySha?: string, originalRepositoryInfo?: null | RepoInfoDetails, previousJson?: JsonOutput, now?: Date, log?: Logger): Promise<{
|
|
58
59
|
finalContent: string;
|
|
59
60
|
jsonData: JsonOutput;
|
|
60
61
|
}>;
|
package/dist/markdown.js
CHANGED
|
@@ -4,6 +4,7 @@ import remarkParse from 'remark-parse';
|
|
|
4
4
|
import remarkStringify from 'remark-stringify';
|
|
5
5
|
import { unified } from 'unified';
|
|
6
6
|
import { visit } from 'unist-util-visit';
|
|
7
|
+
import { firstSeenFor } from './first-seen.js';
|
|
7
8
|
import { formatRequestError, getRepoInfo as fetchRepoInfo, makeOctokit, parseGitHubUrl, } from './github.js';
|
|
8
9
|
import { consoleLog } from './logger.js';
|
|
9
10
|
/** The sole crossing from the API shape to the emitted one, so both agree on the renames. */
|
|
@@ -79,7 +80,7 @@ async function fetchTargetData(urls, repos) {
|
|
|
79
80
|
log.info(`Target fetch: ${repoInfoMap.size}/${urls.size} repo-info ok in ${Date.now() - fetchStart}ms (concurrency ${FETCH_CONCURRENCY}).`);
|
|
80
81
|
return repoInfoMap;
|
|
81
82
|
}
|
|
82
|
-
export async function processMarkdownContent(originalContent, token, replacements = [], sortOptions = { by: '', minLinks: 2 }, relativeLinkPrefix = '', enhancedRepository, enhancedRepositoryDescription, originalRepositorySha, originalRepositoryInfo, now = new Date(), log = consoleLog) {
|
|
83
|
+
export async function processMarkdownContent(originalContent, token, replacements = [], sortOptions = { by: '', minLinks: 2 }, relativeLinkPrefix = '', enhancedRepository, enhancedRepositoryDescription, originalRepositorySha, originalRepositoryInfo, previousJson, now = new Date(), log = consoleLog) {
|
|
83
84
|
const repos = createRepoInfoLookup(token, log);
|
|
84
85
|
const brandingEnabled = replacements.some(rule => rule.type === 'branding');
|
|
85
86
|
const contentAfterReplacements = applyTextReplacements(originalContent, replacements.filter(rule => rule.type !== 'branding'), log);
|
|
@@ -88,7 +89,8 @@ export async function processMarkdownContent(originalContent, token, replacement
|
|
|
88
89
|
normalizeGitHubUrls(tree);
|
|
89
90
|
const githubUrls = collectGitHubLinks(tree);
|
|
90
91
|
const repoInfoMap = await fetchTargetData(githubUrls, repos);
|
|
91
|
-
const
|
|
92
|
+
const firstSeen = firstSeenFor(previousJson, now);
|
|
93
|
+
const { sections, title: rawTitle, titleHeadingIndex, } = processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo, firstSeen);
|
|
92
94
|
// Single source of truth for the document title: brand it once and use the
|
|
93
95
|
// same value for the markdown H1 and metadata.title (parity).
|
|
94
96
|
const title = brandingEnabled ? brandTitle(rawTitle) : rawTitle;
|
|
@@ -439,7 +441,7 @@ function splitEntryText(inlines) {
|
|
|
439
441
|
// group — never a `repo_info`, that's the identity-borrowing bug; no own link
|
|
440
442
|
// and no children is a non-GitHub leaf, kept in markdown, dropped from JSON.
|
|
441
443
|
// TODO(future): preserve non-GitHub leaves in a separate shape.
|
|
442
|
-
function emitEntryNodes(githubUrl, repoInfo, text, childrenJson) {
|
|
444
|
+
function emitEntryNodes(githubUrl, repoInfo, text, childrenJson, firstSeen) {
|
|
443
445
|
if (githubUrl && repoInfo) {
|
|
444
446
|
return [
|
|
445
447
|
{
|
|
@@ -448,6 +450,7 @@ function emitEntryNodes(githubUrl, repoInfo, text, childrenJson) {
|
|
|
448
450
|
description: text.description || null,
|
|
449
451
|
children: childrenJson,
|
|
450
452
|
repo_info: toRepoInfo(repoInfo),
|
|
453
|
+
first_seen: firstSeen(repoInfo.id),
|
|
451
454
|
},
|
|
452
455
|
];
|
|
453
456
|
}
|
|
@@ -466,7 +469,7 @@ function emitEntryNodes(githubUrl, repoInfo, text, childrenJson) {
|
|
|
466
469
|
}
|
|
467
470
|
return [];
|
|
468
471
|
}
|
|
469
|
-
function processListRecursively(listNode, repoInfoMap, sortOptions, isNested = false,
|
|
472
|
+
function processListRecursively(listNode, repoInfoMap, sortOptions, firstSeen, isNested = false,
|
|
470
473
|
// The caller's section-scope gate decision (sectionGatePasses). Absent for
|
|
471
474
|
// nested lists (emitted under their parent regardless) and for top-level
|
|
472
475
|
// lists with no open container (preamble), which gate per list.
|
|
@@ -493,8 +496,8 @@ sectionGateOpen) {
|
|
|
493
496
|
// regardless of the gate: the top-level call already gated the section.
|
|
494
497
|
const nestedContent = itemNode.children.filter((child) => child.type === 'list' || child.type === 'table');
|
|
495
498
|
const childrenJson = nestedContent.flatMap(child => child.type === 'list'
|
|
496
|
-
? processListRecursively(child, repoInfoMap, sortOptions, true)
|
|
497
|
-
: processTableRows(child, repoInfoMap, true));
|
|
499
|
+
? processListRecursively(child, repoInfoMap, sortOptions, firstSeen, true)
|
|
500
|
+
: processTableRows(child, repoInfoMap, true, firstSeen));
|
|
498
501
|
// Title/description split on the FIRST paragraph only — a paper-list
|
|
499
502
|
// entry's identity link may live in a later paragraph (findOwnGitHubLink)
|
|
500
503
|
// while its title text stays the leading one.
|
|
@@ -508,7 +511,7 @@ sectionGateOpen) {
|
|
|
508
511
|
// The shared title fallbacks (an inline-code link label carries no text
|
|
509
512
|
// nodes, so the split alone can leave an empty title).
|
|
510
513
|
entryText.title = entryTitle(entryText.title, ownLink, repoInfo);
|
|
511
|
-
const emitted = emitEntryNodes(githubUrl, repoInfo, entryText, childrenJson);
|
|
514
|
+
const emitted = emitEntryNodes(githubUrl, repoInfo, entryText, childrenJson, firstSeen);
|
|
512
515
|
entries.push({ emitted, node: itemNode, repoInfo });
|
|
513
516
|
}
|
|
514
517
|
if (sortOptions.by) {
|
|
@@ -536,18 +539,7 @@ function entryTitle(base, ownLink, repoInfo) {
|
|
|
536
539
|
}
|
|
537
540
|
return repoInfo ? `${repoInfo.owner}/${repoInfo.repo}` : base;
|
|
538
541
|
}
|
|
539
|
-
|
|
540
|
-
// scala's `[name](repo) | description`, spec tables with the link in a later
|
|
541
|
-
// column — holds ONE repo per row: the title is the first cell's text (the
|
|
542
|
-
// own link's text, then the repo name, when the first cell is empty), the
|
|
543
|
-
// description the remaining cells' text (badge images carry no text nodes, so
|
|
544
|
-
// they never pollute it). Card grids pin several repos per row, each cell its
|
|
545
|
-
// own card, so those emit one entry per link-bearing cell. A linked row above
|
|
546
|
-
// the delimiter is content like any other (grid tables have no label header;
|
|
547
|
-
// pure-label header rows carry no links and emit nothing). Rows stay in source
|
|
548
|
-
// order — unlike the unranked lists the product sorts, a table's row order is
|
|
549
|
-
// part of its meaning.
|
|
550
|
-
function processTableRows(tableNode, repoInfoMap, gateOpen) {
|
|
542
|
+
function processTableRows(tableNode, repoInfoMap, gateOpen, firstSeen) {
|
|
551
543
|
if (!gateOpen) {
|
|
552
544
|
return [];
|
|
553
545
|
}
|
|
@@ -589,7 +581,7 @@ function processTableRows(tableNode, repoInfoMap, gateOpen) {
|
|
|
589
581
|
items.push(...emitEntryNodes(ownLink.url, repoInfo, {
|
|
590
582
|
title: entryTitle(titleText.title, ownLink, repoInfo),
|
|
591
583
|
description,
|
|
592
|
-
}, []));
|
|
584
|
+
}, [], firstSeen));
|
|
593
585
|
continue;
|
|
594
586
|
}
|
|
595
587
|
const emittedUrls = new Set();
|
|
@@ -603,34 +595,15 @@ function processTableRows(tableNode, repoInfoMap, gateOpen) {
|
|
|
603
595
|
items.push(...emitEntryNodes(ownLink.url, repoInfo, {
|
|
604
596
|
title: entryTitle(cellText.title, ownLink, repoInfo),
|
|
605
597
|
description: cellText.description,
|
|
606
|
-
}, []));
|
|
598
|
+
}, [], firstSeen));
|
|
607
599
|
}
|
|
608
600
|
}
|
|
609
601
|
return items;
|
|
610
602
|
}
|
|
611
|
-
// A top-level paragraph can BE an entry, not only feed container prose. Two
|
|
612
|
-
// corpus families qualify (progress/empty-tree-parses.md, step 4): a GitHub
|
|
613
|
-
// link LEADING the paragraph behind a short entry label, or the paragraph
|
|
614
|
-
// ending in a tag cluster whose GitHub link carries the identity — the
|
|
615
|
-
// paper-list shape whose first link points at the paper and a [Code]/[Github]
|
|
616
|
-
// tag at the end, name/author lines ending in that tag, and dated lines
|
|
617
|
-
// ("… [Github] 4 Feb 2023"). Prose that mentions a repo ("Please see
|
|
618
|
-
// CONTRIBUTING", "See also [repo]", intro text ending in a bare repo URL) is
|
|
619
|
-
// neither and stays description. Calibrated on the 2,293-README corpus plus
|
|
620
|
-
// the fixture fleet, not intuition: an entry label is a TAG (empty, CJK/emoji,
|
|
621
|
-
// bracketed, or colon-terminated) — never bare English prose — and the
|
|
622
|
-
// identity link must carry text, so the ubiquitous image-only awesome badge
|
|
623
|
-
// is not an entry.
|
|
624
603
|
const ENTRY_LABEL_MAX = 15;
|
|
625
604
|
const ENTRY_TRAILING_MAX = 3;
|
|
626
605
|
const TAG_LINK_TEXT = /^[\[\]()*\s:_-]*(?:source\s+code|code|github|repo|source|src|project|paper|page|web|site|home|official|notebook|demo|data|docs|implementation|arxiv)\b[\[\]()*\s:_-]*$/i;
|
|
627
606
|
const URL_LINK_TEXT = /^https?:\/\/\S+$/i;
|
|
628
|
-
// A title that names nothing — the degenerate families every entry source can
|
|
629
|
-
// emit: a pure number/punctuation run (a table's rank or year column, "15."
|
|
630
|
-
// / "2023" / "2025-05" / "-"), a URL (a link whose label is the URL itself,
|
|
631
|
-
// scheme or scheme-less `github.com/…`), or a bare tag word ("GitHub",
|
|
632
|
-
// "Source code" — best-of's generated lines). CJK labels are letters
|
|
633
|
-
// (`\p{L}`) and stay titles.
|
|
634
607
|
const URL_TITLE = /^(?:[a-z][a-z0-9+.-]*:\/\/|www\.)\S+$|^github\.com\/\S+$/i;
|
|
635
608
|
function isDegenerateTitle(title) {
|
|
636
609
|
const trimmed = title.trim();
|
|
@@ -639,9 +612,6 @@ function isDegenerateTitle(title) {
|
|
|
639
612
|
TAG_LINK_TEXT.test(trimmed) ||
|
|
640
613
|
!/[\p{L}]/u.test(trimmed)));
|
|
641
614
|
}
|
|
642
|
-
// A link label that can serve as a title. The degenerate shapes are excluded
|
|
643
|
-
// twice over — a URL label stays a URL, and a tag label names the link's role
|
|
644
|
-
// ("[Github](repo)"), not the repo.
|
|
645
615
|
function isMeaningfulLinkText(text) {
|
|
646
616
|
return text !== '' && !isDegenerateTitle(text);
|
|
647
617
|
}
|
|
@@ -757,13 +727,13 @@ function blockquoteEntries(blockquote) {
|
|
|
757
727
|
// The shared entry emission for the non-list sources (paragraph entries,
|
|
758
728
|
// blockquote cards): resolve, split, title-fallback — the same decisions the
|
|
759
729
|
// list and table paths make through the same helpers.
|
|
760
|
-
function entryNodesFor(ownLink, inlines, repoInfoMap) {
|
|
730
|
+
function entryNodesFor(ownLink, inlines, repoInfoMap, firstSeen) {
|
|
761
731
|
const repoInfo = repoInfoMap.get(ownLink.url) ?? null;
|
|
762
732
|
const entryText = splitEntryText(inlines);
|
|
763
733
|
return emitEntryNodes(ownLink.url, repoInfo, {
|
|
764
734
|
title: entryTitle(entryText.title, ownLink, repoInfo),
|
|
765
735
|
description: entryText.description,
|
|
766
|
-
}, []);
|
|
736
|
+
}, [], firstSeen);
|
|
767
737
|
}
|
|
768
738
|
// The <details><summary>…</summary> collapsible-section idiom: the summary
|
|
769
739
|
// text delimits structure like a heading would (java's generated README,
|
|
@@ -894,7 +864,7 @@ function applyBrandingToTree(tree, title, titleHeadingIndex) {
|
|
|
894
864
|
tree.children.unshift(heading);
|
|
895
865
|
}
|
|
896
866
|
}
|
|
897
|
-
function processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo) {
|
|
867
|
+
function processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo, firstSeen) {
|
|
898
868
|
// Remember the title H1's index so branding replaces the exact same node;
|
|
899
869
|
// scope matches branding/section-building so they can't drift apart.
|
|
900
870
|
const titleHeadingIndex = findTitleHeadingIndex(tree);
|
|
@@ -925,7 +895,7 @@ function processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo) {
|
|
|
925
895
|
openImplicitSection(stack, atIndex);
|
|
926
896
|
}
|
|
927
897
|
if (gateForSection(stack[0])) {
|
|
928
|
-
stack[stack.length - 1].children.push(...entryNodesFor(ownLink, inlines, repoInfoMap));
|
|
898
|
+
stack[stack.length - 1].children.push(...entryNodesFor(ownLink, inlines, repoInfoMap, firstSeen));
|
|
929
899
|
}
|
|
930
900
|
};
|
|
931
901
|
// A standalone entry restating the repo of an open item container (crypto's
|
|
@@ -952,7 +922,7 @@ function processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo) {
|
|
|
952
922
|
const promotedEntry = headingEntry && getInlineText(headingEntry.link.children)
|
|
953
923
|
? headingEntry
|
|
954
924
|
: null;
|
|
955
|
-
closeContainers(stack, node.depth, sectionRecords, !!promotedEntry);
|
|
925
|
+
closeContainers(stack, node.depth, sectionRecords, firstSeen, !!promotedEntry);
|
|
956
926
|
openContainer(stack, node, i, sectionDepth, promotedEntry, headingEntry);
|
|
957
927
|
}
|
|
958
928
|
else if (node.type === 'paragraph') {
|
|
@@ -1004,7 +974,7 @@ function processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo) {
|
|
|
1004
974
|
// section, which keeps collecting after the collapsible block.
|
|
1005
975
|
const summaryTitle = detailsSummaryTitle(node.value);
|
|
1006
976
|
if (summaryTitle) {
|
|
1007
|
-
closeInnermostDetails(stack, sectionRecords);
|
|
977
|
+
closeInnermostDetails(stack, sectionRecords, firstSeen);
|
|
1008
978
|
// Container depths never decrease going up the stack. When the open
|
|
1009
979
|
// containers sit deeper than sectionDepth (a mid-document H1 defines
|
|
1010
980
|
// sectionDepth while the content sections run deeper), the
|
|
@@ -1025,7 +995,7 @@ function processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo) {
|
|
|
1025
995
|
});
|
|
1026
996
|
}
|
|
1027
997
|
else if (DETAILS_CLOSE.test(node.value)) {
|
|
1028
|
-
closeInnermostDetails(stack, sectionRecords);
|
|
998
|
+
closeInnermostDetails(stack, sectionRecords, firstSeen);
|
|
1029
999
|
}
|
|
1030
1000
|
}
|
|
1031
1001
|
else if (node.type === 'list') {
|
|
@@ -1039,7 +1009,7 @@ function processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo) {
|
|
|
1039
1009
|
// Every list inside the open container contributes items — a section is
|
|
1040
1010
|
// not closed by its first list — and the minLinks gate is decided per
|
|
1041
1011
|
// section, against the whole section subtree.
|
|
1042
|
-
const items = processListRecursively(node, repoInfoMap, sortOptions, false, gateForSection(stack[0]));
|
|
1012
|
+
const items = processListRecursively(node, repoInfoMap, sortOptions, firstSeen, false, gateForSection(stack[0]));
|
|
1043
1013
|
stack[stack.length - 1].children.push(...items);
|
|
1044
1014
|
}
|
|
1045
1015
|
else if (node.type === 'table') {
|
|
@@ -1049,11 +1019,11 @@ function processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo) {
|
|
|
1049
1019
|
if (stack.length === 0) {
|
|
1050
1020
|
openImplicitSection(stack, i);
|
|
1051
1021
|
}
|
|
1052
|
-
const items = processTableRows(node, repoInfoMap, gateForSection(stack[0]));
|
|
1022
|
+
const items = processTableRows(node, repoInfoMap, gateForSection(stack[0]), firstSeen);
|
|
1053
1023
|
stack[stack.length - 1].children.push(...items);
|
|
1054
1024
|
}
|
|
1055
1025
|
}
|
|
1056
|
-
closeContainers(stack, 0, sectionRecords);
|
|
1026
|
+
closeContainers(stack, 0, sectionRecords, firstSeen);
|
|
1057
1027
|
const sections = sectionRecords
|
|
1058
1028
|
.sort((a, b) => a.headingIndex - b.headingIndex)
|
|
1059
1029
|
.map(record => record.section);
|
|
@@ -1314,7 +1284,7 @@ function sectionGatePasses(tree, titleSlotIndex, sectionDepth, repoInfoMap, sort
|
|
|
1314
1284
|
// item-bearing nodes) is dropped; an item always survives, it IS the content.
|
|
1315
1285
|
// Non-section containers always have an open parent (stack invariant), and
|
|
1316
1286
|
// kind === 'item' exactly when repoInfo is set.
|
|
1317
|
-
function finalizeContainer(container, stack, sectionRecords) {
|
|
1287
|
+
function finalizeContainer(container, stack, sectionRecords, firstSeen) {
|
|
1318
1288
|
if (container.children.length === 0 && container.kind !== 'item') {
|
|
1319
1289
|
return;
|
|
1320
1290
|
}
|
|
@@ -1334,6 +1304,7 @@ function finalizeContainer(container, stack, sectionRecords) {
|
|
|
1334
1304
|
node_type: 'item',
|
|
1335
1305
|
repo_info: toRepoInfo(container.repoInfo),
|
|
1336
1306
|
title: container.title,
|
|
1307
|
+
first_seen: firstSeen(container.repoInfo.id),
|
|
1337
1308
|
});
|
|
1338
1309
|
}
|
|
1339
1310
|
else {
|
|
@@ -1351,13 +1322,13 @@ function finalizeContainer(container, stack, sectionRecords) {
|
|
|
1351
1322
|
// group/item always has a parent to land in. stopAtSynthesized: the pop an
|
|
1352
1323
|
// entry heading triggers must stop at the synthesized section wrapping its
|
|
1353
1324
|
// run — the next entry heading of the run lands back inside it.
|
|
1354
|
-
function closeContainers(stack, depth, sectionRecords, stopAtSynthesized = false) {
|
|
1325
|
+
function closeContainers(stack, depth, sectionRecords, firstSeen, stopAtSynthesized = false) {
|
|
1355
1326
|
while (stack.length > 0 &&
|
|
1356
1327
|
stack[stack.length - 1].headingDepth >= depth) {
|
|
1357
1328
|
if (stopAtSynthesized && stack[stack.length - 1].openedBySynthesis) {
|
|
1358
1329
|
break;
|
|
1359
1330
|
}
|
|
1360
|
-
finalizeContainer(stack.pop(), stack, sectionRecords);
|
|
1331
|
+
finalizeContainer(stack.pop(), stack, sectionRecords, firstSeen);
|
|
1361
1332
|
}
|
|
1362
1333
|
}
|
|
1363
1334
|
// Ends the innermost open details-section and everything opened inside it,
|
|
@@ -1365,7 +1336,7 @@ function closeContainers(stack, depth, sectionRecords, stopAtSynthesized = false
|
|
|
1365
1336
|
// details boundary never ends its parent section, so content after the
|
|
1366
1337
|
// collapsible block keeps collecting under it. A stray </details> (no
|
|
1367
1338
|
// details-section open) is a no-op.
|
|
1368
|
-
function closeInnermostDetails(stack, sectionRecords) {
|
|
1339
|
+
function closeInnermostDetails(stack, sectionRecords, firstSeen) {
|
|
1369
1340
|
let detailsIndex = -1;
|
|
1370
1341
|
for (let s = stack.length - 1; s >= 0; s--) {
|
|
1371
1342
|
if (stack[s].openedByDetails) {
|
|
@@ -1377,7 +1348,7 @@ function closeInnermostDetails(stack, sectionRecords) {
|
|
|
1377
1348
|
return;
|
|
1378
1349
|
}
|
|
1379
1350
|
while (stack.length > detailsIndex) {
|
|
1380
|
-
finalizeContainer(stack.pop(), stack, sectionRecords);
|
|
1351
|
+
finalizeContainer(stack.pop(), stack, sectionRecords, firstSeen);
|
|
1381
1352
|
}
|
|
1382
1353
|
}
|
|
1383
1354
|
function serializeAst(tree, originalContent) {
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -6,11 +6,11 @@ export interface EnhanceOptions {
|
|
|
6
6
|
disableBranding?: boolean;
|
|
7
7
|
enhancedRepository?: string;
|
|
8
8
|
enhancedRepositoryDescription?: string;
|
|
9
|
-
/** Defaults to the console sink; pass your own (e.g. an Actions workflow-command sink) to route diagnostics. */
|
|
10
9
|
log?: Logger;
|
|
11
10
|
now?: Date;
|
|
12
11
|
originalRepositoryInfo?: null | RepoInfoDetails;
|
|
13
12
|
originalRepositorySha?: string;
|
|
13
|
+
previousJson?: JsonOutput;
|
|
14
14
|
relativeLinkPrefix?: string;
|
|
15
15
|
replacements?: ReplacementRule[];
|
|
16
16
|
sortBy?: '' | 'last_commit' | 'stars';
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { processMarkdownContent, } from './markdown.js';
|
|
2
2
|
export async function enhance(options) {
|
|
3
|
-
const { content, disableBranding = false, log, now = new Date(), originalRepositoryInfo, originalRepositorySha, relativeLinkPrefix = '', replacements = [], sortBy = '', enhancedRepository, enhancedRepositoryDescription, token, } = options;
|
|
3
|
+
const { content, disableBranding = false, log, now = new Date(), originalRepositoryInfo, originalRepositorySha, previousJson, relativeLinkPrefix = '', replacements = [], sortBy = '', enhancedRepository, enhancedRepositoryDescription, token, } = options;
|
|
4
4
|
// Branding is an internal rule prepended to the caller's own; build a fresh
|
|
5
5
|
// array so the caller's `replacements` is never mutated.
|
|
6
6
|
const branding = { type: 'branding' };
|
|
@@ -9,7 +9,7 @@ export async function enhance(options) {
|
|
|
9
9
|
by: sortBy,
|
|
10
10
|
minLinks: 2,
|
|
11
11
|
};
|
|
12
|
-
const { finalContent, jsonData } = await processMarkdownContent(content, token, rules, sortOptions, relativeLinkPrefix, enhancedRepository, enhancedRepositoryDescription, originalRepositorySha, originalRepositoryInfo, now, log);
|
|
12
|
+
const { finalContent, jsonData } = await processMarkdownContent(content, token, rules, sortOptions, relativeLinkPrefix, enhancedRepository, enhancedRepositoryDescription, originalRepositorySha, originalRepositoryInfo, previousJson, now, log);
|
|
13
13
|
return {
|
|
14
14
|
finalContent,
|
|
15
15
|
jsonData,
|