chore:little fixes and formating and linting and patches
This commit is contained in:
@@ -58,20 +58,39 @@ const FEATURES = [
|
||||
slug: 'funnels',
|
||||
url: '/features/funnels',
|
||||
// "conversion funnel(s)" links to funnels, not conversion
|
||||
patterns: ['conversion funnels', 'conversion funnel', 'funnel analysis', 'funnels', 'funnel'],
|
||||
patterns: [
|
||||
'conversion funnels',
|
||||
'conversion funnel',
|
||||
'funnel analysis',
|
||||
'funnels',
|
||||
'funnel',
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'retention',
|
||||
url: '/features/retention',
|
||||
// "retention" alone is included but guarded by excludeBefore
|
||||
patterns: ['retention analysis', 'user retention', 'retention rates', 'retention rate', 'retention'],
|
||||
excludeBefore: ['data', 'unlimited'], // skip "data retention", "unlimited retention"
|
||||
patterns: [
|
||||
'retention analysis',
|
||||
'user retention',
|
||||
'retention rates',
|
||||
'retention rate',
|
||||
'retention',
|
||||
],
|
||||
excludeBefore: ['data', 'unlimited'], // skip "data retention", "unlimited retention"
|
||||
excludeAfter: ['period', 'policy', 'limit', 'of data'],
|
||||
},
|
||||
{
|
||||
slug: 'conversion',
|
||||
url: '/features/conversion',
|
||||
patterns: ['conversion tracking', 'conversion rates', 'conversion rate', 'conversion paths', 'conversions', 'conversion'],
|
||||
patterns: [
|
||||
'conversion tracking',
|
||||
'conversion rates',
|
||||
'conversion rate',
|
||||
'conversion paths',
|
||||
'conversions',
|
||||
'conversion',
|
||||
],
|
||||
excludeBefore: ['data'],
|
||||
},
|
||||
];
|
||||
@@ -90,39 +109,57 @@ function getSkipZones(text) {
|
||||
|
||||
// Fenced code blocks ```…```
|
||||
const codeBlock = /```[\s\S]*?```/g;
|
||||
while ((m = codeBlock.exec(text))) zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
while ((m = codeBlock.exec(text))) {
|
||||
zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
}
|
||||
|
||||
// Inline code `…`
|
||||
const inlineCode = /`[^`\n]+`/g;
|
||||
while ((m = inlineCode.exec(text))) zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
while ((m = inlineCode.exec(text))) {
|
||||
zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
}
|
||||
|
||||
// Existing markdown links [text](url)
|
||||
const mdLink = /\[[^\]]*\]\([^)]*\)/g;
|
||||
while ((m = mdLink.exec(text))) zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
while ((m = mdLink.exec(text))) {
|
||||
zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
}
|
||||
|
||||
// Headings # … (entire line)
|
||||
const heading = /^#{1,6}\s+.+$/gm;
|
||||
while ((m = heading.exec(text))) zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
while ((m = heading.exec(text))) {
|
||||
zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
}
|
||||
|
||||
// JSX / HTML tags (attributes may contain feature words)
|
||||
const jsxTag = /<[^>]+>/g;
|
||||
while ((m = jsxTag.exec(text))) zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
while ((m = jsxTag.exec(text))) {
|
||||
zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
}
|
||||
|
||||
// import statements
|
||||
const imp = /^import\s+.+$/gm;
|
||||
while ((m = imp.exec(text))) zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
while ((m = imp.exec(text))) {
|
||||
zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
}
|
||||
|
||||
// Frontmatter block
|
||||
const fm = /^---[\s\S]*?---/;
|
||||
if ((m = fm.exec(text))) zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
if ((m = fm.exec(text))) {
|
||||
zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
}
|
||||
|
||||
// Markdown table rows (| … |)
|
||||
const tableRow = /^\|.+\|$/gm;
|
||||
while ((m = tableRow.exec(text))) zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
while ((m = tableRow.exec(text))) {
|
||||
zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
}
|
||||
|
||||
// > blockquote lines that contain links
|
||||
const bqLink = /^>\s.*\[.*\]\(.*\).*$/gm;
|
||||
while ((m = bqLink.exec(text))) zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
while ((m = bqLink.exec(text))) {
|
||||
zones.push({ start: m.index, end: m.index + m[0].length });
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
@@ -146,28 +183,42 @@ function processFile(filePath, dir) {
|
||||
|
||||
for (const feature of FEATURES) {
|
||||
// If the file already links to this feature URL, skip entirely
|
||||
if (content.includes(feature.url)) continue;
|
||||
if (content.includes(feature.url)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let linked = false;
|
||||
|
||||
for (const pattern of feature.patterns) {
|
||||
if (linked) break;
|
||||
if (linked) {
|
||||
break;
|
||||
}
|
||||
|
||||
const re = new RegExp(`\\b${escapeRegex(pattern)}\\b`, 'gi');
|
||||
let m;
|
||||
|
||||
while ((m = re.exec(content))) {
|
||||
// In a skip zone?
|
||||
if (overlapsSkipZone(m.index, m[0].length, skipZones)) continue;
|
||||
if (overlapsSkipZone(m.index, m[0].length, skipZones)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check excludeBefore / excludeAfter
|
||||
if (feature.excludeBefore) {
|
||||
const before = content.slice(Math.max(0, m.index - 20), m.index).toLowerCase();
|
||||
if (feature.excludeBefore.some((w) => before.endsWith(w + ' '))) continue;
|
||||
const before = content
|
||||
.slice(Math.max(0, m.index - 20), m.index)
|
||||
.toLowerCase();
|
||||
if (feature.excludeBefore.some((w) => before.endsWith(w + ' '))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (feature.excludeAfter) {
|
||||
const after = content.slice(m.index + m[0].length, m.index + m[0].length + 20).toLowerCase();
|
||||
if (feature.excludeAfter.some((w) => after.startsWith(' ' + w))) continue;
|
||||
const after = content
|
||||
.slice(m.index + m[0].length, m.index + m[0].length + 20)
|
||||
.toLowerCase();
|
||||
if (feature.excludeAfter.some((w) => after.startsWith(' ' + w))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Build replacement
|
||||
@@ -194,7 +245,9 @@ function processFile(filePath, dir) {
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.length === 0) return null;
|
||||
if (changes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add / update the `updated` frontmatter field for articles & guides
|
||||
if (DIRS_WITH_UPDATED.includes(dir)) {
|
||||
@@ -223,8 +276,11 @@ function walk(dir) {
|
||||
const files = [];
|
||||
for (const e of entries) {
|
||||
const full = path.join(dir, e.name);
|
||||
if (e.isDirectory()) files.push(...walk(full));
|
||||
else if (e.name.endsWith('.mdx')) files.push(full);
|
||||
if (e.isDirectory()) {
|
||||
files.push(...walk(full));
|
||||
} else if (e.name.endsWith('.mdx')) {
|
||||
files.push(full);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
@@ -235,7 +291,9 @@ const results = [];
|
||||
|
||||
for (const dir of DIRS) {
|
||||
const dirPath = path.join(CONTENT_DIR, dir);
|
||||
if (!fs.existsSync(dirPath)) continue;
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const file of walk(dirPath)) {
|
||||
const changes = processFile(file, dir);
|
||||
@@ -247,7 +305,9 @@ for (const dir of DIRS) {
|
||||
|
||||
console.log('=== Internal Linking Report ===\n');
|
||||
console.log(`Total files modified: ${results.length}`);
|
||||
console.log(`Total links added: ${results.reduce((s, r) => s + r.changes.length, 0)}\n`);
|
||||
console.log(
|
||||
`Total links added: ${results.reduce((s, r) => s + r.changes.length, 0)}\n`
|
||||
);
|
||||
|
||||
for (const r of results) {
|
||||
console.log(` ${r.file}`);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user