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

elrh-cosca

v0.4.3

Published

Library of I/O functions helping with building CLI scripts for making changes in target projects - like adding default configuration files or new sections in `package.json`.

Readme

COSCA

Library of I/O functions helping with building CLI scripts for making changes in target projects - like adding default configuration files or new sections in package.json.

The first experimental "customers" are my Nuxt Spec and Nuxt Ignis projects.

The "COSCA" abbreviation stands for COde SCAffolding, which points out the library's purpose of providing methods for altering existing files and adding new ones from scratch using Node-based filesystem APIs.

How to use

NOTE: The library is ESM only and it is advised to use it with at least Node 22.

Run npm install elrh-cosca to include it in your project.

Options objects

All functions with arguments take a single required opts object with one or more properties. Each options type is exported from elrh-cosca and defined in src/types/functions.ts.

Bundle size and lazy loading

The library has no runtime dependencies. Third-party parsers are bundled into the published package, but split into separate chunks that are only loaded on demand:

| Chunk | Approx. size | Loaded by | | --- | --- | --- | | dist/elrh-cosca.mjs (main entry) | ~19 kB | always | | dist/chunks/magicast-*.mjs (magicast incl. @babel/parser) | ~590 kB | updateConfigFile | | dist/chunks/yaml-*.mjs (yaml) | ~140 kB | updateYamlFile, removeFromYamlFile, hasYamlKey |

What this means in practice:

  • Installed package size is (sadly) not reduced - all chunks are always downloaded with the package.
  • Nothing extra to install - all chunks are part of the elrh-cosca package, so the parsers are always available and never clash with other versions in your dependency tree.
  • Running with Node (e.g. a CLI script) - only the main entry is loaded on startup. A parser chunk is loaded via dynamic import() the first time a function needing it is called, and it is cached by Node afterwards.
  • Bundling into your own code - the package is marked as "sideEffects": false, so unused functions can be tree-shaken. If you don't use any of the functions listed above, bundlers like Vite drop the related chunks entirely. Other bundlers may still emit them as separate files, but they are never loaded.

List of file-manipulation functions

All file-manipulation options extend the following shared shape. force defaults to false. An omitted or empty prompt uses the function's built-in initial confirmation question.

interface FileOperationOptions {
  force?: boolean
  prompt?: string
}

createFileFromTemplate

interface CreateFileFromTemplateOptions extends FileOperationOptions {
  templateFile: string
  targetFile: string
}
async function createFileFromTemplate(opts: CreateFileFromTemplateOptions): Promise<void>

Takes the file given by templateFile from an installed package and creates a fresh copy of it in the target project.

Path to templateFile must be prefixed with the package name to allow proper resolution, e.g. your-package:path/to/template (the path is relative to the package root). The package name can be scoped (e.g. @scope/package). The package is resolved using resolvePackagePath.

Path to targetFile is relative to process.cwd(), which allows consumers to run npx your-script in their project roots during development. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. If the target directory does not exist, it will be created automatically.

By default the function asks for confirmation before attempting to create the file and again if a file with the same name as targetFile already exists. Setting opts.force to true will suppress manual confirmation prompts. Passing opts.prompt allows tailoring your own initial question to the user.

createFileFromWebTemplate

interface CreateFileFromWebTemplateOptions extends FileOperationOptions {
  url: string
  targetFile: string
}
async function createFileFromWebTemplate(opts: CreateFileFromWebTemplateOptions): Promise<void>

Downloads the file given by url and creates a fresh copy of it in the target project.

Contents of url must be accessible via the node:https.get function and the response will be copied byte-for-byte. Redirects (5) are followed before the fetch fails.

Path to targetFile is relative to process.cwd(), which allows consumers to run npx your-script in their project roots during development. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. If the target directory does not exist, it will be created automatically.

By default the function asks for confirmation before attempting to create the file and again if a file with the same name as targetFile already exists. Setting opts.force to true will suppress manual confirmation prompts. Passing opts.prompt allows tailoring your own initial question to the user.

updateConfigFile

interface UpdateConfigFileOptions extends FileOperationOptions {
  targetFile: string
  newConfig: Record<string | number | symbol, any>
  createMissing?: boolean
}
async function updateConfigFile(opts: UpdateConfigFileOptions): Promise<void>

Takes a path to a configuration file and updates it with the provided newConfig object.

Path to targetFile is relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file currently must use ESM format with either a default export or exactly one named export. The exported value must be a configuration object or a function call with a configuration object as its first argument (e.g. defineConfig({...})). CommonJS module.exports is not supported.

