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

normalize-fit-file

v1.2.0

Published

Turn Fit files from Garmin, Stryd, and other vendors into normalized JSON, YAML, or plain objects and compare them systematically

Readme

normalize-fit-file

normalize-fit-file turns FIT (.fit) binaries from Garmin, Stryd and other vendors into a normalized shape using JSON, YAML, or plain objects and compares them systematically.

Prerequisites

  • Node.js 18+ (for the published CLI and library).
  • Bun (optional; used in this repo to run TypeScript dev scripts without a prior build).

From the repo root:

bun install
# or: pnpm install / npm install

CLI (normalize-fit-file command)

After npm install normalize-fit-file (or pnpm/yarn/bun), fit-file-parser is installed automatically as a dependency. @garmin/fitsdk is an optional dependency (npm installs it when possible; it is only needed for parse-garmin / normalizeGarmin).

Subcommands

npx normalize-fit-file parse-ffp <file.fit> [options]
npx normalize-fit-file parse-garmin <file.fit> [options]
npx normalize-fit-file compare <fileA> <fileB> [options]
  • parse-ffp uses the bundled fit-file-parser dependency.
  • parse-garmin uses @garmin/fitsdk, declared as an optional dependency of this package (npm tries to install it alongside normalize-fit-file).
  • compare loads two already normalized files (JSON or YAML), compares field coverage and scalar values, and writes a report. Report keys use labels derived from each input filename (basename without extension)—for example race-garminOnly / race-ffpOnly and per-field mismatch rows keyed by those labels (see the header comment in src/cli/compare.ts).

Use npx normalize-fit-file help (or -h, --help) for usage. Bun users can run the same with bunx normalize-fit-file ….

Options: parse-ffp and parse-garmin

| Option | Meaning | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | -f, --format | Output format: json or yaml (default: json; can be inferred from -o when it is a file with .json, .yaml, or .yml). | | -o, --output <path> | Output file or directory. If omitted, writes <basename>.<format> in the current working directory (<basename> comes from the .fit filename). If -o is a directory, writes <dir>/<basename>.<format>. | | -r, --raw | Also write raw parser output next to the normalized file: same path with -raw inserted before the extension (e.g. activity.jsonactivity-raw.json). | | -s, --sample <N> | Keep every Nth record row (1-based: first kept, then every Nth). |

Options: compare

| Option | Meaning | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | -f, --format | Report format: json or yaml (default: json; inferred from -o when it is a file with a matching extension). | | -o, --output <path> | Report file or directory. Default report path: ./comparison-report.<format> in the current working directory. |

You must pass a .fit path to parse-ffp / parse-garmin; running without it exits with an error. compare requires two positional paths to normalized JSON/YAML files.

