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

sarif-to-comment

v0.2.1

Published

Write, inspect and publish SARIF 2.1.0 findings as one GitHub draft pull request review, with durable, never-duplicating delivery.

Readme

sarif-to-comment

Write, inspect and publish SARIF 2.1.0 findings as one GitHub draft pull request review:

  • general feedback in the review body;
  • findings on changed lines as inline comments;
  • supported fixes as native suggestions.

It is sent in a single create-review request.

The SARIF can come from any analyzer. Or you can write it yourself, with no analyzer:

  • create a document and add findings on lines or line ranges;
  • add the changes you staged with Git as suggested fixes on those findings;
  • inspect the result before publishing.

Every step reads and writes ordinary SARIF and is optional. SARIF from an analyzer can be inspected, given staged changes or published directly, without any setup step.

The scope is deliberately narrow:

  • One-way. SARIF goes to GitHub once. The tool never updates, reconciles, submits, restores or deletes a review afterwards. Supplying a new SARIF document (with a new state path) creates a separate review.
  • Drafts only. The review is created pending. A person submits it on GitHub.
  • Whole review or nothing. If any finding can't be published faithfully, nothing is published, and the tool explains why.
  • Never duplicated. A durable state file makes retries confirm the existing review instead of creating another.

Requires Node.js 22 or later. There are two surfaces, the library (SARIF in memory) and the CLI (SARIF files); both run the same code.

Documentation

These documents are included in the package. The links open them on unpkg (for the latest published version) and need no access to the source repository.

  • Getting started:
    • credentials, the reviewed commit, the source root and the state path;
    • complete library and CLI examples for publishing an analyzer's SARIF, and for writing a review yourself from staged changes;
    • how to handle every outcome.
  • API reference: generated by API Documenter from the package's TypeScript declarations.
  • Changelog: release notes produced by Changesets.

After installation, the same files are in node_modules/sarif-to-comment/docs/ and node_modules/sarif-to-comment/CHANGELOG.md.

Installation

npm install sarif-to-comment
npx sarif-to-comment --help

The package ships:

  • the library (CommonJS, usable from ES modules) with TypeScript declarations;
  • the CLI;
  • the vendored official SARIF schema;
  • the documentation above.

It has three runtime dependencies (ajv, ajv-draft-04, ajv-formats).

Quick start: library

SARIF is passed as an in-memory object; no temporary file is needed.

import { publishSarifReview } from 'sarif-to-comment';

const outcome = await publishSarifReview({
  sarif, // the parsed SARIF log (a plain JSON object)
  destination: { owner: 'acme', repo: 'widgets', pullNumber: 42 },
  reviewedCommit: 'c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de', // full 40-character SHA
  statePath: '/var/lib/my-linter/reviews/acme-widgets-42-run-1817.json', // absolute; keep it
  token: process.env.GH_TOKEN, // a personal access token or user token
  // sourceRootUri: 'file:///home/ci/work/widgets/', // optional: repo root in the producer's file system
  // oldSourceCommit: '<full SHA>',                  // optional: see "Old-side source"
  // options: { ignoreApprovalHold: true },          // optional: see "Approval hold"
});

console.log(outcome.markdown); // always a human-readable explanation
if (outcome.status === 'published') console.log(outcome.review.url);

status is one of:

  • published: the publication is complete, and review has the review's id and url. A repeated call answers from the state file without contacting GitHub, so a person may since have submitted, edited or deleted the review; the tool does not check.
  • blocked: nothing was written anywhere.
  • uncertain: retry with the same statePath.
  • rejected: GitHub refused the request; it is never resent.

The contract is status plus markdown, and review or statePath where listed. Internal diagnostic codes are not part of it and may change. The getting-started guide has a complete, runnable version of this example with outcome handling.

The promise rejects for:

  • invalid input (a TypeError, before any network request);
  • a corrupt state file, or a state path reused for different input;
  • operational failures, such as a network error or an unreachable pull request.

A rejection never contains the token.

The sarif value is copied when the call starts, so changing your object afterwards has no effect. Getters are never run. The copy refuses cycles and values JSON can't represent (functions, undefined, NaN, class instances and so on), rather than silently dropping them.

Quick start: CLI