If targetFile does not exist, the function throws an error by default. Setting opts.createMissing to true will instead create the file (including missing directories) as export default {} with newConfig merged in. The user is asked to confirm the creation unless opts.force is true.

The merge is performed using unjs/magicast. It should:

  • preserve comments
  • work recursively to allow deep-merge
  • extend the existing object with new keys from newConfig
  • overwrite keys with the same name with values from newConfig
  • create a unique union in case of arrays

Please report any logical flaws and issues of the process.

Warning: The function will fail if the extracted object is proxied (e.g. when created using defu). In such case, the error would be:

TypeError: 'set' on proxy: trap returned falsish for property '<YOUR_PROPERTY>'

If possible, you need to alter your logic, e.g. by creating a new object via the spread operator.

By default the function asks for confirmation before attempting to alter the targetFile. Setting opts.force to true will suppress manual confirmation prompts. Passing opts.prompt allows tailoring your own initial question to the user.

updateJsonFile

interface UpdateJsonFileOptions extends FileOperationOptions {
  targetFile: string
  jsonKey: string
  patch: DataValue
  createMissing?: boolean
  overwriteArrays?: boolean
}
async function updateJsonFile(opts: UpdateJsonFileOptions): Promise<void>

Takes a path to a JSON file and injects patch under the jsonKey key. The patch is of type DataValue - a custom type defined as follows:

type DataPrimitive = string | number | boolean | null
type DataObject = { [key: string]: DataValue }
type DataArray = DataValue[]
type DataValue = DataPrimitive | DataObject | DataArray

Path to targetFile is relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must be a valid JSON file. It is parsed using plain JSON.parse.

The given jsonKey might point to a nested key using dot notation, e.g. a.b.c. Missing intermediate levels are created as empty objects, and existing intermediate levels that are not objects (primitives, arrays and null) are replaced with objects. Empty key segments (e.g. a..b) and the segments __proto__, constructor and prototype are rejected with an error. If jsonKey does not exist, a new key is added. If patch is an object, its keys are shallow-merged into the existing value. If the existing value is not an object, it is replaced. If patch is an array and the existing value is also an array, the items of patch are appended to it (duplicates are skipped). If the existing non-null value is not an array, it is turned into an array with the existing value as its first item, followed by the new items. This also applies to arrays among the keys of an object patch. Setting opts.overwriteArrays to true results in full array replacement instead. Other values (primitives, arrays replacing a missing or null value and null) replace the existing value. The function tracks if any real change was made (values are compared deeply) and notifies the user if not.

If targetFile does not exist, the function throws an error by default. Setting opts.createMissing to true will instead create the file (including missing directories) as an empty JSON object with patch applied. The user is asked to confirm the creation unless opts.force is true.

By default the function asks for confirmation before attempting to alter the targetFile. Setting opts.force to true will suppress manual confirmation prompts. Passing opts.prompt allows tailoring your own initial question to the user.

updateYamlFile

interface UpdateYamlFileOptions extends FileOperationOptions {
  targetFile: string
  yamlKey: string
  patch: DataValue
  createMissing?: boolean
  overwriteArrays?: boolean
}
async function updateYamlFile(opts: UpdateYamlFileOptions): Promise<void>

Takes a path to a YAML file and injects patch under the yamlKey key. The patch is of type DataValue (see updateJsonFile).

Path to targetFile is relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must be a valid single-document YAML file with a map at its root (an empty file is accepted). It is parsed using eemeli/yaml package.

The given yamlKey might point to a nested key using dot notation, e.g. a.b.c. Missing intermediate levels are created as empty maps, and existing intermediate levels that are not maps (scalars and sequences) are replaced with maps. Empty key segments (e.g. a..b) and the segments __proto__, constructor and prototype are rejected with an error. If yamlKey does not exist, a new key is added. If patch is an object, its keys are shallow-merged into the existing value. If the existing value is not a map, it is replaced. If patch is an array and the existing value is a sequence, the items of patch are appended to it (duplicates are skipped). If the existing non-null value is not a sequence, it is turned into a sequence with the existing value as its first item, followed by the new items. Comments of the existing value are preserved in both cases. This also applies to arrays among the keys of an object patch. Setting opts.overwriteArrays to true results in full array replacement instead. Other values (primitives, arrays replacing a missing or null value and null) replace the existing value. The function tracks if any real change was made (values are compared deeply) and notifies the user if not.

