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

@mikitasazan/analytics

v0.3.3

Published

Shared multi-source analytics core (GA4, Search Console, Yandex Metrica/Webmaster) with an `analytics` CLI, for all projects

Readme

@mikitasazan/analytics

Общее ядро аналитики для всех проектов: тянет данные из GA4, Google Search Console, Яндекс.Метрики и Яндекс.Вебмастера, складывает дневной снимок и строит дайджест/недельный отчёт. Даёт CLI analytics. Всё, что различается по сайту, живёт в консюмер-репо (analytics.config.ts + .env), а не в пакете.

Статус: публикуется в публичный npm. Потребители: playhub, game-publisher, one-q — список не ведётся вручную, propagate находит их поиском по репозиториям.

Установка

npm install -D @mikitasazan/analytics

Релиз

release patch|minor|major     # из корня пакета

Публикация и раскатка по потребителям — одной командой; канон — скилл package-ops. Своего scripts/release.sh у пакета больше нет.

Пакет ставит бинарь analytics. Ядро поставляется как TypeScript и исполняется через tsx (как это уже делают playhub/one-q) — отдельного шага сборки нет, ровно как у @mikitasazan/config.

Настройка сайта (per-site)

  1. analytics.config.ts в корне репо — какие сервисы включены + карта целей Метрики:

    import { defineConfig } from '@mikitasazan/analytics';
    
    export default defineConfig({
      services: ['ga4', 'gsc', 'metrica', 'webmaster'], // game-publisher: ['ga4', 'gsc']
      metricaGoals: { playhub_click: 'Переход в PlayHub', search: 'Поиск по сайту' }
    });

    Образец — examples/analytics.config.ts.

  2. .env в корне репо — id и токены сайта. Канон имён — examples/.env.example.

Команды

analytics fetch [YYYY-MM-DD]    # тянет включённые сервисы → data/analytics/raw/<день>.json;
                                # без даты — свежайший финализированный день (сегодня − лаг);
                                # с датой — бэкфилл конкретного дня
analytics digest [YYYY-MM-DD]   # дайджест в терминал + отчёт docs/analytics/<день>.md
analytics weekly [YYYY-MM-DD]   # недельный отчёт → data/analytics/weekly/<ISO-неделя>.md
                                # (каталог переопределяется env ANALYTICS_WEEKLY_DIR)
analytics audit [start] [end]   # прямой опрос всех источников за диапазон — проверка «живо ли»
analytics auth                  # разовое получение GSC refresh token (get-tokens.ts)
analytics seo-check [baseUrl]   # SEO smoke-check рендера (canonical/hreflang/JSON-LD),
                                # exit 1 — реальная проблема, exit 2 — цель недоступна.
                                # Default http://localhost:4321; либо SEO_CHECK_BASE=…

seo-check проверяет РЕНДЕР ключевых страниц (не билдеры схем изолированно) — canonical, hreflang, JSON-LD (тип, обязательные поля, origin-leak на localhost). Общее ядро (обход страниц, разбор JSON-LD/@graph, определение «цель недоступна») — в пакете; per-site — секция seoCheck в analytics.config.ts консюмера: режим hreflang (always — фиксированный список локалей на каждой странице; conditional — hreflang проверяется только если он есть, и тогда обязателен x-default), стартовые страницы и ожидаемые типы JSON-LD по шаблонам URL. Образец — секция seoCheck в examples/analytics.config.ts.

Пути резолвятся от корня консюмера (process.cwd()), .env подхватывается автоматически (dotenv). Рекомендуемый крон: ежедневный analytics fetch + цикл «сегодня−4…сегодня−1» для догрузки дней, пропущенных упавшим краном.

Повторный fetch дня ничего не стирает

Именно этот цикл догрузки и был опасен: fetch писал снимок поверх старого тем, что получилось в ЭТОМ запуске, а упавший источник просто отсутствовал в результате. День, собранный полностью, превращался в пустой, и digest рисовал из него отчёт с прочерками. Так погибли три отчёта в game-publisher (28–29.07) и один в playhub (02.08) — вместе с сырыми файлами под ними.

Сейчас повторный fetch может ТОЛЬКО добавить или обновить секцию. Источник, который ответил, побеждает; источник, который упал или больше не включён в services, оставляет прежние числа, а его имя попадает в поле carriedOver снимка и в строку в логе крона. fetchedAt по-прежнему означает время последней ЗАПИСИ файла — какие секции в нём не от этого запуска, отвечает carriedOver.

