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

projectfmt

v1.1.0

Published

Format generated code the way its destination project expects

Readme

projectfmt

Format generated code the way its destination project expects.

Copyright (c) 2015 by Gadi Cohen. MIT Licensed.

npm JSR JSR score CI coverage semantic-release TypeScript MIT License

Quick Start

projectfmt is a project-aware formatter broker for code generators and libraries. Give it source text, the path where that source is intended to live, and either an explicit project boundary or an absolute path from which one can be inferred. It discovers and invokes the formatter already chosen by that project—without writing a temporary file.

Deno / JSR import:

import { formatSource } from "@gadicc/projectfmt";

Node / npm import:

import { formatSource } from "projectfmt";

The call is the same in either runtime:

const formatted = await formatSource(generatedSource, {
  filePath: "src/generated/schema.ts",
  projectRoot,
});

The intended path is not cosmetic. It controls nested configuration discovery, language/parser selection, ignores, plugins, and monorepo project selection.

Why projectfmt?

projectfmt does not own formatting rules. Its job is discovery, resolution, invocation, and consistent failure behavior for virtual files.

The closest prior art is Formatly, which also detects a project's formatter. Its current public workflow accepts file/glob patterns and runs formatter commands; projectfmt is source-string and virtual-file first. Formatly's open discussions about an importable formatting API, parent-directory discovery, and project package-manager execution show how closely related the problem spaces are.

| Tool | Primary center of gravity | Difference from projectfmt | | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | Formatly | Formatter detection for file/glob and subprocess workflows | Closest analogue; projectfmt starts with an in-memory string and an intended virtual path | | treefmt and Trunk | Project-wide formatter orchestration and CLI workflows | Operate at repository/workspace scale and use their own orchestration configuration | | Unibeautify | Universal beautifier ecosystem | Configuration-centric formatter abstraction rather than destination-project resolution for virtual files | | Spotless | Composable formatting steps integrated with build systems | Especially strong in JVM/Gradle/Maven workflows | | Prettier API and dprint plugins | Programmatic APIs for one formatter ecosystem | They format directly; they do not select whichever formatter an arbitrary destination project chose |

This comparison was reviewed on 22 July 2026. It is deliberately about product shape, not popularity. Alternatives exist and may be better for repository-wide formatting.

The original formatter integration was used by valibot-serialize from 17 September 2025. It became a standalone package because project-aware formatting is useful to code generators well beyond that project. The extraction also corrects several limitations of that first integration; see History and intentional differences.

Install

Deno / JSR:

deno add jsr:@gadicc/projectfmt

Node / npm:

npm install projectfmt

The destination project is expected to already provide its formatter:

  • Prettier: a project-local prettier package;
  • Biome: a project-local @biomejs/biome package and its platform binary;
  • Deno fmt: a deno executable on PATH.

All formatter integrations are optional. projectfmt never installs or downloads one, invokes a package manager, or contacts the network.

Agent skill

Install the bundled projectfmt skill to help coding agents integrate the library correctly:

npx skills add https://github.com/gadicc/projectfmt --skill projectfmt

API

formatSource(source, optionsOrAbsolutePath)

Returns Promise<string>.

const output = await formatSource("export const answer=42", {
  filePath: "packages/api/src/generated/answer.ts",
  projectRoot: "/workspace",
  formatter: "auto", // default; also "prettier", "biome", "deno", or "none"
  strict: true,
});

filePath may be relative to an explicit projectRoot or absolute. Relative paths without projectRoot are rejected rather than interpreted against the process's current working directory. An absolute path may omit projectRoot, in which case projectfmt infers it. Supplying projectRoot for an absolute path is still supported when the caller needs an exact, reproducible boundary. Paths that escape an explicit or inferred project boundary are rejected, as is an intended path equal to projectRoot: the intended path must name a descendant file. strict defaults to false.

The equivalent absolute-path shorthand uses all default options:

await formatSource(
  source,
  "/workspace/packages/api/src/generated/answer.ts",
);

The string shorthand must be absolute. Use the options object to supply a relative path, explicit boundary, formatter selection, strict mode, custom adapters, or processing constraints.

For automatic inference, projectfmt walks upward from the intended file's directory. The nearest VCS root (.git or .hg) or workspace boundary wins. Recognized workspaces include pnpm, Lerna, Rush, package.json#workspaces, and Deno workspaces. If none exists, the nearest package, Deno, JSR, lockfile, or supported formatter configuration is used. Inference errors instead of using the filesystem root when no defensible project marker exists.

