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

@savvy-web/silk-effects

v0.5.0

Published

Shared Effect library for Silk Suite conventions

Readme

@savvy-web/silk-effects

npm License: MIT

Shared Effect library providing Silk Suite conventions for publishability detection, versioning strategy, tag formatting, managed file sections, config discovery, Biome schema synchronization and CLI tool resolution. Platform-agnostic — consumers provide their own runtime layer (NodeContext, BunContext, etc.).

Features

  • Detect a package's publish targets from its package.json publishConfig, with multi-registry support and a changeset-ignore-aware override for workspaces-effect's PublishabilityDetector
  • Read changeset config through a typed accessor service that reports silk vs vanilla mode, ignore patterns and fixed groups
  • Manage tool-owned sections in user-editable files without clobbering surrounding content, including ordered multi-section sync for composing several managed regions per file
  • Discover and resolve CLI tools globally or locally with version enforcement and caching
  • Detect versioning strategy and format git tags from changeset configuration
  • Locate config files and keep Biome schema URLs in sync across workspaces

Install

npm install @savvy-web/silk-effects effect @effect/platform @effect/platform-node
# or
pnpm add @savvy-web/silk-effects effect @effect/platform @effect/platform-node

effect and @effect/platform are peer dependencies. Install a platform package (@effect/platform-node, @effect/platform-bun) matching your runtime.

Quick start

All exports come from the package root:

import {
  SilkPublishability,
  ManagedSection, ManagedSectionLive, SectionDefinition,
  ToolDiscovery, ToolDiscoveryLive, ToolDefinition,
} from "@savvy-web/silk-effects";

SilkPublishability.detect is a pure static — no layers, no Effect runtime. Pass a package name and the raw package.json and get back the publish targets the silk rules resolve:

import { SilkPublishability } from "@savvy-web/silk-effects";

const targets = SilkPublishability.detect("@my-org/my-package", {
  private: true,
  publishConfig: { access: "public", targets: ["npm", "github"] },
});
// => [PublishTarget { name: "@my-org/my-package", registry: "https://registry.npmjs.org/", ... },
//     PublishTarget { name: "@my-org/my-package", registry: "https://npm.pkg.github.com/", ... }]

Services

The services are grouped by which platform layers they require.


No platform layer required

These services are pure logic — no filesystem or shell access needed.

SilkPublishability

Apply silk publishability rules to a raw package.json and resolve its publish targets. Targets are PublishTarget records from workspaces-effect with name, registry, directory, access and provenance fields. The static detect, expandShorthand and resolveTargetAccess helpers are pure; resolveTargets and listPublishable are Effects that read from disk (see below).

In silk mode private: true is the norm on workspace package.json files. Publishability is derived from publishConfig, with the private flag consulted only as a last-resort default.

import { SilkPublishability } from "@savvy-web/silk-effects";

// Targets-first: one PublishTarget per surviving publishConfig.targets entry
const targets = SilkPublishability.detect("@my-org/pkg", {
  private: true,
  publishConfig: { access: "public", targets: ["npm", "github"] },
});
// => [PublishTarget { registry: "https://registry.npmjs.org/", access: "public", ... },
//     PublishTarget { registry: "https://npm.pkg.github.com/", access: "public", ... }]

// Not publishable -> empty array
const none = SilkPublishability.detect("@my-org/internal", { private: true });
// => []

See Publishability for the full rule order and the disk-reading helpers.

TagStrategy

Determine git-tag naming strategy and format tag strings. Strategy is "single" (one publishable package, tags like 1.2.3) or "scoped" (multiple packages, tags like @scope/[email protected]). Tag format follows strict SemVer 2.0.0 with no v prefix.

import { Effect } from "effect";
import { TagStrategy, TagStrategyLive } from "@savvy-web/silk-effects";

const tag = await Effect.runPromise(
  Effect.gen(function* () {
    const ts = yield* TagStrategy;
    const strategy = yield* ts.determine(versioningResult);
    return yield* ts.formatTag("@savvy-web/silk-effects", "1.0.0", strategy);
  }).pipe(Effect.provide(TagStrategyLive)),
);
// => "@savvy-web/[email protected]"

FileSystem layer required

These services read or write files. Provide a platform layer such as NodeContext.layer or BunContext.layer.

SilkPublishabilityDetectorLive and PublishabilityDetectorAdaptiveLive

SilkPublishability.detect is also exposed through workspaces-effect's PublishabilityDetector Tag so consumers can swap silk rules into any program that already yields the detector. Two layers override the Tag:

  • SilkPublishabilityDetectorLive — applies silk rules unconditionally. Requires FileSystem.
  • PublishabilityDetectorAdaptiveLive — ignore-aware. Changeset-ignored packages resolve to [], then it dispatches by changeset mode (none[], silk → silk rules, vanilla → the workspaces-effect default). Requires FileSystem and ChangesetConfig.
import { Effect } from "effect";
import { NodeContext } from "@effect/platform-node";
import { PublishabilityDetector } from "workspaces-effect";
import { SilkPublishabilityDetectorLive } from "@savvy-web/silk-effects";

