Skip to content

Commit c099665

Browse files
authored
Merge pull request #1 from maxmind/greg/eng-4043
ENG-4043: Add device tracking library
2 parents d08a8c4 + 072f295 commit c099665

26 files changed

+4977
-10
lines changed

.githooks/pre-commit

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/bin/sh
2+
precious lint --staged

.github/dependabot.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: npm
4+
directory: /
5+
schedule:
6+
interval: daily
7+
time: '14:00'
8+
open-pull-requests-limit: 20
9+
groups:
10+
minor-and-patch:
11+
patterns:
12+
- '*'
13+
update-types:
14+
- minor
15+
- patch
16+
cooldown:
17+
default-days: 7
18+
- package-ecosystem: github-actions
19+
directory: /
20+
schedule:
21+
interval: daily
22+
time: '14:00'
23+
cooldown:
24+
default-days: 7
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: "Code scanning - action"
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
schedule:
9+
- cron: '0 3 * * 6'
10+
11+
jobs:
12+
CodeQL-Build:
13+
14+
runs-on: ubuntu-latest
15+
16+
permissions:
17+
security-events: write
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
22+
with:
23+
fetch-depth: 2
24+
persist-credentials: false
25+
26+
- name: Initialize CodeQL
27+
uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
28+
29+
- name: Autobuild
30+
uses: github/codeql-action/autobuild@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
31+
32+
- name: Perform CodeQL Analysis
33+
uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10

.github/workflows/lint.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Precious
2+
on:
3+
pull_request:
4+
push:
5+
branches:
6+
- main
7+
schedule:
8+
- cron: '3 0 * * SUN'
9+
workflow_dispatch:
10+
permissions:
11+
contents: read
12+
jobs:
13+
precious:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
17+
with:
18+
fetch-depth: 0
19+
persist-credentials: false
20+
- uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
21+
- run: pnpm install --frozen-lockfile
22+
- name: Fetch base ref
23+
if: ${{ github.event.pull_request }}
24+
run: git fetch origin "${BASE_REF}"
25+
env:
26+
BASE_REF: ${{ github.base_ref }}
27+
- name: Select files
28+
id: select-files
29+
run: |
30+
if [[ -n "${PR_NUMBER}" ]]; then
31+
echo "precious-args=--git-diff-from origin/${BASE_REF}" >> "$GITHUB_OUTPUT"
32+
else
33+
echo 'precious-args=--all' >> "$GITHUB_OUTPUT"
34+
fi
35+
env:
36+
PR_NUMBER: ${{ github.event.pull_request.number }}
37+
BASE_REF: ${{ github.base_ref }}
38+
- name: Lint files
39+
run: precious lint ${PRECIOUS_ARGS}
40+
env:
41+
PRECIOUS_ARGS: ${{ steps.select-files.outputs.precious-args }}

.github/workflows/release.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Release
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
push:
7+
branches:
8+
- main
9+
release:
10+
types: [published]
11+
12+
permissions: {}
13+
14+
jobs:
15+
build:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
19+
with:
20+
persist-credentials: false
21+
- uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
22+
- run: pnpm install --frozen-lockfile
23+
- run: pnpm test
24+
- run: pnpm run lint
25+
- run: pnpm run build
26+
27+
publish:
28+
needs: build
29+
if: github.event_name == 'release' && github.event.action == 'published'
30+
runs-on: ubuntu-latest
31+
environment: npm
32+
permissions:
33+
contents: write
34+
id-token: write
35+
steps:
36+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
37+
with:
38+
persist-credentials: false
39+
- uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
40+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
41+
with:
42+
registry-url: 'https://registry.npmjs.org'
43+
- run: pnpm install --frozen-lockfile
44+
- run: pnpm run build
45+
- run: pnpm publish --provenance --no-git-checks
46+
env:
47+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.github/workflows/test.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Run tests
2+
on:
3+
pull_request:
4+
push:
5+
branches:
6+
- main
7+
schedule:
8+
- cron: '3 2 * * SUN'
9+
permissions: {}
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
15+
with:
16+
persist-credentials: false
17+
- uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
18+
- run: pnpm install --frozen-lockfile
19+
- run: pnpm test --coverage
20+
- run: pnpm run build