Inferred roots are cached for traversed directories at or below the detected root, so later files in the same package or workspace reuse the result. Call clearProjectRootCache() if a long-running process changes project topology, such as creating or removing a workspace or VCS boundary.

Biome automatically runs the equivalent of biome check --write --stdin-file-path=.... This applies the repository's formatting, safe lint fixes, and enabled assist actions such as import organization. It never passes --unsafe. Set formatOnly: true to opt out of lint fixes and assist actions and call the equivalent of biome format instead:

await formatSource(source, {
  filePath,
  projectRoot,
  formatOnly: true,
});

formatOnly is an adapter-independent processing constraint. Prettier and Deno already perform formatting only, so Biome is the only built-in adapter whose behavior currently changes. Custom adapters receive the constraint as context.formatOnly and should avoid lint fixes, assists, or other cleanup when it is true.

formatSourceWithResult(source, optionsOrAbsolutePath)

Returns the source plus resolution diagnostics:

const result = await formatSourceWithResult(source, options);

console.log(result.changed, result.ignored);
console.log(result.resolution.formatter);
console.log(result.resolution.evidence);

It accepts the same absolute-path string shorthand as formatSource.

The result contains:

  • source, changed, and ignored;
  • the selected formatter and implementation/version availability;
  • absolute normalized project and file paths;
  • the configuration root, all discovery evidence, ranked candidates, ambiguity state, and a human-readable reason.

resolveFormatter(optionsOrAbsolutePath)

Performs the same discovery and availability probing without formatting:

const resolution = await resolveFormatter({
  filePath: "src/generated/schema.ts",
  projectRoot,
});

switch (resolution.status) {
  case "selected":
    console.log(resolution.formatter, resolution.availability?.version);
    break;
  case "not-configured":
  case "unavailable":
  case "disabled":
    console.log(resolution.reason);
}

For default resolution options, pass the absolute intended path directly:

const resolution = await resolveFormatter(absoluteOutputPath);

clearProjectRootCache()

Clears roots cached by automatic inference. Explicit projectRoot calls do not use this cache. Each separate public call performs fresh formatter discovery and availability probing; formatter evidence, configuration, and availability are not cached across calls.

Within one formatting call, projectfmt keeps the selected adapter map and snapshots the successful probe result through formatting. Prettier and Biome therefore use the package implementation path selected by that call instead of resolving it a second time. Repointing or removing a package after probing does not select a replacement for the current call; failure to load or invoke the snapshotted path is reported as a FormatterExecutionError. A later public call discovers and probes again.

Custom adapters

Additional adapters use the same discovery/probe/format lifecycle as built-ins:

Deno / JSR import:

import type { FormatterAdapter } from "@gadicc/projectfmt";

Node / npm import:

import type { FormatterAdapter } from "projectfmt";

The adapter definition is runtime-neutral:

const custom: FormatterAdapter = {
  name: "companyfmt",
  priority: 100,
  async discover(directory) {
    // Return evidence for this directory, or [].
    return [];
  },
  async probe() {
    return { available: true, implementation: "in-process" };
  },
  async format(source) {
    return { source: companyFormat(source) };
  },
};

await formatSource(source, {
  filePath,
  projectRoot,
  formatter: "companyfmt",
  adapters: [custom],
});

Adapter names must be non-empty and unique, priorities must be finite, and all three lifecycle methods are required at runtime. Discovery returns an array of evidence whose formatter matches the adapter, kind is supported, path is absolute and within projectRoot, description is non-empty, and strength is finite. A nested evidence path is valid, but ranking and configRoot use the directory passed to discover, so evidence cannot spoof its search distance.

probe must return an object with a boolean available and optional string details. format must return an object with string source, optional boolean ignored, and optional string stderr. projectfmt validates these results, passes the same context (including formatOnly) to probe and format, and wraps probe or format failures in structured errors while preserving their causes and available stderr. Because automatic root inference cannot know custom project markers, callers using custom adapters should normally provide projectRoot explicitly.

Resolution rules