If targetFile does not exist, the function throws an error by default. Setting opts.createMissing to true will instead create the file (including missing directories) as an empty YAML map with patch applied. The user is asked to confirm the creation unless opts.force is true.

By default the function asks for confirmation before attempting to alter the targetFile. Setting opts.force to true will suppress manual confirmation prompts. Passing opts.prompt allows tailoring your own initial question to the user.

updateTextFile

interface UpdateTextFileOptions extends FileOperationOptions {
  targetFile: string
  rowsToAdd: string[]
  allowDuplicates?: boolean
  createMissing?: boolean
}
async function updateTextFile(opts: UpdateTextFileOptions): Promise<void>

Takes a path to a plain text file and appends rowsToAdd at the end of the file, provided they are not already present in the file (as an exact line match). Setting opts.allowDuplicates to true disables this check and all rowsToAdd are appended regardless of the current file contents. The function tracks if any real change was made and notifies the user if not.

Path to targetFile is relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed.

If targetFile does not exist, the function throws an error by default. Setting opts.createMissing to true will instead create the file (including missing directories) containing rowsToAdd. The user is asked to confirm the creation unless opts.force is true.

By default the function asks for confirmation before attempting to alter the targetFile. Setting opts.force to true will suppress manual confirmation prompts. Passing opts.prompt allows tailoring your own initial question to the user.

removeFromJsonFile

interface RemoveFromJsonFileOptions extends FileOperationOptions {
  targetFile: string
  jsonKey: string
}
async function removeFromJsonFile(opts: RemoveFromJsonFileOptions): Promise<void>

Takes a path to a JSON file and removes the specified jsonKey.

Path to targetFile is relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist and be a valid JSON file. It is parsed using plain JSON.parse.

The given jsonKey might point to a nested key using dot notation, e.g. a.b.c. If the key is not present, the file is left untouched and the user is notified.

By default the function asks for confirmation before attempting to alter the targetFile. Setting opts.force to true will suppress manual confirmation prompts. Passing opts.prompt allows tailoring your own initial question to the user.

removeFromTextFile

interface RemoveFromTextFileOptions extends FileOperationOptions {
  targetFile: string
  searchText: string
}
async function removeFromTextFile(opts: RemoveFromTextFileOptions): Promise<void>

Takes a path to a plain text file and removes all lines that include the given searchText. The matching is done using String.includes(), so partial matches within a line will cause that line to be removed. The function tracks if any real change was made and notifies the user if not.

Path to targetFile is relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist.

By default the function asks for confirmation before attempting to alter the targetFile. Setting opts.force to true will suppress manual confirmation prompts. Passing opts.prompt allows tailoring your own initial question to the user.

removeFromYamlFile

interface RemoveFromYamlFileOptions extends FileOperationOptions {
  targetFile: string
  yamlKey: string
}
async function removeFromYamlFile(opts: RemoveFromYamlFileOptions): Promise<void>

Takes a path to a YAML file and removes the specified yamlKey.

Path to targetFile is relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist and be a valid single-document YAML file. It is parsed using eemeli/yaml package, which preserves comments and formatting of the untouched parts of the file.

The given yamlKey might point to a nested key using dot notation, e.g. a.b.c. If the key is not present (including when the file is empty or its root is not a map), the file is left untouched and the user is notified.

Comments attached to the removed key are removed as well, with two exceptions. A comment directly above the first root-level key is treated as a file header and kept at the top of the file. A comment left above a nested map that became empty is dropped.

By default the function asks for confirmation before attempting to alter the targetFile. Setting opts.force to true will suppress manual confirmation prompts. Passing opts.prompt allows tailoring your own initial question to the user.

deletePath

interface DeletePathOptions extends FileOperationOptions {
  targetPath: string
}
async function deletePath(opts: DeletePathOptions): Promise<void>

Deletes the given targetPath (a file or a directory, recursively) from FS. Path is resolved relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed.

If the targetPath does not exist, nothing is deleted and the user is notified.

By default the function asks for confirmation before attempting to delete the targetPath. Setting opts.force to true will suppress manual confirmation prompts. Passing opts.prompt allows tailoring your own initial question to the user.

List of content checkers

pathExists

interface PathExistsOptions {
  targetPath: string
}
function pathExists(opts: PathExistsOptions): boolean

Checks if the specified targetPath (a file or a directory) exists on FS. Path is resolved relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed.

If the path exists, the function returns true, false otherwise.

