Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Weekly updates for the npm dependencies (pnpm at the root, npm in examples/) and for GitHub
# Actions. Security updates are separate and not delayed by the cooldown.
version: 2
updates:
- package-ecosystem: npm
directories: [/, /examples]
schedule:
interval: weekly
# Mirrors minimum-release-age in .npmrc: only versions published at least 7 days ago.
cooldown:
default-days: 7
groups:
# Minor and patch updates in one PR; each major update gets its own.
npm:
update-types: [minor, patch]
ignore:
# @types/node tracks the Node.js floor in engines, not the latest Node.js (CONTRIBUTING.md,
# "Toolchain pins"): it moves only when the floor is raised.
- dependency-name: '@types/node'
update-types: ['version-update:semver-major']
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
actions:
patterns: ['*']
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ node_modules
dist
coverage

# Environment variables
# Local env and credentials
.env
.env.*
!.env.example
*.local
*.pem
*.key
.claude

# Examples use npm (package-lock.json is tracked); ignore a stray pnpm lockfile
Expand Down
2 changes: 1 addition & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
.husky
.gitignore
.nvmrc
CLAUDE.md

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: You can probably also add:

  • CONTRIBUTING.md
  • SECURITY.md
    ?

Not sure if this needs to be part of the package that you install because usually nobody is looking at node_modules and they refer to github repo

AGENTS.md
eslint.config.mjs
prettier.config.mjs
tsconfig.json
Expand Down
20 changes: 20 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# glassnode-api-ts-client: agent instructions

Read [CONTRIBUTING.md](CONTRIBUTING.md) first: setup, commands, the version and changelog rules,
and the design notes all live there. Before opening a PR, run the checks in its "Run the full
local check list" step.

## Don't

- Edit `test/fixtures/contract/` by hand: it holds recorded real API responses. A contract test
failing there is API drift: fix the schema, never the fixture or the test.
- Run `scripts/record-fixtures.mjs`: it calls the live API with a real key and spends quota. Only
a maintainer re-records the fixtures.
- Bump `@types/node` past the Node.js floor (`^22`), TypeScript to 7.x, or TypeScript past what
`typedoc` lists: see "Toolchain pins" in CONTRIBUTING.md.
- Write extensionless or `.ts` relative imports in `src/`: they are `./foo.js`.
- Add a `browser` field or a `browser` condition in `exports`, even when `publint` suggests it.
- Add `.transform()` to `GlassnodeConfigSchema`, or set `tag` in `package.json` `publishConfig`.
- Expose the API key: logged URLs mask it, and hook payloads and error messages never carry it.
- Rename the `test` or `compat-node-floor` CI jobs (the rulesets require them by name), or
reference an action by tag instead of a full commit SHA.
322 changes: 0 additions & 322 deletions CLAUDE.md

This file was deleted.

146 changes: 139 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Project layout:
- `examples/` - runnable usage examples (their own `package.json`)
- `scripts/` - the Node-floor smoke test, the contract fixture recorder and the release tooling
- `typecheck/x402-node16/` - a consumer type-check fixture
- `NOTICE` - attribution; listed in `package.json` `files` so it ships in the npm tarball, as
Apache-2.0 section 4(d) requires

Add or update tests for what you change. The public API is what `src/index.ts` and `src/x402.ts`
export; if you change it, update `README.md` to match and give every new export a doc comment.
Expand All @@ -50,13 +52,15 @@ newer than Node 22 in `src/`.

### 3. Bump the version and add a changelog entry

