npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

aeo-score

v1.0.3

Published

AEO Audit scoring engine — 14 product checks across 5 groups

Readme

@avada/aeo-score

Answer Engine Optimization scoring engine for Shopify stores. Evaluate how AI-ready your product pages are — 14 checks across 5 groups, decorator-driven, fully typed.

TypeScript Dual CJS/ESM


Why

Agentic commerce is here. Merchants don't just need SEO anymore — their content has to be parseable by ChatGPT, Perplexity, Google AI Overviews, and Shopify storefront MCP. The same product can rank well on Google yet be invisible to AI shopping agents because it lacks JSON-LD, answer-first intros, FAQ schema, or structured media.

@avada/aeo-score is the audit engine behind the Avada AEO Audit app. It runs 14 deterministic checks against a product's raw Shopify data and returns a weighted 0–100 score with actionable details.


Install

yarn add @avada/aeo-score
# or
npm install @avada/aeo-score

Requires TypeScript ≥ 4.9 if you consume the types directly.


Quick start

import {AeoScoreService} from '@avada/aeo-score';

const service = new AeoScoreService();
const auditor = service.getAuditor('products');

const {score, checks} = auditor.auditItem({
  id: 'gid://shopify/Product/1',
  title: '7 Chakra Bracelet',
  vendor: 'Company 123',
  productType: 'Bracelet',
  bodyHtml: '<p>Handmade bracelet, ideal for everyday wear…</p>',
  variants: [{price: '43', sku: 'SKU-001'}],
  images: [{url: '...', altText: 'Chakra bracelet front view'}],
  hasProductSchema: true,
  faqs: {questions: []}
});

console.log(score); // 65
console.log(checks[0]);
// { id: 'product_schema', passed: true, severity: 'critical',
//   group: 'structured_data', action: {type: 'contact'}, ... }

The 14 product checks

| # | ID | Severity | Weight | Group | | - | -- | -------- | ------ | ----- | | 1 | product_schema | critical | 12 | Structured data | | 2 | product_brand | critical | 12 | Structured data | | 3 | product_type | medium | 5 | Structured data | | 4 | product_description | critical | 12 | Content quality | | 5 | product_answer_first | high | 8 | Content quality | | 6 | product_conversational | medium | 5 | Content quality | | 7 | product_faq | high | 8 | FAQs & UGC | | 8 | product_faq_schema | high | 8 | FAQs & UGC | | 9 | product_reviews | low | 3 | FAQs & UGC | | 10 | product_price | high | 8 | Commerce signals | | 11 | product_variants | high | 8 | Commerce signals | | 12 | product_sku | medium | 5 | Commerce signals | | 13 | product_images | high | 8 | Media | | 14 | product_alt_text | medium | 5 | Media |

Score formula: round(Σ passed.weight / Σ all.weight × 100), skipped checks excluded.


Architecture

┌──────────────────────────────────────────┐
│  AeoScoreService                         │
│  ├── auditors: Record<type, Auditor>     │
│  ├── getAuditor(type)                    │
│  ├── calculateStats(items)               │
│  └── getScoreLabel(score)                │
└────────────┬─────────────────────────────┘
             │
             ▼
┌──────────────────────────────────────────┐
│  ContentTypeAuditor<ProductData>         │
│  ├── auditItem(data, ctx)                │
│  ├── auditAll(items, ctx)                │
│  └── categoryScore(scoredItems)          │
└────────────┬─────────────────────────────┘
             │
             ▼
┌──────────────────────────────────────────┐
│  BaseIssue<TData> (abstract)             │
│  ├── config: IssueConfig (from decorator)│
│  ├── check(data, ctx): CheckResult       │
│  └── toResult(data, ctx): CheckOutput    │
└────────────┬─────────────────────────────┘
             │
             ▼
┌──────────────────────────────────────────┐
│  @Issue(PRODUCT.X)                       │
│  class XxxIssue extends BaseIssue { … }  │
└──────────────────────────────────────────┘

Decorator-driven issues

Every issue is a class annotated with @Issue(config). Config lives in a single source of truth — src/constants/product.ts.

import {Issue, BaseIssue, PRODUCT} from '@avada/aeo-score';

@Issue(PRODUCT.DESCRIPTION)
export class DescriptionQualityIssue extends BaseIssue<ProductData> {
  check(data: ProductData): CheckResult {
    const words = countWords(data.bodyHtml);
    return {
      passed: words >= 50,
      currentValue: `${words} words`,
      detail: words >= 50 ? `${words} words — good length` : 'Too short…',
      suggestion: words >= 50 ? null : 'Add material, dimensions…'
    };
  }
}

The decorator attaches config as a static property:

@Issue(cfg) class X { … }
// ≡ X.__aeoIssueConfig = cfg

No reflect-metadata dependency — the implementation is dependency-free and works with both SWC and esbuild.


Writing a custom issue

import {BaseIssue, Issue, ContentTypeAuditor} from '@avada/aeo-score';

@Issue({
  id: 'product_warranty',
  label: 'Warranty information',
  severity: 'low',
  weight: 3,
  group: 'content_quality',
  action: {type: 'view', anchor: 'sec-description'}
})
class WarrantyIssue extends BaseIssue<ProductData> {
  check(data) {
    const passed = /warranty|guarantee/i.test(data.bodyHtml || '');
    return {
      passed,
      currentValue: passed ? 'Found' : 'Missing',
      detail: passed ? 'Warranty mentioned' : 'Add warranty terms',
      suggestion: passed ? null : 'Mention warranty length in description'
    };
  }
}

// Register at runtime
service.getAuditor('products')!.addIssue(new WarrantyIssue());

API reference

AeoScoreService

| Method | Description | | ------ | ----------- | | getAuditor(type) | Returns the ContentTypeAuditor for a content type. | | calculateStats(itemsByType) | Aggregates {totalScanned, needAttention, passed, criticalIssues} across types. | | getScoreLabel(score) | veryBad | poor | medium | good | excellent. |

ContentTypeAuditor<TData>

| Method | Description | | ------ | ----------- | | auditItem(data, ctx?) | {score, checks} for one item. | | auditAll(items, ctx?) | Batch audit, sorted by score asc. | | categoryScore(scored) | Average item score across items. | | addIssue(issue) / removeIssue(id) | Runtime mutation. |

BaseIssue<TData>

Abstract class. Subclasses must implement check(data, ctx) and be decorated with @Issue(config).

PRODUCT

Typed config constants — PRODUCT.SCHEMA, PRODUCT.BRAND, PRODUCT.DESCRIPTION, ... 14 entries.


Types

All domain types are exported:

import type {
  Severity, Group, ActionType, IssueConfig,
  CheckResult, CheckOutput, AuditContext, ProductData
} from '@avada/aeo-score';

Development

yarn               # install
yarn dev           # watch build
yarn build         # emit dist/ (cjs + mjs + d.ts)
yarn typecheck     # type-check only

Publish

npm version patch           # or minor/major
npm publish --access public # pushes to registry.avada.io (scoped publishConfig)

License

Private — Avada internal use.


Roadmap

  • [ ] Collection audits (6 checks)
  • [ ] Article audits (8 checks)
  • [ ] Page audits (7 checks)
  • [ ] Rule-based @Issue.DependsOn(...) for conditional checks
  • [ ] Output schema versioning via @Issue({version: 2})