hasJsonKey

interface HasJsonKeyOptions {
  targetFile: string
  jsonKey: string
}
function hasJsonKey(opts: HasJsonKeyOptions): boolean

Checks whether the given jsonKey exists in the JSON file located at targetFile. Path is resolved relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist and be a valid JSON file.

The given jsonKey might point to a nested key using dot notation, e.g. a.b.c. If the key is present, the function returns true, false otherwise.

hasYamlKey

interface HasYamlKeyOptions {
  targetFile: string
  yamlKey: string
}
async function hasYamlKey(opts: HasYamlKeyOptions): Promise<boolean>

Checks whether the given yamlKey exists in the YAML file located at targetFile. Path is resolved relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist and be a valid single-document YAML file. It is parsed using eemeli/yaml package.

The given yamlKey might point to a nested key using dot notation, e.g. a.b.c. If the key is present, the function resolves to true, false otherwise (including when the file is empty or its root is not a map).

Unlike other checks, this function runs asynchronously, because the YAML parser is loaded lazily on demand.

hasText

interface HasTextOptions {
  targetFile: string
  pattern: string | RegExp
  exact?: boolean
}
function hasText(opts: HasTextOptions): boolean

Checks whether the given pattern exists in the text file located at targetFile. Path is resolved relative to process.cwd(). Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist.

The pattern might be a plain string or a regular expression. The file is checked line by line. If the pattern is found, the function returns true, false otherwise. By default partial matches are allowed for string patterns (exact defaults to false). If you set the optional exact property to true, the string must completely match at least one line in the file. Surrounding whitespace is trimmed from the lines and from string patterns in both cases. The exact property is ignored for regular expressions.

getPackageManager

function getPackageManager(): 'npm' | 'yarn' | 'pnpm' | 'deno' | 'bun'

Tries to detect the package manager (or runtime) used in the current environment by checking for Deno and Bun global variables and the npm_config_user_agent environment variable. Falls back to npm if the checks fail to detect anything else.

List of terminal helpers

promptUser

interface PromptUserOptions {
  question: string
  input?: NodeJS.ReadableStream
  output?: NodeJS.WritableStream
}
async function promptUser(opts: PromptUserOptions): Promise<boolean>

Prints out a question (with (y/N): appended) to the console and waits for the input. Returns true when the user answers y or yes (case-insensitive) and false otherwise.

By default it uses process.stdin and process.stdout streams. To use custom NodeJS streams, pass input and output directly in opts, e.g. promptUser({ question: 'Continue?', input, output }).

showMessage

interface ShowMessageOptions {
  message: string
  linesAfter?: number
}
function showMessage(opts: ShowMessageOptions): void

Prints out a message to process.stdout and adds linesAfter newlines after it (default is 1). Set linesAfter: 0 to omit newlines. This function is synchronous.

showError

interface ShowErrorOptions {
  message: string
  linesAfter?: number
}
function showError(opts: ShowErrorOptions): void

Prints out a message to process.stderr and adds linesAfter newlines after it (default is 1). Set linesAfter: 0 to omit newlines. This function is synchronous.

List of other utils

getEnvValue

interface GetEnvValueOptions {
  key: string
  envFilePath?: string
}
function getEnvValue(opts: GetEnvValueOptions): string | undefined

Reads a .env file and returns the value of the specified key (without surrounding quotes) or undefined if the file or the key is not found. By default it reads from .env in the current working directory at call time (usually the root of the project). You can specify a custom path to the .env file with the envFilePath property.

parseQualifiedPath

interface ParseQualifiedPathOptions {
  path: string
}
function parseQualifiedPath(opts: ParseQualifiedPathOptions): { pkg: string; file: string }

Expects a path to a file in "package:relative/path/to/file" format and splits it into { pkg, file }. The package name can be scoped (e.g. @scope/package). Throws an error if the input format is invalid.

resolvePackagePath

interface ResolvePackagePathOptions {
  packageName: string
}
function resolvePackagePath(opts: ResolvePackagePathOptions): string

Resolves a package's root directory from the target app (CWD). Returns CWD itself if its package.json has the same name (i.e. the package is being developed), otherwise looks for the package inside node_modules in CWD. The package name can be scoped (e.g. @scope/package). Throws an error if the package cannot be found.

Tech stack

See Changelog for project history and development.

Report bugs & contact

Use GitHub issues to report bugs / propose enhancements / give feedback:

https://github.com/AloisSeckar/elrh-cosca/issues