GH_TOKEN=... npx sarif-to-comment \
  --sarif results.sarif \
  --repo acme/widgets --pull 42 \
  --commit c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de \
  --state /var/lib/my-linter/reviews/acme-widgets-42-run-1817.json
  • Optional flags: --source-root FILE_URI, --old-source-commit FULLSHA, --ignore-approval-hold and --format human|json. Run sarif-to-comment --help for details; it needs no token and makes no request.
  • Command form: sarif-to-comment publish followed by the same flags does exactly the same thing. With --format json either form prints one JSON document (status, review, statePath and the Markdown message), with the same exit status.
  • Same core as the library: the CLI reads the file, calls the same publishSarifReview, and prints the same Markdown to stdout.
  • File encoding: the SARIF file must be UTF-8 JSON.
    • A leading UTF-8 byte-order mark is ignored, so the CLI and a library caller passing the same parsed document produce the same publication.
    • A file that is not valid UTF-8 (including UTF-16) is refused before any request is made. Its bytes are never silently replaced.

| Exit status | Meaning | | --- | --- | | 0 | published (or already published) | | 2 | blocked: nothing was published | | 3 | uncertain: retry with the same --state | | 1 | usage error, unreadable or unparsable SARIF file, refused request, or operational failure (details on stderr) |

Quick start: write a review yourself

# In the repository, with your proposed change staged (git add); unstaged edits are ignored.
npx sarif-to-comment init --output review.sarif --tool-name "Review agent"
npx sarif-to-comment add-comment --sarif review.sarif --file src/parse.js --line 2 \
  --message "Handle the empty-input case."
npx sarif-to-comment add-staged-changes --sarif review.sarif --output review.staged.sarif \
  --worktree . --repo acme/widgets --commit c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de
npx sarif-to-comment inspect --sarif review.staged.sarif
GH_TOKEN=... npx sarif-to-comment publish --sarif review.staged.sarif --repo acme/widgets --pull 42 \
  --commit c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de --state /var/lib/reviews/acme-widgets-42.json