Если файл дня существует, но не читается как JSON, fetch не перезаписывает его, а завершается с кодом 1: в нём может лежать единственная копия дня, и решать её судьбу должен человек. Логика слияния — src/merge-snapshot.ts, тесты — src/merge-snapshot.test.ts.

Отвергнутый ключ — это ошибка, а не «источник выключен»

Правило одно и читается в две строки: ключей нет — тихо пропускаем; ключи есть, но их отвергли — падаем. Первое нужно сайту, который этот источник не использует; второе — сайту, у которого источник сломался.

Раньше обе половины вели себя одинаково тихо. У game-publisher отозвали refresh token Search Console: getGscAccessToken() писал строку в stderr и возвращал null, fetchGsc() превращал это в «источника нет», и в снимке не оказывалось ни ошибки, ни секции. Прогон печатал «2/3 источников · ошибок: 0», выходил с нулём, а карточка в Telegram сообщала об успешном сборе — три дня подряд, пока сайт молча терял все строки поиска (23.08.2026).

Теперь fetchGsc и fetchGa4 в этом случае бросают исключение: оно попадает в errors[] снимка, прогон завершается кодом 1, и сторож в кроне видит настоящее состояние. Тесты — src/auth-failure.test.ts.

Канон env-переменных

| Переменная | Для чего | | --- | --- | | GOOGLE_SERVICE_ACCOUNT_JSON / GOOGLE_APPLICATION_CREDENTIALS | сервис-аккаунт для GA4 (inline JSON или путь). Email аккаунта — Viewer в GA4 Admin | | GOOGLE_OAUTH_CLIENT_ID / _SECRET / _REFRESH_TOKEN | пользовательский OAuth — ТОЛЬКО GSC (analytics-scope Google заблокировал) | | GOOGLE_CLOUD_PROJECT | опц.: проект для квоты GSC (x-goog-user-project). Каноничное имя; GP-шный GOOGLE_QUOTA_PROJECT сведён сюда | | GA4_PROPERTY_ID | GA4 property | | SC_SITE_URL | GSC property (URL-prefix с / или sc-domain:...) | | YANDEX_OAUTH_TOKEN | общий для Метрики и Вебмастера | | YANDEX_METRICA_ID | счётчик Метрики | | YANDEX_WEBMASTER_USER_ID | опц.: иначе тянется из /v4/user | | YANDEX_WEBMASTER_HOST_ID | URL-кодированный хост: https%3A<host>%3A443 | | METRICA_GOAL_PLAY_ID / _SEARCH_ID | опц., только для analytics audit |

Дубли METRIKA_COUNTER_ID / GSC_SITE / WEBMASTER_USER_ID (были в one-q setup.ts) сведены к рантайм-именам выше.

Грабли внешних API

Факты о чужих API, которые не ловятся ни типами, ни тестами. Живут здесь, а не в доках потребителей: код, который на них наступает, — в этом пакете, а потребителей три.

  • Метрика ym:s:goalID отдаёт числовой id цели СТРОКОЙ в поле .name; поля .id в ответе нет. Читаешь .id — получаешь undefined, цель не сопоставляется, и цифры по всем целям молча становятся нулями. Держится в src/metrica.tsmapGoalEvents().
  • Вебмастер search-queries/popular игнорирует date_from/date_to и всегда отдаёт своё скользящее окно в 7 дней (границы честно возвращает в полях ответа). Штамповать его сумму датой снимка — значит выдавать недельные числа за дневные. Дневные клики и показы берутся из search-queries/all/history, popular оставлен для списка запросов и средней позиции. Признак, что наступил: два соседних дня дают идентичные числа.
  • search-queries/last-week у Вебмастера не существует — 404, не заглушка.
  • GSC требует x-goog-user-project, когда токен подписан общим client id gcloud (gcloud auth application-default login). Без него — 403 SERVICE_DISABLED с текстом про Application Default Credentials, хотя токен валиден. Лечится переменной GOOGLE_CLOUD_PROJECT (см. канон env выше), а не перевыпуском OAuth-клиента.

Per-site: запись в БД (persistence)

fetch-all пишет ТОЛЬКО JSON-снимок. Запись в PostgreSQL (была в one-q через @one-q/db) — забота консюмера: прочитай записанный JSON, либо импортируй фетчеры программно:

import { fetchGa4, fetchGsc } from '@mikitasazan/analytics';