This guide describes how to set up a local development environment for
@adguard/github-stats, run the CLI tools against real data, run tests
and linters, and contribute changes back.
For a high-level overview of what the tool does and how end users install it, see README.md. For coding conventions, architecture rules, and AI agent guidance, see AGENTS.md.
- Prerequisites
- Getting Started
- Development Workflow
- Common Tasks
- Debugging
- Troubleshooting
- Additional Resources
Install the following tools before you start:
- Node.js 22.17.0 or newer — required by the runtime; the codebase uses
ES Modules in
src/and Rollup-bundled CommonJS inbin/. - Yarn 1.22.x (Classic) — the committed lockfile is
yarn.lockand every workflow under .github/workflows/ callsyarn installandyarn build. Yarn 2+ (Berry) is not supported. - Git — any recent version.
- A GitHub Personal Access Token with
public_reposcope for authenticated polling. Without a token the GitHub Events API is limited to 60 requests per hour. - A Slack bot token (
xoxb-...) and target channel id, only if you want to exercisegithub-publishlocally.
Recommended Editor:
- VS Code with the ESLint extension.
git clone https://github.com/AdguardTeam/AdGuardFiltersStats.git
cd AdGuardFiltersStatsyarn installThis installs both runtime and dev dependencies and sets up the Husky pre-commit hook (see Pre-commit hooks).
Copy .env-example to .env in the repository root and
fill in the values you need:
cp .env-example .env.env is gitignored. The file is loaded by dotenv at the top of each
CLI entry script in src/.
The supported variables are:
| Variable | Required for | Description |
|---|---|---|
COLLECTION_PATH |
poll, stats, publish |
Directory where daily JSONL event files and metadata sidecars are stored. |
REPO |
poll, stats, publish |
Target repository in {owner}/{repo_name} form. |
GITHUB_TOKEN |
poll (recommended), stats, publish |
GitHub PAT. Without it, requests are limited to 60/hour. |
SINCE |
stats, publish |
ISO 8601 timestamp (YYYY-MM-DDTHH:MM:SSZ). Lower bound of the stat window. |
UNTIL |
stats, publish |
ISO 8601 timestamp. Upper bound of the stat window. Defaults to now. |
SLACK_OAUTH_TOKEN |
publish |
Slack bot token (xoxb-...). |
SLACK_CHANNEL_ID |
publish |
Slack channel id to post to. |
When you add a new variable, also update .env-example and the relevant section in README.md.
The bin/ directory contains Rollup-generated CommonJS bundles with
shebangs. Source changes in src/ are not picked up by yarn poll,
yarn stats, or yarn publish until you rebuild:
yarn buildRollup writes:
bin/github-poll.jsbin/github-stats.jsbin/github-publish.js- hashed shared chunks (e.g.
bin/fs-utils-*.js,bin/prepare-stats-*.js)
Do not hand-edit any file under bin/ — re-run yarn build instead.
Commit the regenerated bin/ artifacts together with the source change.
After yarn build, invoke any of:
yarn poll # node ./bin/github-poll.js
yarn stats # node ./bin/github-stats.js
yarn publish # node ./bin/github-publish.jsEach script reads its configuration from the environment (or .env) as
described above.
- Create a feature branch off the default branch.
- Keep changes focused; one logical change per PR.
- Match the existing commit message style (short imperative subject).
- Before opening a PR:
- Run
yarn lintto check for code style issues. - Run
yarn testto ensure all tests pass. - Run
yarn buildand commit the regeneratedbin/artifacts if any source undersrc/changed. - Update README.md, .env-example, examples/, AGENTS.md, and CHANGELOG.md when relevant (see the "Configuration & Documentation" section in AGENTS.md).
- Run
- Source under src/ and tests/ is authored as ES
Modules. Babel + Rollup transpile to CommonJS for
bin/. - Linting is the only static-analysis gate: ESLint with
eslint-config-airbnb-base, configured in .eslintrc. - There is no Prettier and no TypeScript. Match the surrounding style and let ESLint catch deviations.
- Full conventions (naming, imports, JSDoc, layered architecture, etc.) are documented in AGENTS.md — read it before contributing non-trivial changes.
Useful commands:
yarn lint # lint the entire repo
yarn lint --fix # auto-fix where possibleThe project uses Jest with babel-jest. Tests live under
tests/ and mirror the src/ layout. Static fixtures live in
tests/test-files/ — reuse them where possible
and add new fixtures rather than mutating shared ones.
Run all tests:
yarn testRun a single test file:
yarn test tests/publish-utils/repo-stat-to-blocks.test.jsRun tests matching a name pattern:
yarn test -t "repo stat"Watch mode while developing:
yarn test --watchFocus testing on pure functions in src/prepare-stats/ and
src/publish-utils/format-utils/. Mock network and filesystem
boundaries (@octokit/core, @slack/web-api) with
jest.mock rather than hitting them live. New behavior MUST come with
at least one success-path test and one failure-path test.
yarn buildConfiguration lives in rollup.config.js and babel.config.js. The build doubles as a parse/type sanity check; treat a failed build the same as a failed lint.
Husky installs a pre-commit hook at .husky/pre-commit that runs:
yarn lint-staged
yarn testlint-staged lints staged files matching {src,tests,scripts}/**/*.js.
Do not bypass the hook with git commit --no-verify unless you have a
specific reason and call it out in the PR description.
Authenticated polling against a public repository:
env \
COLLECTION_PATH=stats-data \
GITHUB_TOKEN=ghp_xxx \
REPO=AdguardTeam/AdguardFilters \
yarn pollRe-running on the same day is safe — events are deduplicated and the
metadata sidecar (stats-data/YYYY-MM-DD-metadata.json) is updated in
place.
env \
COLLECTION_PATH=stats-data \
REPO=AdguardTeam/AdguardFilters \
SINCE=2025-05-01T00:00:00Z \
UNTIL=2025-05-25T15:00:00Z \
yarn statsUse a private test channel and a dedicated bot token. Never publish to production channels from a dev machine.
env \
SLACK_OAUTH_TOKEN=xoxb-xxx \
SLACK_CHANNEL_ID=Cxxxxxxxx \
COLLECTION_PATH=stats-data \
REPO=AdguardTeam/AdguardFilters \
SINCE=2025-05-01T00:00:00Z \
UNTIL=2025-05-02T00:00:00Z \
yarn publishFollow the rules in AGENTS.md → Dependency Management:
- Pin the exact version (no
^or~) inpackage.json. - Use the latest stable release from npm.
- Justify the addition; prefer Node built-ins or existing helpers.
yarn add <pkg>@<exact-version>
yarn add --dev <pkg>@<exact-version>After adding, run yarn build and yarn test to confirm nothing
regressed.
- Read it from
process.envat the top of the relevant CLI entry in src/, validating it and exiting non-zero on missing required values. - Add it to .env-example with a placeholder value.
- Document it in README.md and in the table in Step 3 of this guide.
- Update examples/ workflows if the new variable is needed in CI.
- Create a
*.test.jsfile under tests/ that mirrors the path of the module under test. - Add fixtures to tests/test-files/ when helpful.
- Mock external boundaries with
jest.mock. - Run
yarn testand confirm the suite passes.
The CLI entries source-map back to src/. A minimal launch
configuration in .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug github-poll",
"program": "${workspaceFolder}/bin/github-poll.js",
"envFile": "${workspaceFolder}/.env",
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"]
}
]
}Run yarn build once before starting the debugger so bin/ reflects
your latest changes.
{
"type": "node",
"request": "launch",
"name": "Debug Jest (current file)",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand", "${relativeFile}"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}You can also drop debugger; statements anywhere in src/ or
tests/ — Babel preserves them.
For ad-hoc logging, the project standard is plain console.log /
console.warn / console.error with an inline
// eslint-disable-next-line no-console comment at each call site, as
already used in the codebase.
API rate limit exceeded from github-poll
You are running unauthenticated. Set GITHUB_TOKEN in .env (PAT with
public_repo scope is enough). The unauthenticated limit is 60
requests per hour; authenticated is 5000.
Stats are missing recent events
The GitHub Events API only returns the 300 most recent events per
repository, and daily JSONL files older than EVENT_EXPIRATION_DAYS
(30) are pruned during stat preparation. Run github-poll more
frequently from CI to avoid gaps.
yarn poll / yarn stats / yarn publish does not reflect my
changes
These scripts run the bundled files under bin/. Re-run yarn build
after any change to src/.
Pre-commit hook does not run
Re-install dependencies (yarn install) so Husky re-installs the git
hook. Verify .husky/pre-commit is executable
(chmod +x .husky/pre-commit).
yarn build fails with a Rollup chunk error
Stale or hand-edited files under bin/ (especially the hashed
fs-utils-*.js / prepare-stats-*.js chunks) can confuse incremental
builds. Delete bin/ and re-run yarn build.
Slack publish fails with not_in_channel
Invite the bot user (whose token is in SLACK_OAUTH_TOKEN) to the
target channel — /invite @your-bot from inside the channel.
Slack publish fails with invalid_blocks
Block Kit payloads above Slack's per-message limits are pruned by the
helpers under src/publish-utils/format-utils/.
If you changed a formatter, add a unit test under
tests/publish-utils/ reproducing the failing
input.