const targets = await Effect.runPromise(
  Effect.gen(function* () {
    const detector = yield* PublishabilityDetector;
    return yield* detector.detect(pkg, root);
  }).pipe(
    Effect.provide(SilkPublishabilityDetectorLive),
    Effect.provide(NodeContext.layer),
  ),
);
// => ReadonlyArray<PublishTarget>

See Publishability for the adaptive layer and the ChangesetConfig service.

ChangesetConfig

Typed accessor over a workspace root's .changeset/config.json, reading through ChangesetConfigReader with a per-root cache. Every accessor is total — a missing or unreadable config collapses to mode: "none" and empty defaults. Methods: mode, versionPrivate, ignorePatterns, isIgnored, fixed, plus a static ChangesetConfig.matches(name, pattern).

import { Effect } from "effect";
import { NodeContext } from "@effect/platform-node";
import {
  ChangesetConfig, ChangesetConfigLive, ChangesetConfigReaderLive,
} from "@savvy-web/silk-effects";

const mode = await Effect.runPromise(
  Effect.gen(function* () {
    const config = yield* ChangesetConfig;
    return yield* config.mode(process.cwd());
  }).pipe(
    Effect.provide(ChangesetConfigLive),
    Effect.provide(ChangesetConfigReaderLive),
    Effect.provide(NodeContext.layer),
  ),
);
// => "silk" | "vanilla" | "none"

ManagedSection

Manage tool-owned delimited sections inside user-editable files. Sections are bounded by markers like # --- BEGIN TOOL MANAGED SECTION --- / # --- END ... ---. User content outside the markers is never touched.

SectionDefinition is a value object representing section identity (tool name + comment style). It creates SectionBlock instances that hold the actual content. Definitions support typed content factories via generate() and generateEffect().

SectionBlock represents the content between markers. It supports diff(), prepend() and append() operations and uses normalized content for equality comparison.

Methods: read, write, sync, syncMany, check, remove, isManaged — all support dual API (data-first and data-last) for pipe composition. sync manages one section; syncMany manages several ordered sections in one file; remove deletes a section including its markers.

import { Effect } from "effect";
import { NodeContext } from "@effect/platform-node";
import {
  ManagedSection, ManagedSectionLive, SectionDefinition,
} from "@savvy-web/silk-effects";

// Define section identity
const def = SectionDefinition.make({ toolName: "LINT-STAGED" });

// Create a content block from the definition
const block = def.block("\nnpx lint-staged\n");

await Effect.runPromise(
  Effect.gen(function* () {
    const ms = yield* ManagedSection;

    // Sync: creates the section if missing, updates if changed, no-op if identical
    const result = yield* ms.sync(".husky/pre-commit", block);
    // => SyncResult: Created | Updated | Unchanged

    // Check: compare file content against expected block
    const check = yield* ms.check(".husky/pre-commit", block);
    // => CheckResult: Found | NotFound

    // Remove: delete the section and its markers, collapsing the leftover blank line
    const removed = yield* ms.remove(".husky/pre-commit", def);
    // => true if a section was removed, false if none was present
  }).pipe(
    Effect.provide(ManagedSectionLive),
    Effect.provide(NodeContext.layer),
  ),
);

SectionDefinition also supports // comment style for JavaScript/TypeScript files:

const jsDef = SectionDefinition.make({ toolName: "MY-TOOL", commentStyle: "//" });

Use ShellSectionDefinition when the comment style is always # and should not be configurable.

syncMany keeps several sections in one file in their declared relative order. It updates existing sections in place, inserts a missing section next to its declared sibling, normalizes order when sections drift out of order and preserves user content and unrelated tool sections. It returns one SyncResult per input block in input order and is idempotent.

The SavvySections exports compose ordered managed sections per husky hook file. A base section defines shared shell, then each consumer layers its own one-line tool section on top:

  • SavvyBaseSection is a ShellSectionDefinition (tool name savvy-base); pair it with savvyBasePreamble(), which defines ROOT, the in_ci predicate, PM via package-manager detection and pm_exec.
  • SavvyHooksSection (tool name savvy-hooks) pairs with savvyHooksHygiene(), a self-guarded repo-hygiene block that runs outside CI.
  • savvyToolSection(toolName, command) builds a consumer's one-line tool section whose content is exactly in_ci || pm_exec <command> — the command is appended verbatim, so shell tokens like $ROOT and $1 survive into the output. A savvy-base section must precede it in the same hook file, so pass both to syncMany in that order.
import { Effect } from "effect";
import { NodeContext } from "@effect/platform-node";
import {
  ManagedSection, ManagedSectionLive,
  SavvyBaseSection, savvyBasePreamble, savvyToolSection,
} from "@savvy-web/silk-effects";

await Effect.runPromise(
  Effect.gen(function* () {
    const ms = yield* ManagedSection;
    const results = yield* ms.syncMany(".husky/commit-msg", [
      SavvyBaseSection.block(savvyBasePreamble()),
      savvyToolSection("savvy-commit", 'commitlint --config "$ROOT/lib/configs/commitlint.config.ts" --edit "$1"'),
    ]);
    // => ReadonlyArray<SyncResult>, one per input block in declared order
  }).pipe(
    Effect.provide(ManagedSectionLive),
    Effect.provide(NodeContext.layer),
  ),
);