For formatter: "auto", discovery walks from the intended file's directory up to and including projectRoot. It never searches above that boundary.

  1. The nearest directory containing formatter evidence wins. This establishes the nested-project/monorepo boundary.
  2. Within that directory, explicit configuration (biome.json, a Prettier config or package metadata, or deno.json(c)#fmt) beats a formatting script, which beats dependency-only evidence. Discovery follows the native filename sets, including dotted Biome JSON/JSONC, Prettier TOML, and truthy package.yaml#prettier configuration.
  3. Equal-strength candidates use the stable precedence biome, then deno, then prettier. This is an explicit tie-breaker, not adapter import order. strict: true rejects the ambiguity instead.
  4. Availability never changes selection. If the chosen formatter is missing, formatSource errors instead of silently falling through to a different formatter.

Explicit selection bypasses ranking but still uses nearest applicable config and project-local implementation resolution. formatter: "none" returns the input unchanged and performs no formatter probe.

With no configured formatter, non-strict auto mode returns the input unchanged and reports not-configured; strict mode errors. Ignored files are returned unchanged in either mode.

Formatter behavior

| Formatter | Implementation | Project/path behavior | Deno | Node | | --------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | --------------------------- | | Prettier | Project-local JS API | Passes filepath, loads the exact discovered config and project plugins, and checks bounded .prettierignore/.gitignore paths. EditorConfig lookup is disabled. | Yes | Yes | | Biome | Project-local platform CLI binary | Runs check --write --stdin-file-path by default with the exact discovered config, safe lint rules, assists, and file/tool includes. formatOnly selects format. Configless calls use a temporary empty config. | Yes | Yes | | Deno fmt | deno fmt subprocess | Passes the exact discovered config or --no-config, infers --ext, honors fmt.include/fmt.exclude, and uses the nearest existing destination directory as cwd. EditorConfig lookup is disabled. | Yes | Yes, when Deno is installed |

No adapter creates a temporary source or destination file. Configless Biome calls create and unconditionally remove a temporary empty configuration. Paths containing spaces are passed as process arguments rather than shell strings.

The Deno adapter evaluates top-level and fmt exclusions in order, including negated re-inclusions. Markdown suffix aliases such as .markdown map to the canonical md media type. Other media types and any required unstable flags are derived from the selected Deno 2 executable's fmt --help output on each call, so version-gated formats are used only when that runtime advertises them.

Errors and diagnostics

FormatterResolutionError covers invalid options, strict ambiguity, missing configuration in strict mode, and unavailable selected implementations. FormatterExecutionError wraps parsing/configuration/process failures.

Both extend ProjectfmtError and preserve useful fields such as code, formatter, filePath, projectRoot, evidence, resolution, stderr, and the original cause. Formatter failures are never converted to unchanged output.

Security and trust model

Use projectfmt only with projects you trust.

Prettier configuration and plugins are JavaScript modules and can execute code when loaded. The Biome adapter executes the project's pinned native binary, and the Deno adapter executes the deno binary on PATH. This is the same broad trust boundary as running those project formatters directly. projectRoot is a discovery and module-resolution boundary, not a sandbox. Provide it explicitly when automatic inference would grant a broader boundary than the caller intends. Native formatter configuration search is also bounded: projectfmt passes only the exact configuration discovered within projectRoot, or explicitly disables native auto-discovery. EditorConfig lookup is disabled for all built-in adapters because bounded EditorConfig inheritance is not yet implemented.

The library itself performs no network access or installation. Deno callers must grant the filesystem, environment, and subprocess permissions required to inspect the destination project and run its formatter.

History and intentional differences

Compared with the formatter subsystem extracted from valibot-serialize:

  • auto mode selects from project evidence instead of whichever adapter imports first;
  • Prettier receives the intended filepath, resolved configuration, ignores, and plugins;
  • Deno fmt receives the real extension, project cwd, and config rather than always parsing stdin as TypeScript;
  • Biome consistently applies configured formatting, safe lint fixes, and assist actions in both runtimes, with a formatting-only opt-out;
  • Node and Deno share one resolution/error contract;
  • diagnostics, strict mode, custom adapters, ignored virtual files, and project boundary validation are public behavior.

Roadmap

Likely future adapters include dprint and oxfmt. A whole-repository formatting CLI is not the primary goal.

Development

See CONTRIBUTING.md and docs/RELEASING.md. The main gates are:

deno task pre-commit
deno task coverage
deno task test:packages

MIT © Gadi Cohen.