aeorank 1.4.0 → 1.5.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/dist/index.cjs CHANGED
@@ -348,6 +348,8 @@ __export(index_exports, {
348
348
  inferCategory: () => inferCategory,
349
349
  isSpaShell: () => isSpaShell,
350
350
  prefetchSiteData: () => prefetchSiteData,
351
+ scoreAllPages: () => scoreAllPages,
352
+ scorePage: () => scorePage,
351
353
  scoreToStatus: () => scoreToStatus
352
354
  });
353
355
  module.exports = __toCommonJS(index_exports);
@@ -2870,12 +2872,359 @@ async function fetchMultiPageData(siteData, options) {
2870
2872
  return added;
2871
2873
  }
2872
2874
 
2875
+ // src/page-scorer.ts
2876
+ var PAGE_CRITERIA = {
2877
+ schema_markup: { weight: 0.15, label: "Schema.org Structured Data" },
2878
+ qa_content_format: { weight: 0.15, label: "Q&A Content Format" },
2879
+ clean_html: { weight: 0.1, label: "Clean, Crawlable HTML" },
2880
+ faq_section: { weight: 0.1, label: "FAQ Section Content" },
2881
+ original_data: { weight: 0.1, label: "Original Data & Expert Content" },
2882
+ query_answer_alignment: { weight: 0.08, label: "Query-Answer Alignment" },
2883
+ content_freshness: { weight: 0.07, label: "Content Freshness Signals" },
2884
+ table_list_extractability: { weight: 0.07, label: "Table & List Extractability" },
2885
+ direct_answer_density: { weight: 0.07, label: "Direct Answer Paragraphs" },
2886
+ semantic_html: { weight: 0.05, label: "Semantic HTML5 & Accessibility" },
2887
+ fact_density: { weight: 0.05, label: "Fact & Data Density" },
2888
+ definition_patterns: { weight: 0.04, label: "Definition Patterns" },
2889
+ canonical_url: { weight: 0.04, label: "Canonical URL Strategy" },
2890
+ visible_date_signal: { weight: 0.04, label: "Visible Date Signal" }
2891
+ };
2892
+ function extractJsonLdBlocks(html) {
2893
+ const blocks = [];
2894
+ const regex = /<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
2895
+ let match;
2896
+ while ((match = regex.exec(html)) !== null) {
2897
+ blocks.push(match[1]);
2898
+ }
2899
+ return blocks;
2900
+ }
2901
+ function extractTypesFromJsonLd(blocks) {
2902
+ const types = /* @__PURE__ */ new Set();
2903
+ for (const block of blocks) {
2904
+ const typeMatches = block.match(/"@type"\s*:\s*"([^"]+)"/g) || [];
2905
+ for (const m of typeMatches) {
2906
+ const t = m.match(/"@type"\s*:\s*"([^"]+)"/);
2907
+ if (t) types.add(t[1]);
2908
+ }
2909
+ }
2910
+ return types;
2911
+ }
2912
+ function getTextContent(html) {
2913
+ return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
2914
+ }
2915
+ function extractQuestionHeadings2(html) {
2916
+ const headings = html.match(/<h[2-3][^>]*>([\s\S]*?)<\/h[2-3]>/gi) || [];
2917
+ const questions = [];
2918
+ for (const h of headings) {
2919
+ const text = h.replace(/<[^>]*>/g, "").trim();
2920
+ if (/\?$/.test(text) || /^(what|how|why|when|where|who|which|can|do|does|is|are|should|will)\b/i.test(text)) {
2921
+ questions.push(text);
2922
+ }
2923
+ }
2924
+ return questions;
2925
+ }
2926
+ function countAnsweredQuestions(html) {
2927
+ const questions = extractQuestionHeadings2(html);
2928
+ if (questions.length === 0) return { total: 0, answered: 0 };
2929
+ let answered = 0;
2930
+ for (const q of questions) {
2931
+ const escaped = q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2932
+ const pattern = new RegExp(escaped + "[\\s\\S]*?</h[2-3]>\\s*<p[^>]*>([\\s\\S]*?)</p>", "i");
2933
+ const match = html.match(pattern);
2934
+ if (match && match[1].replace(/<[^>]*>/g, "").trim().length >= 20) {
2935
+ answered++;
2936
+ }
2937
+ }
2938
+ return { total: questions.length, answered };
2939
+ }
2940
+ function cap(value, max) {
2941
+ return Math.min(value, max);
2942
+ }
2943
+ function scoreSchemaMarkup(html) {
2944
+ const blocks = extractJsonLdBlocks(html);
2945
+ if (blocks.length === 0) return 0;
2946
+ let score = 3;
2947
+ const types = extractTypesFromJsonLd(blocks);
2948
+ const knownTypes = [
2949
+ "Organization",
2950
+ "LocalBusiness",
2951
+ "Article",
2952
+ "FAQPage",
2953
+ "Product",
2954
+ "WebPage",
2955
+ "BreadcrumbList",
2956
+ "HowTo",
2957
+ "Person",
2958
+ "WebSite",
2959
+ "BlogPosting",
2960
+ "Service"
2961
+ ];
2962
+ let knownCount = 0;
2963
+ for (const t of types) {
2964
+ if (knownTypes.includes(t)) knownCount++;
2965
+ }
2966
+ score += cap(knownCount * 2, 4);
2967
+ if (types.has("Organization") || types.has("LocalBusiness")) score += 2;
2968
+ if (types.has("FAQPage")) score += 1;
2969
+ return cap(score, 10);
2970
+ }
2971
+ function scoreQAFormat(html) {
2972
+ const questions = extractQuestionHeadings2(html);
2973
+ let score = 0;
2974
+ if (questions.length >= 10) score += 5;
2975
+ else if (questions.length >= 3) score += 3;
2976
+ else if (questions.length >= 1) score += 1;
2977
+ const { answered } = countAnsweredQuestions(html);
2978
+ if (answered >= 1) score += 3;
2979
+ const h1Matches = html.match(/<h1[\s>]/gi) || [];
2980
+ if (h1Matches.length === 1) score += 2;
2981
+ return cap(score, 10);
2982
+ }
2983
+ function scoreCleanHtml(html) {
2984
+ let score = 0;
2985
+ const semantics = ["<main", "<article", "<section"];
2986
+ let semCount = 0;
2987
+ for (const tag of semantics) {
2988
+ if (html.toLowerCase().includes(tag)) semCount++;
2989
+ }
2990
+ score += cap(semCount, 3);
2991
+ const h1Matches = html.match(/<h1[\s>]/gi) || [];
2992
+ if (h1Matches.length === 1) score += 2;
2993
+ const text = getTextContent(html);
2994
+ if (text.length > 500) score += 3;
2995
+ const hasTitle = /<title[^>]*>[^<]+<\/title>/i.test(html);
2996
+ const hasDesc = /<meta\s[^>]*name=["']description["'][^>]*content=["'][^"']+["']/i.test(html) || /<meta\s[^>]*content=["'][^"']+["'][^>]*name=["']description["']/i.test(html);
2997
+ if (hasTitle && hasDesc) score += 2;
2998
+ return cap(score, 10);
2999
+ }
3000
+ function scoreFaqSection(html) {
3001
+ let score = 0;
3002
+ const lowerHtml = html.toLowerCase();
3003
+ if (/frequently\s*asked|faq/i.test(html)) score += 2;
3004
+ const blocks = extractJsonLdBlocks(html);
3005
+ const types = extractTypesFromJsonLd(blocks);
3006
+ if (types.has("FAQPage")) score += 3;
3007
+ const questions = extractQuestionHeadings2(html);
3008
+ if (questions.length >= 10) score += 1;
3009
+ if (/<details[\s>]/i.test(html) || /accordion|collapsible|toggle/i.test(lowerHtml)) score += 1;
3010
+ return cap(score, 10);
3011
+ }
3012
+ function scoreOriginalData(html) {
3013
+ let score = 0;
3014
+ const text = getTextContent(html);
3015
+ if (/\b(our (study|analysis|research|survey|data|findings))\b/i.test(text)) {
3016
+ score += 3;
3017
+ } else if (/\d+(\.\d+)?%|\$[\d,.]+|\b\d{1,3}(,\d{3})+\b/.test(text)) {
3018
+ score += 1;
3019
+ }
3020
+ if (/\bcase\s+stud(y|ies)\b/i.test(text) && /\d+(\.\d+)?%|\$[\d,.]+/.test(text)) {
3021
+ score += 3;
3022
+ } else if (/\bcase\s+stud(y|ies)\b/i.test(text)) {
3023
+ score += 1;
3024
+ }
3025
+ if (/\baccording\s+to\b|\bexpert|\b(Ph\.?D|MD|professor|analyst|researcher)\b/i.test(text)) {
3026
+ score += 2;
3027
+ }
3028
+ if (/href=["'][^"']*\/blog\b/i.test(html)) {
3029
+ score += 2;
3030
+ }
3031
+ return cap(score, 10);
3032
+ }
3033
+ function scoreQueryAnswerAlignment(html) {
3034
+ const { total, answered } = countAnsweredQuestions(html);
3035
+ if (total === 0) return 5;
3036
+ const ratio = answered / total;
3037
+ if (ratio >= 0.8) return 10;
3038
+ if (ratio >= 0.5) return 7;
3039
+ if (answered > 0) return 4;
3040
+ return 0;
3041
+ }
3042
+ function scoreContentFreshness(html) {
3043
+ let score = 0;
3044
+ const blocks = extractJsonLdBlocks(html);
3045
+ const allJsonLd = blocks.join(" ");
3046
+ if (/datePublished|dateModified/i.test(allJsonLd)) score += 3;
3047
+ const timeElements = html.match(/<time[\s>]/gi) || [];
3048
+ if (timeElements.length >= 2) score += 3;
3049
+ else if (timeElements.length === 1) score += 1;
3050
+ if (/<meta\s[^>]*property=["']article:(published_time|modified_time)["']/i.test(html)) score += 2;
3051
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
3052
+ const yearPattern = new RegExp(`\\b(${currentYear}|${currentYear - 1})\\b`);
3053
+ if (yearPattern.test(html)) score += 2;
3054
+ return cap(score, 10);
3055
+ }
3056
+ function scoreTableListExtractability(html) {
3057
+ let score = 0;
3058
+ const tablesWithHeaders = html.match(/<table[\s\S]*?<th[\s>]/gi) || [];
3059
+ if (tablesWithHeaders.length >= 2) score += 4;
3060
+ else if (tablesWithHeaders.length === 1) score += 3;
3061
+ if (tablesWithHeaders.length === 0 && /<table[\s>]/i.test(html)) score += 1;
3062
+ if (/<ol[\s>]/i.test(html)) score += 2;
3063
+ if (/<ul[\s>]/i.test(html)) score += 2;
3064
+ const listItems = html.match(/<li[\s>]/gi) || [];
3065
+ if (listItems.length >= 10) score += 1;
3066
+ if (/<dl[\s>]/i.test(html)) score += 1;
3067
+ return cap(score, 10);
3068
+ }
3069
+ function scoreDirectAnswerDensity(html) {
3070
+ let score = 0;
3071
+ const { answered } = countAnsweredQuestions(html);
3072
+ if (answered >= 3) score += 6;
3073
+ else if (answered >= 1) score += 3;
3074
+ const paragraphs = html.match(/<p[^>]*>([\s\S]*?)<\/p>/gi) || [];
3075
+ let snippetCount = 0;
3076
+ for (const p of paragraphs) {
3077
+ const text = p.replace(/<[^>]*>/g, "").trim();
3078
+ const words = text.split(/\s+/).filter((w) => w.length > 0).length;
3079
+ if (words >= 40 && words <= 150) snippetCount++;
3080
+ }
3081
+ if (snippetCount >= 3) score += 2;
3082
+ else if (snippetCount >= 1) score += 1;
3083
+ const directOpeners = getTextContent(html).match(/\b(yes|no|in short|the answer is|simply put|in summary)\b/gi) || [];
3084
+ if (directOpeners.length >= 2) score += 2;
3085
+ return cap(score, 10);
3086
+ }
3087
+ function scoreSemanticHtml(html) {
3088
+ let score = 0;
3089
+ const lowerHtml = html.toLowerCase();
3090
+ const elements = ["<main", "<article", "<time", "<nav", "<header", "<footer"];
3091
+ let count = 0;
3092
+ for (const el of elements) {
3093
+ if (lowerHtml.includes(el)) count++;
3094
+ }
3095
+ score += cap(Math.floor(count * 0.7), 4);
3096
+ const imgTags = html.match(/<img\s[^>]*>/gi) || [];
3097
+ if (imgTags.length > 0) {
3098
+ let withAlt = 0;
3099
+ for (const img of imgTags) {
3100
+ if (/\salt=["'][^"']*["']/i.test(img)) withAlt++;
3101
+ }
3102
+ if (withAlt / imgTags.length >= 0.8) score += 2;
3103
+ }
3104
+ if (/<html[^>]*\slang=["'][^"']+["']/i.test(html)) score += 2;
3105
+ if (/\baria-/i.test(html)) score += 2;
3106
+ return cap(score, 10);
3107
+ }
3108
+ function scoreFactDensity(html) {
3109
+ let score = 0;
3110
+ const text = getTextContent(html);
3111
+ const numericPatterns = text.match(/\d+(\.\d+)?%|\$[\d,.]+|\b\d{1,3}(,\d{3})+\b|\b\d+\s*(million|billion|thousand|users|customers|employees)\b/gi) || [];
3112
+ if (numericPatterns.length >= 6) score += 5;
3113
+ else if (numericPatterns.length >= 3) score += 3;
3114
+ else if (numericPatterns.length >= 1) score += 1;
3115
+ const years = /* @__PURE__ */ new Set();
3116
+ const yearMatches = text.match(/\b(19|20)\d{2}\b/g) || [];
3117
+ for (const y of yearMatches) years.add(y);
3118
+ if (years.size >= 2) score += 2;
3119
+ else if (years.size === 1) score += 1;
3120
+ if (/\baccording to\b|\bsource:\s|\bcited\b|\breported by\b/i.test(text)) score += 2;
3121
+ const units = text.match(/\b\d+\s*(kg|lb|miles|km|hours|minutes|days|months|years|GB|MB|TB)\b/gi) || [];
3122
+ if (units.length >= 2) score += 1;
3123
+ return cap(score, 10);
3124
+ }
3125
+ function scoreDefinitionPatterns(html) {
3126
+ let score = 0;
3127
+ const text = getTextContent(html);
3128
+ const defPatterns = text.match(/\b(is a|is an|refers to|defined as|means that|also known as|abbreviated as)\b/gi) || [];
3129
+ if (defPatterns.length >= 3) score += 5;
3130
+ else if (defPatterns.length >= 1) score += 3;
3131
+ const early = text.slice(0, 2e3);
3132
+ if (/\b(is a|is an|refers to|defined as)\b/i.test(early)) score += 2;
3133
+ if (/<dfn[\s>]/i.test(html) || /<abbr[\s>]/i.test(html)) score += 1;
3134
+ if (/<dl[\s>]/i.test(html) || /glossary/i.test(html)) score += 2;
3135
+ return cap(score, 10);
3136
+ }
3137
+ function scoreCanonicalUrl(html, url) {
3138
+ let score = 0;
3139
+ const canonicalMatch = html.match(/<link[^>]*rel=["']canonical["'][^>]*href=["']([^"']+)["']/i) || html.match(/<link[^>]*href=["']([^"']+)["'][^>]*rel=["']canonical["']/i);
3140
+ if (!canonicalMatch) return 0;
3141
+ score += 4;
3142
+ const canonicalHref = canonicalMatch[1];
3143
+ if (url) {
3144
+ try {
3145
+ const canonicalUrl = new URL(canonicalHref, url);
3146
+ const pageUrl = new URL(url);
3147
+ if (canonicalUrl.pathname === pageUrl.pathname && canonicalUrl.hostname === pageUrl.hostname) {
3148
+ score += 3;
3149
+ }
3150
+ } catch {
3151
+ }
3152
+ }
3153
+ if (canonicalHref.startsWith("https://")) score += 2;
3154
+ const allCanonicals = html.match(/<link[^>]*rel=["']canonical["'][^>]*>/gi) || [];
3155
+ if (allCanonicals.length === 1) score += 1;
3156
+ return cap(score, 10);
3157
+ }
3158
+ function scoreVisibleDateSignal(html) {
3159
+ let score = 0;
3160
+ const timeWithDatetime = html.match(/<time[^>]*datetime=["'][^"']+["'][^>]*>[^<]+<\/time>/gi) || [];
3161
+ if (timeWithDatetime.length > 0) score += 5;
3162
+ const blocks = extractJsonLdBlocks(html);
3163
+ const allJsonLd = blocks.join(" ");
3164
+ if (/datePublished|dateModified/i.test(allJsonLd)) score += 3;
3165
+ if (/<meta\s[^>]*property=["']article:(published_time|modified_time)["']/i.test(html)) score += 2;
3166
+ const modifiedMatch = allJsonLd.match(/"dateModified"\s*:\s*"([^"]+)"/i);
3167
+ if (modifiedMatch) {
3168
+ try {
3169
+ const modified = new Date(modifiedMatch[1]);
3170
+ const daysDiff = (Date.now() - modified.getTime()) / (1e3 * 60 * 60 * 24);
3171
+ if (daysDiff <= 180) score += 1;
3172
+ } catch {
3173
+ }
3174
+ }
3175
+ return cap(score, 10);
3176
+ }
3177
+ var SCORING_FUNCTIONS = {
3178
+ schema_markup: scoreSchemaMarkup,
3179
+ qa_content_format: scoreQAFormat,
3180
+ clean_html: scoreCleanHtml,
3181
+ faq_section: scoreFaqSection,
3182
+ original_data: scoreOriginalData,
3183
+ query_answer_alignment: scoreQueryAnswerAlignment,
3184
+ content_freshness: scoreContentFreshness,
3185
+ table_list_extractability: scoreTableListExtractability,
3186
+ direct_answer_density: scoreDirectAnswerDensity,
3187
+ semantic_html: scoreSemanticHtml,
3188
+ fact_density: scoreFactDensity,
3189
+ definition_patterns: scoreDefinitionPatterns,
3190
+ canonical_url: scoreCanonicalUrl,
3191
+ visible_date_signal: scoreVisibleDateSignal
3192
+ };
3193
+ function scorePage(html, url) {
3194
+ let totalWeight = 0;
3195
+ let weightedSum = 0;
3196
+ const criterionScores = [];
3197
+ for (const [criterion, { weight, label }] of Object.entries(PAGE_CRITERIA)) {
3198
+ const fn = SCORING_FUNCTIONS[criterion];
3199
+ const score = fn(html, url);
3200
+ criterionScores.push({ criterion, criterion_label: label, score, weight });
3201
+ weightedSum += score / 10 * weight * 100;
3202
+ totalWeight += weight;
3203
+ }
3204
+ const aeoScore = totalWeight === 0 ? 0 : Math.round(weightedSum / totalWeight);
3205
+ return { aeoScore, criterionScores };
3206
+ }
3207
+ function scoreAllPages(siteData) {
3208
+ const results = [];
3209
+ if (siteData.homepage) {
3210
+ const url = siteData.protocol ? `${siteData.protocol}://${siteData.domain}` : void 0;
3211
+ results.push(scorePage(siteData.homepage.text, url));
3212
+ }
3213
+ if (siteData.blogSample) {
3214
+ for (const page of siteData.blogSample) {
3215
+ const url = page.finalUrl || void 0;
3216
+ results.push(scorePage(page.text, url));
3217
+ }
3218
+ }
3219
+ return results;
3220
+ }
3221
+
2873
3222
  // src/page-analyzer.ts
2874
3223
  function extractTitle(html) {
2875
3224
  const match = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
2876
3225
  return match ? match[1].replace(/\s+/g, " ").trim() : "";
2877
3226
  }
2878
- function getTextContent(html) {
3227
+ function getTextContent2(html) {
2879
3228
  return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
2880
3229
  }
2881
3230
  function countWords(text) {
@@ -3028,7 +3377,7 @@ function checkHasQuestionHeadings(html) {
3028
3377
  }
3029
3378
  function analyzePage(html, url, category) {
3030
3379
  const title = extractTitle(html);
3031
- const textContent = getTextContent(html);
3380
+ const textContent = getTextContent2(html);
3032
3381
  const wordCount = countWords(textContent);
3033
3382
  const issues = [];
3034
3383
  const strengths = [];
@@ -3054,7 +3403,8 @@ function analyzePage(html, url, category) {
3054
3403
  for (const result of strengthChecks) {
3055
3404
  if (result) strengths.push(result);
3056
3405
  }
3057
- return { url, title, category, wordCount, issues, strengths };
3406
+ const { aeoScore, criterionScores } = scorePage(html, url);
3407
+ return { url, title, category, wordCount, issues, strengths, aeoScore, criterionScores };
3058
3408
  }
3059
3409
  function analyzeAllPages(siteData) {
3060
3410
  const reviews = [];
@@ -3265,17 +3615,22 @@ function generateHtmlReport(result) {
3265
3615
  <td>${escapeHtml(opp.description)}</td>
3266
3616
  </tr>`;
3267
3617
  }).join("\n");
3268
- const pagesRows = (result.pagesReviewed || []).map((page) => {
3618
+ const pagesReviewed = result.pagesReviewed || [];
3619
+ const pagesRows = pagesReviewed.map((page) => {
3269
3620
  const issueCount = page.issues.length;
3270
3621
  const strengthCount = page.strengths.length;
3622
+ const aeoDisplay = page.aeoScore != null ? `<span style="font-weight:600;color:${scoreColor(page.aeoScore)}">${page.aeoScore}</span>` : "-";
3271
3623
  return `<tr>
3272
3624
  <td>${escapeHtml(page.url)}</td>
3273
3625
  <td>${escapeHtml(page.category)}</td>
3274
3626
  <td>${page.wordCount}</td>
3627
+ <td>${aeoDisplay}</td>
3275
3628
  <td>${issueCount}</td>
3276
3629
  <td>${strengthCount}</td>
3277
3630
  </tr>`;
3278
3631
  }).join("\n");
3632
+ const scoredPages = pagesReviewed.filter((p) => p.aeoScore != null);
3633
+ const avgPageScore = scoredPages.length > 0 ? Math.round(scoredPages.reduce((sum, p) => sum + p.aeoScore, 0) / scoredPages.length) : null;
3279
3634
  const now = (/* @__PURE__ */ new Date()).toISOString();
3280
3635
  return `<!DOCTYPE html>
3281
3636
  <html lang="en">
@@ -3311,10 +3666,11 @@ function generateHtmlReport(result) {
3311
3666
  </table>
3312
3667
  ` : ""}
3313
3668
 
3314
- ${(result.pagesReviewed || []).length > 0 ? `
3315
- <h2 class="section-title">Pages Reviewed (${(result.pagesReviewed || []).length})</h2>
3669
+ ${pagesReviewed.length > 0 ? `
3670
+ <h2 class="section-title">Pages Reviewed (${pagesReviewed.length})</h2>
3671
+ ${avgPageScore != null ? `<div class="summary-box"><div class="summary-stat"><div class="num" style="color:${scoreColor(avgPageScore)}">${avgPageScore}</div><div class="label">Avg Page AEO Score</div></div></div>` : ""}
3316
3672
  <table>
3317
- <thead><tr><th>URL</th><th>Category</th><th>Words</th><th>Issues</th><th>Strengths</th></tr></thead>
3673
+ <thead><tr><th>URL</th><th>Category</th><th>Words</th><th>AEO Score</th><th>Issues</th><th>Strengths</th></tr></thead>
3318
3674
  <tbody>${pagesRows}</tbody>
3319
3675
  </table>
3320
3676
  ` : ""}
@@ -3508,6 +3864,8 @@ async function compare(domainA, domainB, options) {
3508
3864
  inferCategory,
3509
3865
  isSpaShell,
3510
3866
  prefetchSiteData,
3867
+ scoreAllPages,
3868
+ scorePage,
3511
3869
  scoreToStatus
3512
3870
  });
3513
3871
  //# sourceMappingURL=index.cjs.map