Keep personal activity files out of git (see .gitignore: e.g. sample-fits/, fits/*.fit).

The published npm package ships dist/ only—use your own .fit paths when running the CLI.

Parse your own FIT file (recommended path)

The default pipeline uses fit-file-parser plus normalization (normalizeFFP in src/parse-ffp.ts; exported from src/index.ts):

From the published package:

npx normalize-fit-file parse-ffp path/to/your/activity.fit

This writes activity.json (or activity.yaml with -f yaml) in the current directory unless you set -o.

YAML example:

npx normalize-fit-file parse-ffp path/to/your/activity.fit -f yaml -o ./out/

In this repository (Bun runs the router in TypeScript):

bun run parse:ffp -- path/to/your/activity.fit

Large files: downsample record rows while debugging:

npx normalize-fit-file parse-ffp path/to/activity.fit --sample 10
# repo (Bun):
bun run parse:ffp -- path/to/activity.fit --sample 10

That keeps every 10th record (see parseCliArgs).

Optional: Garmin SDK parse (reference / comparison)

npx normalize-fit-file parse-garmin path/to/your/activity.fit
# repo:
bun run parse:garmin -- path/to/your/activity.fit

Use the same -f, -o, -r, and -s options as parse-ffp.

Comparing two normalized files

After you have two normalized files (e.g. from Garmin SDK vs fit-file-parser), pass both paths to compare:

npx normalize-fit-file compare ./garmin-sdk-normalized.json ./fit-file-parser-normalized.json
# repo:
bun run compare -- ./garmin-sdk-normalized.json ./fit-file-parser-normalized.json

Default report: ./comparison-report.json. Example with explicit paths and YAML:

npx normalize-fit-file compare race-garmin.json race-ffp.yaml -o report.yaml

See followup-key-aliasing.md for how key naming relates to comparisons.

Use as a library (TypeScript / Node.js or Bun)

  1. Install normalize-fit-filefit-file-parser comes with it. @garmin/fitsdk is pulled in as an optional dependency when npm can install it; add it explicitly in your app if you need a specific version or if optional install was skipped.

  2. Named exports (see src/index.ts) include parsing and normalization:

    • normalizeFFP, parseFitBuffer, normalizeGarmin
    • Types: NormalizedFitData, WorkoutMetadata, SessionSummary, LapData, RecordData, FfpParsedFit, etc.
    • downsampleRecords, objectToHybrid
    • Garmin / Stryd key helpers: renameRowKeys, renameRowArray, snakeToCamelKey, renameStrydRowKeys, renameStrydRowArray, STRYD_LABEL_TO_CAMEL, and related types
  3. Typical import:

    import {
      normalizeFFP,
      parseFitBuffer,
      type NormalizedFitData,
    } from "normalize-fit-file";

    normalizeFFP returns a plain JavaScript object (NormalizedFitData) you can read, spread, and assign to like any other JSON-shaped data:

    import { readFile } from "node:fs/promises";
    import {
      normalizeFFP,
      parseFitBuffer,
      type NormalizedFitData,
    } from "normalize-fit-file";
    
    const fitPath = "path/to/your/activity.fit";
    const fitFileBuffer = await readFile(fitPath);
    const arrayBuffer = fitFileBuffer.buffer.slice(
      fitFileBuffer.byteOffset,
      fitFileBuffer.byteOffset + fitFileBuffer.byteLength,
    ) as ArrayBuffer;
    
    const raw = await parseFitBuffer(arrayBuffer);
    const data: NormalizedFitData = normalizeFFP(raw);
    
    // Plain object: metadata, deviceInfo, session, laps, records
    console.log(data.records.length, data.session.totalTimerTime);
    const edited: NormalizedFitData = {
      ...data,
      session: { ...data.session /* add or override fields */ },
    };

    With Bun, you can skip the Buffer conversion: await parseFitBuffer(await Bun.file(fitPath).arrayBuffer()), then normalizeFFP as above.

  4. Read the .fit file into an ArrayBuffer, parse with parseFitBuffer, then pass the result to normalizeFFP(raw).

Field naming pipeline:

  1. Pass 1 — src/ffp-garmin-field-names.ts: FIT snake_case → Garmin-style camelCase.
  2. Pass 2 — src/ffp-stryd-second-pass.ts: Stryd (and similar) display strings → camelCase; explicit map in STRYD_LABEL_TO_CAMEL.

Scripts (summary)

| Command | Purpose | | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | npx normalize-fit-file parse-ffp <file.fit> [opts] | Published CLI: FIT → normalized JSON/YAML (.fit path required). | | npx normalize-fit-file parse-garmin <file.fit> [opts] | Published CLI: Garmin SDK parse (.fit path required). | | npx normalize-fit-file compare <fileA> <fileB> [opts] | Published CLI: compare two normalized files → report (default ./comparison-report.<format>). | | bun run parse:ffp -- <file.fit> [opts] | Repo dev: same via Bun + src/cli/normalize-fit-file.ts. | | bun run parse:garmin -- <file.fit> [opts] | Repo dev: Garmin path. | | bun run compare -- <fileA> <fileB> [opts] | Repo dev: compare (pass two normalized files). | | bun test | Unit tests. Optional smoke: set FIT_SMOKE_FIXTURE to a local .fit (gitignored). | | pnpm run typecheck | tsc --noEmit. | | pnpm run build | Build dist/ (library + normalize-fit-file CLI). |

Further reading