VersioningStrategy

Classify the versioning strategy from changeset configuration. Outputs "single" (0-1 publishable packages), "fixed-group" (all packages in one fixed group) or "independent" (multiple packages, not in a single group). Falls back gracefully if config is missing.

import { Effect } from "effect";
import { NodeContext } from "@effect/platform-node";
import {
  VersioningStrategy, VersioningStrategyLive,
  ChangesetConfigReaderLive,
} from "@savvy-web/silk-effects";

const result = await Effect.runPromise(
  Effect.gen(function* () {
    const vs = yield* VersioningStrategy;
    return yield* vs.detect(publishablePackages, process.cwd());
  }).pipe(
    Effect.provide(VersioningStrategyLive),
    Effect.provide(ChangesetConfigReaderLive),
    Effect.provide(NodeContext.layer),
  ),
);
// => { strategy: "single" | "fixed-group" | "independent", ... }

ChangesetConfigReader

Read and decode .changeset/config.json. Auto-detects whether the project uses @savvy-web/changesets (returning SilkChangesetConfigFile with _isSilk: true) or standard changesets (returning ChangesetConfigFile).

import { Effect } from "effect";
import { NodeContext } from "@effect/platform-node";
import {
  ChangesetConfigReader, ChangesetConfigReaderLive,
} from "@savvy-web/silk-effects";

const config = await Effect.runPromise(
  Effect.gen(function* () {
    const reader = yield* ChangesetConfigReader;
    return yield* reader.read(process.cwd());
  }).pipe(
    Effect.provide(ChangesetConfigReaderLive),
    Effect.provide(NodeContext.layer),
  ),
);
// => ChangesetConfigFile | SilkChangesetConfigFile

ConfigDiscovery

Locate config files using a priority-based search convention. Checks lib/configs/{name} (shared configs) first, then {cwd}/{name} (local override).

import { Effect } from "effect";
import { NodeContext } from "@effect/platform-node";
import { ConfigDiscovery, ConfigDiscoveryLive } from "@savvy-web/silk-effects";

const result = await Effect.runPromise(
  Effect.gen(function* () {
    const cd = yield* ConfigDiscovery;
    return yield* cd.find("biome.jsonc");
  }).pipe(
    Effect.provide(ConfigDiscoveryLive),
    Effect.provide(NodeContext.layer),
  ),
);
// => { path: "/project/biome.jsonc", source: "root" } | null

BiomeSchemaSync

Keep Biome config $schema URLs current. Locates biome.json or biome.jsonc, compares the $schema value against the expected URL for the given version, and optionally updates in place. Strips semver range prefixes.

import { Effect } from "effect";
import { NodeContext } from "@effect/platform-node";
import { BiomeSchemaSync, BiomeSchemaSyncLive } from "@savvy-web/silk-effects";

const result = await Effect.runPromise(
  Effect.gen(function* () {
    const bss = yield* BiomeSchemaSync;
    return yield* bss.sync("2.0.0");
  }).pipe(
    Effect.provide(BiomeSchemaSyncLive),
    Effect.provide(NodeContext.layer),
  ),
);
// => { updated: true, skipped: false, current: "2.0.0" }

FileSystem + CommandExecutor layer required

ToolDiscovery

Locate CLI tools globally (PATH) or locally (via package manager), extract versions, enforce constraints and cache results.

ToolDefinition configures how a tool is resolved: VersionExtractor (Flag, Json or None), ResolutionPolicy (Report, PreferLocal, PreferGlobal, RequireMatch) and SourceRequirement (Any, OnlyLocal, OnlyGlobal, Both). Equality is based on tool name only.

ResolvedTool is the result of resolution. It carries the tool's name, source ("global" or "local"), version and package manager. Its exec() and dlx() methods return a ToolCommand — a wrapper around @effect/platform Command with instance-method ergonomics (string(), lines(), exitCode(), stream()).

import { Effect } from "effect";
import { NodeContext } from "@effect/platform-node";
import {
  ToolDiscovery, ToolDiscoveryLive, ToolDefinition,
} from "@savvy-web/silk-effects";

const output = await Effect.runPromise(
  Effect.gen(function* () {
    const td = yield* ToolDiscovery;

    // Resolve a tool (results are cached by name)
    const biome = yield* td.resolve(ToolDefinition.make({ name: "biome" }));

    // Check availability without throwing
    const hasBiome = yield* td.isAvailable(ToolDefinition.make({ name: "biome" }));

    // Execute the resolved tool
    return yield* biome.exec("check", ".").string();
  }).pipe(
    Effect.provide(ToolDiscoveryLive),
    Effect.provide(NodeContext.layer),
  ),
);

Use require() to fail with a descriptive error if the tool is not found:

const biome = yield* td.require(
  ToolDefinition.make({ name: "biome" }),
  "Biome is required for linting",
);

Documentation

License

MIT