Every change bumps `version` in `package.json` and adds an entry at the top of `CHANGELOG.md`
describing it. Follow [semver](https://semver.org/):
Every change to the published package bumps `version` in `package.json` and adds an entry at the
top of `CHANGELOG.md` describing it. Docs-, CI- and tooling-only changes (README, CONTRIBUTING,
workflows, tests, lint and editor config) don't: they publish nothing, and the top `CHANGELOG.md`
heading must keep naming the current version. Follow [semver](https://semver.org/):

- **Major**: breaking changes (removed or renamed exports, changed method signatures, changed
defaults, tightened or widened response schemas, a higher minimum Node.js version).
- **Minor**: new features, methods or config options that are backward compatible.
- **Patch**: bug fixes, docs and internal refactors with no API change.
- **Patch**: bug fixes and internal refactors with no API change.

The README's [Stability and versioning](./README.md#stability-and-versioning) section spells out
what counts as breaking.
Expand Down Expand Up @@ -88,6 +92,8 @@ pnpm exec tsc -p tsconfig.test.json --noEmit
pnpm exec tsc -p tsconfig.examples.json
pnpm run build
pnpm run build:browser
pnpm exec tsc -p typecheck/x402-node16/tsconfig.json
node scripts/check-changelog-heading.mjs main
npx prettier --check .
pnpm run docs
pnpm dlx publint
Expand Down Expand Up @@ -145,16 +151,142 @@ restores it with `npm dist-tag add glassnode-api@<newest version> latest`.

`test/fixtures/contract/` holds real API responses recorded by `scripts/record-fixtures.mjs`.
Never hand-edit them; re-record them. That needs an API key, so it is only done to refresh the
fixtures. See [Recording contract fixtures](./README.md#recording-contract-fixtures) in the README.
fixtures. A contract test that fails against a fixture is real API drift: fix the schema, not the
fixture or the test (after a re-recording, the README lists the few literal values in
`test/contract.spec.ts` that may legitimately need updating). See
[Recording contract fixtures](./README.md#recording-contract-fixtures) in the README.

## Security

- Report vulnerabilities privately, through GitHub's private vulnerability reporting on this
repository (the **Security** tab → **Report a vulnerability**). Never open a public issue for
them.
- Report vulnerabilities privately as described in [SECURITY.md](./SECURITY.md). Never open a
public issue for them.
- Never commit API keys or other secrets, including in fixtures, examples, `.env` files or test
output.

## Design notes

Decisions that are easy to undo by accident. Read the relevant one before changing the toolchain,
the build or the workflows.

### Node.js versions

Two baselines, kept separate:

- **Consumers** need Node.js >= 22 (`package.json` `engines`; raised from 18 in 1.0). `src/` uses
only universal APIs plus global `fetch` and also runs in browsers, so the floor is no licence to
use Node-only APIs.
- **Developers** run Node.js 24 (`.nvmrc`, the main CI and publish jobs). Vitest 5 needs
Node.js >= 22.12; that is dev-only and never reaches consumers. The CI `compat-node-floor` job
proves the consumer floor: on Node 22 it builds, `require`s the CJS entry and runs
`scripts/smoke-timeout.mjs`.

### Toolchain pins

- **`@types/node` is pinned to the floor** (`^22`), not the dev runtime, so the compiler rejects
APIs newer than Node 22. It is approximate (CI runs the latest 22.x and `@types/node` is
`^22.20`, so an API added after 22.0 is not caught). Bump its major only when the minimum
supported Node.js is intentionally raised, which is a breaking change (major release and
`engines` bump). Dependabot ignores its major updates for this reason.
- **TypeScript stays on 6.x.** As of 1.0.0, `typescript-eslint` declares its `typescript` peer as
`<6.1.0`, so TypeScript 7 breaks `pnpm run lint` (in CI and in the pre-commit hook). Move to 7.x
only once a `typescript-eslint` release accepts it.
- **`typedoc` also gates TypeScript:** its `typescript` peer lists explicit minors (up to 6.0.x on
0.28.20), so any TypeScript bump, even to 6.1, needs a `typedoc` release that lists it, or
`pnpm run docs` (and CI) break.

### API reference (TypeDoc)

- Entry points are `src/index.ts` (`glassnode-api`) and `src/x402.ts` (`glassnode-api/x402`),
named by their `@module` comments; README.md is the landing page. Config in `typedoc.json`,
output in `api-docs/` (not committed, not published to npm).
- `typedoc-plugin-zod` expands `z.infer`/`z.input` aliases, so Zod-derived types such as
`MetricMetadata` and `GlassnodeConfig` render as objects with their field comments.
- Every warning is an error (`treatWarningsAsErrors`; `notExported`, `invalidLink`, `invalidPath`
and `rewrittenLink` on). `notDocumented` is off: it only reports nested members of Zod objects
and `z.enum` literals, which cannot carry comments. Every top-level export and class member is
documented; keep it so.
- `.github/workflows/docs.yml` deploys the reference to GitHub Pages
(https://glassnode.github.io/glassnode-api-ts-client/) on every push to `main`. It needs the repo
setting Settings → Pages → Source: **GitHub Actions**.

### Build targets and packaging

- **CJS:** `tsc` → `dist/` (the `require` entry). **ESM:** `tsc -p tsconfig.esm.json` →
`dist/esm/`, unbundled with `zod` external (the `import` entry), plus a generated
`dist/esm/package.json` (`{"type":"module"}`). `exports` has per-condition `types`, checked by
`publint` and `@arethetypeswrong/cli` in CI. The package itself is `"type": "commonjs"`.
- **Browser:** Rollup (`tsconfig.browser.json`) builds minified UMD
(`dist/glassnode-api.umd.min.js`, global `GlassnodeAPI`) and ESM
(`dist/glassnode-api.esm.min.js`) bundles from `src/index.ts`, with `zod` bundled in and
`src/x402.ts` left out. Source maps are `hidden` and not published. `module` points at the
unbundled `dist/esm/index.js`, not a bundle. `tsconfig.browser.json` uses
`moduleResolution: bundler` (not the deprecated `node`/`node10`); Rollup's node-resolve plugin
does the actual resolution.
- **No `browser` field.** `unpkg` and `jsdelivr` point at the UMD bundle so the bare CDN URL serves
it. There is deliberately no top-level `browser` field and no `browser` condition in `exports`:
the condition would send webpack 5, Vite and esbuild to the pre-minified bundle with `zod`
inlined instead of the tree-shakeable ESM build (the top-level field only affected
`exports`-unaware tools such as Browserify). Keep both out, even though `publint` suggests the
condition (#37).
- **`src/` imports end in `.js`** (`./foo.js` for `foo.ts`). `dist/esm/` is unbundled `tsc`
output, Node's ESM loader needs full extensions, and `tsc` never rewrites a `.js` specifier.
Extensionless specifiers only work behind a bundler or CommonJS. `.ts` specifiers with
`rewriteRelativeImportExtensions` were rejected: `tsc` leaves them as `.ts` in the emitted
`.d.ts`. Tools that run `src/` directly must map `.js` to `.ts`; ts-node needs
`experimentalResolver`, set in `examples/tsconfig.json`, and CI guards it by importing `../src`
through ts-node from `examples/`.
- **`GlassnodeConfigSchema` stays a plain `z.object`** (no `.transform()`), so `.shape`, `.pick`,
`.extend` and `.partial` keep working. Defaults that depend on other options (`maxRetries` is
`DEFAULT_MAX_RETRIES`, or `DEFAULT_X402_MAX_RETRIES` with `x402`) are applied in the constructor.
- TypeScript configs: `tsconfig.json` (CJS), `tsconfig.esm.json` (ESM), `tsconfig.browser.json`
(browser), `tsconfig.test.json` (tests and IDE), `tsconfig.examples.json` (type-checks
`examples/` against `src/` with the root dependencies), `examples/tsconfig.json` (ts-node, for
running the examples) and `typecheck/x402-node16/tsconfig.json` (a node16 consumer with
`skipLibCheck: false`).

### CI and workflows

- `ci.yml` runs on PRs into `main` and `release/**`, and `publish.yml` calls it as its `verify` job,
so a release runs the same checks. Its concurrency group cancels superseded runs only for PRs; a
run called by `publish.yml` is never cancelled.
- Its jobs are `test` (Node 24: the step-4 check list, `test:coverage` and the examples' ts-node
import guard) and `compat-node-floor` (Node 22). The rulesets require them by name, so renaming
a job means updating the rulesets; `compat-node-floor` is version-neutral so a floor bump only
changes its `node-version`.
- Every action is pinned by full commit SHA with a `# vX.Y.Z` comment, never by a movable tag. To
bump one, resolve the tag with `gh api repos/<owner>/<repo>/git/ref/tags/<tag>` (for an
annotated tag, dereference it with `gh api repos/<owner>/<repo>/git/tags/<sha>` to get the
commit), update SHA and comment together, and read the release notes for changed inputs or
defaults.
- Every workflow sets minimal `permissions:`: `contents: read` by default, and write access only on
the `publish` job, which needs it.

### Publishing setup

- `publish.yml` publishes with **npm Trusted Publishing (OIDC)** and provenance: there is no
`NPM_TOKEN` secret. The Trusted Publisher for `glassnode-api` on npmjs.com names repo
`glassnode/glassnode-api-ts-client`, workflow `publish.yml` and environment `npm`.
- The `npm` environment (Settings → Environments) has the maintainer as required reviewer and
allows deployments from `main` only. Only the `publish` job has write permissions:
`id-token: write` (OIDC) and `contents: write` (tag and Release, via `GITHUB_TOKEN`).
- The `release` job runs `scripts/release-state.sh`: only an E404 from `npm view` means "not
published"; any other failure fails the job and never publishes. `publish` re-checks npm before
publishing. An existing tag on the same commit or an existing Release is fine; a tag on another
commit is left alone with a warning. If the version is on npm from this very commit (npm records
its `gitHead`), a re-run only (re)creates the missing tag and Release.
- npm CLI >= 11.5.1 does the OIDC exchange, so the workflow installs the latest npm and runs
`npm publish`, not `pnpm publish` (which uses the setup-node placeholder token and gets a 404).
The setup-node `.npmrc` is overwritten first so npm authenticates via OIDC.
- The repo `.npmrc` sets `ignore-scripts=true`, so `npm publish` skips `prepublishOnly`: the
workflow runs `build` and `build:browser` itself. The `pnpm/action-setup` steps set
`npm_config_ignore_scripts: 'false'` because the standalone pnpm needs its own setup script.
- **Dist-tag guard** (`scripts/release-plan.mjs`, SemVer 2.0.0 precedence, no dependencies):
`npm publish` always gets an explicit `--tag`: `latest`, `next` for a prerelease, or
`backport-<major>.<minor>` for a version below the current `latest`. Not `v<major>.<minor>`: npm
rejects a tag that is a valid semver range. `release-state.sh` fails on any other tag.
`package.json` `publishConfig` deliberately sets **no** `tag`: `tag: "latest"` there would force
`latest` on a manual publish or an npm that doesn't let `--tag` override it. Keep it out.

## License of contributions

This project is licensed under the [Apache License 2.0](./LICENSE). Under its Section 5, any
Expand Down
21 changes: 21 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Security policy

## Contents

- [Reporting a vulnerability](#reporting-a-vulnerability)
- [Supported versions](#supported-versions)

## Reporting a vulnerability

Don't open a public issue. Report it privately:

- **GitHub:** the repository's **Security** tab → **Report a vulnerability**
([private vulnerability reporting](https://github.com/glassnode/glassnode-api-ts-client/security/advisories/new)).
- **Email:** security@glassnode.com, if you can't use GitHub.

Include the affected version, steps to reproduce and the impact. Glassnode has no bug bounty
program.

## Supported versions

Only the latest release receives security fixes.
Loading