.github/workflows/zizmor.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: GitHub Actions Security Analysis with zizmor
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["**"]
8+
9+
permissions: {}
10+
11+
jobs:
12+
zizmor:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
security-events: write
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
19+
with:
20+
persist-credentials: false
21+
22+
- name: Run zizmor
23+
uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0

.precious.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[commands.eslint]
2+
type = "lint"
3+
cmd = ["pnpm", "exec", "eslint"]
4+
invoke = "once"
5+
include = ["src/**/*.ts"]
6+
ok-exit-codes = 0
7+
8+
[commands.tsc]
9+
type = "lint"
10+
cmd = ["pnpm", "exec", "tsc", "--noEmit"]
11+
invoke = "once"
12+
path-args = "none"
13+
include = ["src/**/*.ts", "tsconfig.json"]
14+
ok-exit-codes = 0
15+
16+
[commands.prettier]
17+
type = "both"
18+
cmd = ["pnpm", "exec", "prettier", "--parser", "typescript"]
19+
lint-flags = ["--check"]
20+
tidy-flags = ["--write"]
21+
path-args = "absolute-file"
22+
include = ["src/**/*.ts"]
23+
ok-exit-codes = 0

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist/
2+
node_modules/
3+
pnpm-lock.yaml

CLAUDE.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# @maxmind/device-tracking
2+
3+
A thin TypeScript loader that validates inputs,
4+
dynamically imports a remote fingerprinting module from MaxMind's servers, and
5+
returns a device tracking token. Runs in the browser.
6+
7+
## Commands
8+
9+
```sh
10+
pnpm test # Jest (ESM via --experimental-vm-modules)
11+
pnpm test:watch # Jest in watch mode
12+
pnpm run lint # ESLint + tsc --noEmit
13+
pnpm run build # Clean build to dist/
14+
pnpm run prettier:ts # Format src/**/*.ts
15+
```
16+
17+
Run a single test file:
18+
19+
```sh
20+
pnpm test -- src/loader.spec.ts
21+
```
22+
23+
Jest prints an `ExperimentalWarning` about VM Modules on every run. This is
24+
expected and harmless.
25+
26+
## Architecture
27+
28+
```
29+
src/
30+
index.ts # Public API: trackDevice() with input validation
31+
loader.ts # Singleton module loader with caching and timeout
32+
dynamic-import.ts # Thin wrapper around import() for test mocking
33+
types.ts # TrackDeviceOptions, TrackResult interfaces
34+
*.spec.ts # Co-located test files
35+
```
36+
37+
- `index.ts` is the only public entry point (`package.json` exports map).
38+
- `loader.ts` caches module promises per-host; clears cache on failure for retry.
39+
- `dynamic-import.ts` exists solely to make `import()` mockable in Jest.
40+
- Tests use `jest.unstable_mockModule` (ESM-compatible mocking).
41+
42+
## Conventions
43+
44+
- **ESM only**`"type": "module"` in package.json, `.js` extensions in imports.
45+
- **Strict TypeScript**`strict: true`, target ES2022, module Node16.
46+
- **Prettier** — single quotes, trailing commas (es5).
47+
- **Formatting separate from logic** — keep style-only changes in their own commits.
48+
- **`fixup!` commits** — prefix fixup commits with `fixup! <original subject>` for autosquash.
49+
- **Error messages include context** — URLs, received values, types.
50+
- **`@internal` JSDoc tag** — marks exports that exist only for testing (e.g. `resetModuleCache`).
51+
52+
## Testing notes
53+
54+
- Test environment is jsdom (browser globals).
55+
- Tests co-locate next to source files (`*.spec.ts`).
56+
- The `moduleNameMapper` in jest.config.js strips `.js` extensions for ts-jest.
57+
- Loader tests use real dynamic import (which fails in Node) to exercise error paths.
58+
- Mocked-module tests use `jest.unstable_mockModule` + dynamic `import()` to get fresh modules.
59+
- Fake timers are used for timeout tests — always call `jest.useRealTimers()` in `afterEach`.
60+
61+
## Tooling
62+
63+
- **mise** manages Node, pnpm, and precious versions (see `mise.toml`).
64+
- **precious** is the code-quality runner (config: `.precious.toml`).
65+
- **Git hooks** live in `.githooks/`. Enable with: `git config core.hooksPath .githooks`
66+
- GitHub Actions: test, lint, CodeQL, zizmor, release workflows.

0 commit comments

Comments
 (0)