@enhansome/core 1.9.0 → 1.10.1
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/github.d.ts +5 -4
- package/dist/github.js +7 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/markdown.d.ts +2 -3
- package/dist/markdown.js +64 -43
- package/dist/orchestrator.d.ts +2 -3
- package/dist/orchestrator.js +2 -2
- package/package.json +1 -1
package/dist/github.d.ts
CHANGED
|
@@ -77,11 +77,12 @@ export interface MakeOctokitOptions {
|
|
|
77
77
|
export declare function makeOctokit(token: string, { log, maxRetries, maxWaitSeconds, throttle, }?: MakeOctokitOptions): GithubClient;
|
|
78
78
|
export declare function getRepoInfo(octokit: GithubClient, owner: string, repo: string): Promise<RepoInfoDetails>;
|
|
79
79
|
/**
|
|
80
|
-
* The repo's
|
|
81
|
-
* node ids on
|
|
82
|
-
* metadata
|
|
80
|
+
* The repo's full info, for the JSON metadata — the numeric id consumers key
|
|
81
|
+
* stable node ids on, plus the stars/language/last_commit the root's own
|
|
82
|
+
* metadata row is built from. Returns null (logged) instead of failing the
|
|
83
|
+
* run: metadata is not a gate; consumers fall back when it is missing.
|
|
83
84
|
*/
|
|
84
|
-
export declare function
|
|
85
|
+
export declare function getRepoInfoOrNull(octokit: GithubClient, owner: string, repo: string): Promise<null | RepoInfoDetails>;
|
|
85
86
|
export declare function getReadme(octokit: GithubClient, owner: string, repo: string, format?: 'html' | 'raw'): Promise<string>;
|
|
86
87
|
/** Root file/directory names — the compile-manifest gate reads them to tell a
|
|
87
88
|
* directory of resources from a repo that IS the deliverable. A `path: ''`
|
package/dist/github.js
CHANGED
|
@@ -83,16 +83,17 @@ export async function getRepoInfo(octokit, owner, repo) {
|
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* The repo's
|
|
87
|
-
* node ids on
|
|
88
|
-
* metadata
|
|
86
|
+
* The repo's full info, for the JSON metadata — the numeric id consumers key
|
|
87
|
+
* stable node ids on, plus the stars/language/last_commit the root's own
|
|
88
|
+
* metadata row is built from. Returns null (logged) instead of failing the
|
|
89
|
+
* run: metadata is not a gate; consumers fall back when it is missing.
|
|
89
90
|
*/
|
|
90
|
-
export async function
|
|
91
|
+
export async function getRepoInfoOrNull(octokit, owner, repo) {
|
|
91
92
|
try {
|
|
92
|
-
return
|
|
93
|
+
return await getRepoInfo(octokit, owner, repo);
|
|
93
94
|
}
|
|
94
95
|
catch (error) {
|
|
95
|
-
octokit.log.error(`Failed to fetch repo
|
|
96
|
+
octokit.log.error(`Failed to fetch repo info for ${owner}/${repo}: ${formatRequestError(error)}`);
|
|
96
97
|
return null;
|
|
97
98
|
}
|
|
98
99
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { formatRequestError, getLatestCommitSha, getReadme,
|
|
1
|
+
export { formatRequestError, getLatestCommitSha, getReadme, getRepoInfo, getRepoInfoOrNull, getRootEntryNames, makeOctokit, parseGitHubUrl, parseOwnerRepo, } from './github.js';
|
|
2
2
|
export type { GithubClient, MakeOctokitOptions, RepoIdentifier, RepoInfoDetails, ThrottleOptions, } from './github.js';
|
|
3
3
|
export type { Logger } from './logger.js';
|
|
4
4
|
export { consoleLog, silentLog } from './logger.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { formatRequestError, getLatestCommitSha, getReadme,
|
|
1
|
+
export { formatRequestError, getLatestCommitSha, getReadme, getRepoInfo, getRepoInfoOrNull, getRootEntryNames, makeOctokit, parseGitHubUrl, parseOwnerRepo, } from './github.js';
|
|
2
2
|
export { consoleLog, silentLog } from './logger.js';
|
|
3
3
|
export { toRepoInfo } from './markdown.js';
|
|
4
4
|
export { enhance } from './orchestrator.js';
|
package/dist/markdown.d.ts
CHANGED
|
@@ -45,8 +45,7 @@ export interface JsonMetadata {
|
|
|
45
45
|
enhanced_repository: null | string;
|
|
46
46
|
enhanced_repository_description: null | string;
|
|
47
47
|
last_updated: string;
|
|
48
|
-
|
|
49
|
-
original_repository_id: null | number;
|
|
48
|
+
original_repository_info: null | RepoInfo;
|
|
50
49
|
original_repository_sha: null | string;
|
|
51
50
|
title: string;
|
|
52
51
|
}
|
|
@@ -55,7 +54,7 @@ export interface JsonSection {
|
|
|
55
54
|
items: JsonNode[];
|
|
56
55
|
title: string;
|
|
57
56
|
}
|
|
58
|
-
export declare function processMarkdownContent(originalContent: string, token: string, replacements
|
|
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<{
|
|
59
58
|
finalContent: string;
|
|
60
59
|
jsonData: JsonOutput;
|
|
61
60
|
}>;
|
package/dist/markdown.js
CHANGED
|
@@ -79,7 +79,7 @@ async function fetchTargetData(urls, repos) {
|
|
|
79
79
|
log.info(`Target fetch: ${repoInfoMap.size}/${urls.size} repo-info ok in ${Date.now() - fetchStart}ms (concurrency ${FETCH_CONCURRENCY}).`);
|
|
80
80
|
return repoInfoMap;
|
|
81
81
|
}
|
|
82
|
-
export async function processMarkdownContent(originalContent, token, replacements = [], sortOptions = { by: '', minLinks: 2 },
|
|
82
|
+
export async function processMarkdownContent(originalContent, token, replacements = [], sortOptions = { by: '', minLinks: 2 }, relativeLinkPrefix = '', enhancedRepository, enhancedRepositoryDescription, originalRepositorySha, originalRepositoryInfo, now = new Date(), log = consoleLog) {
|
|
83
83
|
const repos = createRepoInfoLookup(token, log);
|
|
84
84
|
const brandingEnabled = replacements.some(rule => rule.type === 'branding');
|
|
85
85
|
const contentAfterReplacements = applyTextReplacements(originalContent, replacements.filter(rule => rule.type !== 'branding'), log);
|
|
@@ -88,9 +88,7 @@ export async function processMarkdownContent(originalContent, token, replacement
|
|
|
88
88
|
normalizeGitHubUrls(tree);
|
|
89
89
|
const githubUrls = collectGitHubLinks(tree);
|
|
90
90
|
const repoInfoMap = await fetchTargetData(githubUrls, repos);
|
|
91
|
-
|
|
92
|
-
// the enhanced/mirror repo — otherwise the org name doubles into the title.
|
|
93
|
-
const { sections, title: rawTitle, titleHeadingIndex, } = processTree(tree, repoInfoMap, sortOptions, originalRepository);
|
|
91
|
+
const { sections, title: rawTitle, titleHeadingIndex, } = processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo);
|
|
94
92
|
// Single source of truth for the document title: brand it once and use the
|
|
95
93
|
// same value for the markdown H1 and metadata.title (parity).
|
|
96
94
|
const title = brandingEnabled ? brandTitle(rawTitle) : rawTitle;
|
|
@@ -99,8 +97,9 @@ export async function processMarkdownContent(originalContent, token, replacement
|
|
|
99
97
|
}
|
|
100
98
|
const metadata = {
|
|
101
99
|
last_updated: now.toISOString(),
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
original_repository_info: originalRepositoryInfo
|
|
101
|
+
? toRepoInfo(originalRepositoryInfo)
|
|
102
|
+
: null,
|
|
104
103
|
original_repository_sha: (originalRepositorySha?.trim() ?? '') || null,
|
|
105
104
|
enhanced_repository: (enhancedRepository?.trim() ?? '') || null,
|
|
106
105
|
enhanced_repository_description: (enhancedRepositoryDescription?.trim() ?? '') || null,
|
|
@@ -519,14 +518,23 @@ sectionGateOpen) {
|
|
|
519
518
|
listNode.children = entries.map(entry => entry.node);
|
|
520
519
|
return entries.flatMap(entry => entry.emitted);
|
|
521
520
|
}
|
|
522
|
-
// Title with the fallbacks every entry source shares
|
|
523
|
-
//
|
|
524
|
-
// name
|
|
521
|
+
// Title with the fallbacks every entry source shares: the split's own text
|
|
522
|
+
// when it names something; else the own link's label when meaningful; else —
|
|
523
|
+
// for a degenerate base (rank/year cell, URL label, tag word) owner/name, for
|
|
524
|
+
// an absent base (an image-only link) the repo name. A degenerate base with
|
|
525
|
+
// no live repo link (a group) keeps its text: nothing better exists.
|
|
525
526
|
function entryTitle(base, ownLink, repoInfo) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
527
|
+
if (base && !isDegenerateTitle(base)) {
|
|
528
|
+
return base;
|
|
529
|
+
}
|
|
530
|
+
const linkText = ownLink ? getInlineText(ownLink.children) : '';
|
|
531
|
+
if (isMeaningfulLinkText(linkText)) {
|
|
532
|
+
return linkText;
|
|
533
|
+
}
|
|
534
|
+
if (base === '') {
|
|
535
|
+
return repoInfo?.repo ?? '';
|
|
536
|
+
}
|
|
537
|
+
return repoInfo ? `${repoInfo.owner}/${repoInfo.repo}` : base;
|
|
530
538
|
}
|
|
531
539
|
// Table rows are entries under the nearest open container. The common shape —
|
|
532
540
|
// scala's `[name](repo) | description`, spec tables with the link in a later
|
|
@@ -560,15 +568,27 @@ function processTableRows(tableNode, repoInfoMap, gateOpen) {
|
|
|
560
568
|
}
|
|
561
569
|
const repoInfo = repoInfoMap.get(ownLink.url) ?? null;
|
|
562
570
|
const firstCellText = splitEntryText(row.children[0]?.children ?? []);
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
571
|
+
// A degenerate first cell (a rank/year column) moves the title source to
|
|
572
|
+
// the link's own cell: that cell no longer feeds the description — its
|
|
573
|
+
// label became the title — and the degenerate cell drops as noise.
|
|
574
|
+
const linkCellIndex = isDegenerateTitle(firstCellText.title)
|
|
575
|
+
? cellLinks.findIndex(link => link === ownLink)
|
|
576
|
+
: -1;
|
|
577
|
+
const titleText = linkCellIndex === -1
|
|
578
|
+
? firstCellText
|
|
579
|
+
: splitEntryText(row.children[linkCellIndex].children);
|
|
580
|
+
const description = [
|
|
581
|
+
titleText.description,
|
|
582
|
+
...row.children
|
|
583
|
+
.slice(1)
|
|
584
|
+
.filter((_cell, index) => index + 1 !== linkCellIndex)
|
|
585
|
+
.map(cell => getNodeText(cell)),
|
|
586
|
+
]
|
|
587
|
+
.join(' ')
|
|
588
|
+
.trim();
|
|
567
589
|
items.push(...emitEntryNodes(ownLink.url, repoInfo, {
|
|
568
|
-
title: entryTitle(
|
|
569
|
-
description
|
|
570
|
-
.join(' ')
|
|
571
|
-
.trim(),
|
|
590
|
+
title: entryTitle(titleText.title, ownLink, repoInfo),
|
|
591
|
+
description,
|
|
572
592
|
}, []));
|
|
573
593
|
continue;
|
|
574
594
|
}
|
|
@@ -605,6 +625,26 @@ const ENTRY_LABEL_MAX = 15;
|
|
|
605
625
|
const ENTRY_TRAILING_MAX = 3;
|
|
606
626
|
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;
|
|
607
627
|
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
|
+
const URL_TITLE = /^(?:[a-z][a-z0-9+.-]*:\/\/|www\.)\S+$|^github\.com\/\S+$/i;
|
|
635
|
+
function isDegenerateTitle(title) {
|
|
636
|
+
const trimmed = title.trim();
|
|
637
|
+
return (!!trimmed &&
|
|
638
|
+
(URL_TITLE.test(trimmed) ||
|
|
639
|
+
TAG_LINK_TEXT.test(trimmed) ||
|
|
640
|
+
!/[\p{L}]/u.test(trimmed)));
|
|
641
|
+
}
|
|
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
|
+
function isMeaningfulLinkText(text) {
|
|
646
|
+
return text !== '' && !isDegenerateTitle(text);
|
|
647
|
+
}
|
|
608
648
|
// Dated entry lines end their tag cluster with a publication date ("4 Feb
|
|
609
649
|
// 2023", "19 Apr 2022", "2023") — a real corpus family (dated paper/tutorial
|
|
610
650
|
// lists), not a sentence continuation.
|
|
@@ -771,22 +811,6 @@ const INVALID_TITLE_PATTERNS = [
|
|
|
771
811
|
/^video$/i,
|
|
772
812
|
/^science/i,
|
|
773
813
|
];
|
|
774
|
-
function repoNameFromIdentifier(identifier) {
|
|
775
|
-
const trimmed = identifier.trim();
|
|
776
|
-
if (!trimmed) {
|
|
777
|
-
return '';
|
|
778
|
-
}
|
|
779
|
-
if (/^https?:\/\//i.test(trimmed)) {
|
|
780
|
-
const parts = trimmed
|
|
781
|
-
.replace(/\.git$/i, '')
|
|
782
|
-
.replace(/[?#].*$/, '')
|
|
783
|
-
.split('/')
|
|
784
|
-
.filter(Boolean);
|
|
785
|
-
return parts[parts.length - 1] ?? '';
|
|
786
|
-
}
|
|
787
|
-
const slashIndex = trimmed.lastIndexOf('/');
|
|
788
|
-
return slashIndex === -1 ? trimmed : trimmed.slice(slashIndex + 1);
|
|
789
|
-
}
|
|
790
814
|
function formatRepoNameAsTitle(repoName) {
|
|
791
815
|
const cleaned = repoName.replace(/[-_]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
792
816
|
if (/^awesome\s+/i.test(cleaned)) {
|
|
@@ -870,7 +894,7 @@ function applyBrandingToTree(tree, title, titleHeadingIndex) {
|
|
|
870
894
|
tree.children.unshift(heading);
|
|
871
895
|
}
|
|
872
896
|
}
|
|
873
|
-
function processTree(tree, repoInfoMap, sortOptions,
|
|
897
|
+
function processTree(tree, repoInfoMap, sortOptions, originalRepositoryInfo) {
|
|
874
898
|
// Remember the title H1's index so branding replaces the exact same node;
|
|
875
899
|
// scope matches branding/section-building so they can't drift apart.
|
|
876
900
|
const titleHeadingIndex = findTitleHeadingIndex(tree);
|
|
@@ -881,11 +905,8 @@ function processTree(tree, repoInfoMap, sortOptions, originalRepository) {
|
|
|
881
905
|
// Derive a subject from the *source* repository name when no valid H1 is
|
|
882
906
|
// present. Using the source — not the enhanced/mirror repo — keeps the org
|
|
883
907
|
// name out of the title.
|
|
884
|
-
if (documentTitle === '' &&
|
|
885
|
-
|
|
886
|
-
if (repoName) {
|
|
887
|
-
documentTitle = formatRepoNameAsTitle(repoName);
|
|
888
|
-
}
|
|
908
|
+
if (documentTitle === '' && originalRepositoryInfo) {
|
|
909
|
+
documentTitle = formatRepoNameAsTitle(originalRepositoryInfo.repo);
|
|
889
910
|
}
|
|
890
911
|
const sectionDepth = findSectionDepth(tree, titleSlotIndex);
|
|
891
912
|
const gateForSection = sectionGatePasses(tree, titleSlotIndex, sectionDepth, repoInfoMap, sortOptions);
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Logger } from './logger.js';
|
|
2
|
+
import type { RepoInfoDetails } from './github.js';
|
|
2
3
|
import { JsonOutput, ReplacementRule } from './markdown.js';
|
|
3
4
|
export interface EnhanceOptions {
|
|
4
5
|
content: string;
|
|
@@ -8,11 +9,9 @@ export interface EnhanceOptions {
|
|
|
8
9
|
/** Defaults to the console sink; pass your own (e.g. an Actions workflow-command sink) to route diagnostics. */
|
|
9
10
|
log?: Logger;
|
|
10
11
|
now?: Date;
|
|
11
|
-
|
|
12
|
-
originalRepositoryId?: number;
|
|
12
|
+
originalRepositoryInfo?: null | RepoInfoDetails;
|
|
13
13
|
originalRepositorySha?: string;
|
|
14
14
|
relativeLinkPrefix?: string;
|
|
15
|
-
/** Text substitutions applied to the source before it is parsed. */
|
|
16
15
|
replacements?: ReplacementRule[];
|
|
17
16
|
sortBy?: '' | 'last_commit' | 'stars';
|
|
18
17
|
token: string;
|
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(),
|
|
3
|
+
const { content, disableBranding = false, log, now = new Date(), originalRepositoryInfo, originalRepositorySha, 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,
|
|
12
|
+
const { finalContent, jsonData } = await processMarkdownContent(content, token, rules, sortOptions, relativeLinkPrefix, enhancedRepository, enhancedRepositoryDescription, originalRepositorySha, originalRepositoryInfo, now, log);
|
|
13
13
|
return {
|
|
14
14
|
finalContent,
|
|
15
15
|
jsonData,
|