The library has the same operations for in-memory SARIF: createSarifDocument, addSarifComment, inspectSarif, addStagedChangesToSarif and publishSarifReview. Each returns a new document or a view and never changes its input.

  • Line numbers refer to the reviewed commit. For a file the reviewed commit doesn't have, they refer to its staged content.
  • Associating changes with findings. Only the staged index is read. A staged change becomes a fix on a finding only when the finding's lines lie within the lines the change replaces; neither is enlarged.
    • A finding that only partly overlaps a change stays a comment. The receipt says so.
    • A change that no finding explains is kept as a short factual finding attributed to sarif-to-comment. A change that only adds lines replaces no reviewed line, so it is never credited to a nearby finding; the receipt marks it insertion: true.
    • Findings that already have fixes are never changed. If your staged change differs from such a fix on the same lines, the command fails rather than choose between them.
  • Files. init refuses to overwrite an existing file. add-comment updates its SARIF file in place, atomically. add-staged-changes writes a separate output; if that output file already exists, it's first renamed to <UTC time>.old.<name>, and a failed run writes no output.
  • Output. inspect shows every finding in full, with its locations and fixes, plus log-level and inline external properties verbatim. Only fix previews are shortened, visibly. It doesn't check whether the file can be published.
    • --format json on any command prints one JSON document for every outcome, errors included. Exit statuses are the same in both formats.
    • Exit statuses: 0 success; 2 content refused (not valid SARIF, or a staged change that can't be represented); 1 usage or operational error. publish keeps the exit statuses below.
  • Changes that can't be represented. Staged edits to UTF-8 text files become fixes. Publication still checks native-suggestion compatibility: empty reviewed files, or files containing only a byte-order mark, have no source line for an inline suggestion and are blocked. File creations and deletions become proposed file operations: inspect shows them, but publish doesn't support them yet and blocks the review. add-staged-changes fails, naming the path, for mode changes, symbolic links, submodules, binary or non-UTF-8 files, conflicts and intent-to-add entries.

See the getting-started guide for complete, tested examples of both surfaces and of SARIF from an analyzer.

Credentials

Use a GitHub personal access token or user token. It needs permission to read the repository and to create pull request reviews; for a fine-grained token that is Contents: Read and Pull requests: Read and write. The CLI reads GH_TOKEN, or else GITHUB_TOKEN; there is no token flag.

Authentication uses the authenticated user's identity. GitHub App installation tokens, including the automatic GITHUB_TOKEN of GitHub Actions workflows, are not supported.

The token is never written to the state file, never included in any fingerprint, and never printed. It is redacted from errors.

This review credential is unrelated to how the package itself is published. npm releases use trusted publishing, a maintainer concern described under Releasing.

The state path: retries, recovery and concurrency

statePath (--state) is the durable identity of one publication. You choose it, and you must keep it.

  • Before anything is sent, the tool writes the complete intended review and a hidden marker to that file and flushes it to disk. Only the process that creates the file sends, and it sends once.
  • Retry with the same state path. A later run never sends again. It returns the recorded result, or checks GitHub for the marker and confirms the complete review. It doesn't need the branch to still exist or the SARIF to still apply, because it works from the saved request.
  • After an uncertain result, do not delete the state file. Delivery could not be confirmed: the response may have been lost, or the review may not be visible yet. The file is the only record that a review may already exist, and deleting it risks a duplicate. Retry later with the same path. The tool never repairs or restores anything it finds.
  • A new state path means a new, separate review. Use one only when you deliberately want another review, for example for new SARIF.
  • Concurrent runs on the same state path are safe: exactly one sends and the others only check.
  • The state path is bound to its input.
    • Reusing it with different SARIF, a different pull request or commit, or a different source root or old-side candidate is refused.
    • An unresolved publication also checks the authenticated account.
    • Completed and rejected records return without authentication or network calls; the API still requires a token-shaped input.
  • The state file is created with owner-only permissions. It requires a local file system that supports hard links.

One pending review per account

GitHub lets an account hold only one pending (draft) review per pull request, and refuses a second one with HTTP 422. If your account already has a draft on the pull request — made by a person or by an earlier run — publication is rejected. The tool never submits, edits or deletes an existing draft to make room. A person has to submit or delete it on GitHub, and then you publish again with a new state path.

A refused request is recorded in the state file. Later runs with that path report the refusal without contacting GitHub, and never resend it.

Supported SARIF (first-milestone profile)

  • Whole-review validation. The document is validated against the official SARIF 2.1.0 schema, then checked for consistency against the pull request's actual source. Invalid input or an unsupported finding, source association or fix blocks the entire review. Every accepted finding is included; this is not a lossless rendering of all SARIF metadata.
  • General findings. A result without a location goes in the review body.
  • Findings with a location. A result with one physical location becomes an inline comment when it maps exactly onto a line of the pull request's diff at the reviewed commit. Otherwise it goes in the body with an exact permalink to that commit and a copy of the source. Nothing is ever placed on a nearby or different line.
  • Suggestions. A fix with one text replacement on the reviewed head, inside the diff, becomes a native GitHub suggestion.
    • A located result must refer to the same file and revision, with its lines contained in the replacement's lines. Otherwise this profile refuses the association; keep the correct finding location and separate the feedback from the unsupported fix.
    • Cases GitHub doesn't apply faithfully are refused before anything is written: raw CR in the suggestion payload, nested triple-backtick fences, blank-only replacements and unsafe final-line deletions.
  • Refused features. Multiple locations, related locations, code flows, graphs, stacks, attachments, suppressions, alternative or multi-file fixes, and file creation or deletion proposals are refused with an explanation.
  • Metadata limits.
    • Producer fingerprints, rank and occurrence counts are not rendered.
    • A logical location accompanying a physical location is not rendered; a logical-only location is unsupported.
    • Taxonomy classifications remain in preparation evidence but are not shown in the review.
  • Result limits. Results are limited to 100 inline comments, 60,000 characters per body or comment, and about 1 MB per request. These are conservative product limits, not GitHub maxima.
  • Pull request limits. Pull requests changing more than 3,000 files, and source files larger than 1,000,000 bytes or not valid UTF-8, are refused rather than read partially. A file whose patch GitHub omits cannot receive inline comments.

Approval hold

A run or result may declare properties.sarifToComment.approval: "awaiting-approval". Publication then stops with blocked.

options.ignoreApprovalHold (--ignore-approval-hold) overrides only that hold, never any other check. The override is not part of the publication's identity, so a retry doesn't need to repeat it.

Reviewed commit and historical reviews

The review is always tied to reviewedCommit, even when the author's branch has moved on since. If the pull request's head has advanced past the reviewed commit, the tool doesn't retarget the review. Findings that can no longer be anchored inline go in the body as exact links to the reviewed commit.

Old-side source

Findings about deleted or original lines need to know the diff's old side. By default the tool asks GitHub to compare the pull request, and verifies each old file by reversing the pull request's own patch against the exact file contents. Old-side source of renamed files is not supported.

If that comparison can't establish the old side, you may pass oldSourceCommit (--old-source-commit) as a candidate. It is verified the same way, never trusted blindly, and it becomes part of the publication's identity. SARIF provenance naming other revisions is still published as general feedback. It is never chosen automatically as the diff's old side.

Limitations

  • Live verification. The library and CLI have been verified against live GitHub. The runs covered:

    • complete pending reviews, with exact source and original-anchor readback;
    • rendered inline comments and suggestions, and general feedback;
    • branch advance;
    • read-only recovery after a discarded create response and after a process kill.

    These bounded fixture runs do not establish every host failure mode or suggestion shape. Native application of one-line-to-three, two-lines-to-one and middle-line deletion suggestions was verified by exact resulting file bytes and Git blob identities. Inline-only and CRLF-source review bodies also read back exactly. The evidence is recorded in the source repository (docs/milestone-e2e-evidence.md and docs/suggestion-application-e2e.md).

  • Only https://api.github.com is supported.

  • The hidden marker only identifies a review; it is not a secret. A human edit to an unconfirmed draft leaves delivery uncertain rather than being "fixed".

  • The durability steps (write, flush, then send) are ordered for crash safety, but that has not been tested against power loss.

  • GitHub Enterprise Server and GitHub App installation tokens are not supported.

  • There is no review maintenance, re-review or synchronisation back to SARIF.

  • Authoring can't yet remove or correct a finding in place, and there's no standalone check that a document can be published; publish performs every check. Staged file creations and deletions can be recorded in SARIF, but can't be published yet.

  • npm attaches a provenance attestation only when the source repository is public at publish time. A release published while the repository is private has no provenance attestation (see Releasing).

Development

pnpm install
pnpm run build             # rebuild dist/ from src/, roll up the declarations, regenerate api-report/ and docs/api/
pnpm run check             # lint, types, API report/docs freshness, release plan, tests (read-only)
pnpm test                  # refuse a missing or stale dist/, then node --test "test/**/*.test.mts"
pnpm changeset             # describe a change for the next release

Development and release tooling need Node 22.18.0 or later, enforced by devEngines in package.json: the tests and the build, check and release scripts are TypeScript that Node runs directly by type stripping. The published package still needs only Node 22 or later (engines).

Run pnpm run build before pnpm run check or pnpm test, and again after changing sources or build configuration. The tests exercise, and the package ships, the built dist/. pnpm test (and therefore pnpm run check) refuses a dist/ that is missing, incomplete, or was built from different sources or configuration; it compares content hashes of every build input and output, not modification times.

The implementation is strict TypeScript, and there are no hand-written declarations. The public TypeScript declarations are generated from the implementation. src/public-api.cts declares the public API, and the CommonJS runtime entry src/index.cts is checked at compile time to export exactly its functions. tsc emits per-module declarations, API Extractor rolls them up into the shipped dist/sarif-to-comment.d.ts and writes a reviewable API report (api-report/) and a doc model, and API Documenter renders the doc model as the Markdown reference in docs/api/. pnpm run check fails when either is out of date.

pnpm run build rewrites api-report/ and docs/api/ whenever the public API or its TSDoc changes; commit them with the change. CI and the publish workflow build and then fail if either differs from the committed copy.

Repository layout.

  • src/*.cts: the implementation. tsc compiles each module to CommonJS in dist/*.cjs; src/index.cts is the package entry and src/sarif-to-comment.cts the CLI executable.
  • dist/: build output. It is not committed; the package ships its runtime modules and the rolled-up declarations.
  • test/: node:test suites (*.test.mts), fixtures and helpers, run against dist/.
  • scripts/*.mts: build, type-check, API documentation and release-guard tooling.
  • api-report/ and docs/api/: generated by pnpm run build and committed, so API changes are reviewed.
  • vendor/: the official SARIF 2.1.0 schema used for validation.
  • .changeset/: pending release notes.

Releasing

Releases use Changesets for versioning and npm trusted publishing for publication. They are published only by the GitHub Actions workflow .github/workflows/publish.yml, which authenticates with a short-lived OIDC token. There is no npm token in the repository or its secrets.

Versions stay below 1.0.0

MAXIMUM_RELEASE_MAJOR in scripts/release-guard.mts is 0. Until someone deliberately raises it in a reviewed change, nothing can version or publish 1.0.0 or higher, including prereleases such as 1.0.0-rc.0:

  • pnpm run check (run in CI on every pull request) fails if a pending changeset would reach 1.0.0. A major changeset is refused with an explanation. It is never quietly converted to a smaller bump; choose minor yourself if the change shouldn't start 1.0.
  • pnpm run release:version refuses the same plans before changeset version changes any file.
  • What counts as the plan. The guard doesn't parse changeset files itself. It runs the real changeset version in a throwaway copy and judges the version and changelog entry Changesets produces, so any front matter Changesets accepts is judged by its actual effect. That includes quoted values such as "sarif-to-comment": "major". A changeset Changesets can't read is refused, not ignored.
  • The publish workflow refuses any package.json version of 1.0.0 or higher, and so does the prepublishOnly backstop for a manual publish, which also refuses a missing or stale dist/.

Changesets pre mode (prereleases) is not part of this release path.

Deliberately releasing 1.0. In a reviewed change, raise MAXIMUM_RELEASE_MAJOR to 1 and update the test in test/release.test.mts that pins its value. That is the only step: a major changeset then versions and publishes 1.0.0 through the normal flow above, while 2.0.0 and above stay blocked.

Making a release

  1. With each change, add a changeset (pnpm changeset) choosing patch or minor, and merge it to main with the change.

  2. To release, run pnpm run release:version on an up-to-date main. It checks the pending plan, then runs changeset version, which:

    • bumps package.json;
    • writes the CHANGELOG.md entry;
    • consumes the changesets.

    Review and commit the result, then push it to main, for example through a pull request.

  3. When that version bump reaches main, .github/workflows/publish.yml runs. It runs only for main pushes that change package.json, and never for pull requests or other branches. Its release guard decides first:

    • Already on npm: a version that is already published is a no-op.
    • Otherwise, refused unless all of these hold:
      • the version is stable and below 1.0.0;
      • CHANGELOG.md's newest entry is that version, which shows Changesets produced it;
      • pre mode is off;
      • the package metadata is publishable, with the exact repository URL;
      • the commit is on main;
      • npm is at least 11.5.1 and Node at least 22.18.0.
    • When allowed: it builds dist/, runs pnpm run check, packs the tarball, verifies it contains exactly the distribution files, and publishes that tarball.

    Publishes never overlap, and a publish in progress is never cancelled.

  4. If publishing fails, what to do depends on where the cause is. A version npm has already accepted can never be republished.

    • Outside the repository: a transient registry, network or runner failure, or npm trusted-publisher settings that are missing or wrong. Correct it there, then re-run the failed workflow run. A re-run executes the same commit with the same workflow file, so it is only the right tool when nothing in the repository needs to change.

    • In the repository: the release guard, the workflow, a test, the package contents or its metadata. A re-run would repeat the same code, and a push to main that doesn't change package.json does not start a publish run. Merge the fix to main, then prepare the next patch release:

      1. add a patch changeset with pnpm changeset;
      2. run pnpm run release:version;
      3. merge the resulting commit.

      That commit changes package.json, so the workflow runs with the fixed code and publishes the new version. The version that failed stays unpublished; its changelog entry remains as history.

    Recording the release commit. The workflow publishes the verified tarball but doesn't create git tags, and npm's registry metadata for 0.1.0 records no gitHead. Do not assume that field identifies a tarball release. The commit is identified in two places:

    • by the successful publish.yml workflow run for that commit on main;
    • when the repository is public at publish time, by the version's npm provenance attestation, which names the source repository, workflow and commit.

    Maintainers who want tags can run pnpm changeset git-tag locally on that commit and push the tags.

First release. The release history starts from npm's pre-existing 0.0.0, the bootstrap baseline for this package. The initial minor changeset versions that baseline to 0.1.0, with the first changelog entry, and 0.1.0 is the first version this workflow publishes. Later releases follow the same steps from whatever version package.json then holds.

One-time setup (npmjs.com)

On the package's Settings → Trusted publishing page, add a GitHub Actions trusted publisher:

| Field | Value | | --- | --- | | Organization or user | mike-north | | Repository | sarif-to-comment | | Workflow filename | publish.yml | | Environment | (leave empty) |

npm requires repository.url in package.json to match this repository exactly. It is git+https://github.com/mike-north/sarif-to-comment.git. Once a trusted release has succeeded, npm recommends setting Publishing access to Require two-factor authentication and disallow tokens.

Provenance. Trusted publishing authenticates with OIDC from a private or public repository alike. npm attaches a provenance attestation automatically only when the source repository is public at publish time; a release from a private repository has no provenance attestation. That limitation comes from npm, not from this workflow, and the workflow never changes repository visibility. Version 0.1.0 was published from the repository while it was public, and its npm provenance attestation verifies, naming commit 3797ca6efe2156d4c952fad7fed10b569f1dcbbb and .github/workflows/publish.yml. The workflow deliberately does not pass --provenance, which fails for a private repository; npm adds provenance on its own whenever the repository is public.