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

@gingacodemonkey/config

v0.1.0

Published

A collection of TypeScript configurations, based on Total TypeScript's TSConfig Cheat Sheet. ESLint rules based on @epic-web/config.

Readme

@gingacodemonkey/config

Opinionated config bundle for TypeScript + ESLint + git tooling. Bundles decisions from Total TypeScript's TSConfig Cheat Sheet and @epic-web/config.

Requires: Node ≥ 24, pnpm ≥ 10


Quickstart

pnpm add -D @gingacodemonkey/config
pnpm exec gingacodemonkey-config

The interactive CLI prompts you to select tools and generates all config files automatically.

For CI / non-interactive use:

pnpm exec gingacodemonkey-config --all --yes

Tools

The CLI can set up any combination of:

| Tool | What it sets up | |------|-----------------| | ts | tsconfig.json with preset selection, and the #src package import | | eslint | eslint.config.ts | | husky | Git hooks via Husky | | commitLint | Conventional commit linting | | lintStaged | .lintstagedrc — run ESLint on staged files | | knip | Dead code & unused dependency detection | | jscpd | Copy-paste detection | | githubActions | CI workflows (test, lint, knip, ts-check, Claude PR review). In a pnpm workspace, one root ci.yml replaces test, lint and ts-check | | bumpy | Versioning and releases via Bumpy (see Releases) | | workspace | A pnpm workspace with TS and ESLint configs shared from sharedConfig/ (see pnpm workspaces). Not part of --all. |


CLI Reference

pnpm exec gingacodemonkey-config [options]

  --all, -a                   Select all tools (except workspace)
  --yes, -y                   Accept all defaults (non-interactive; setup needs --all or --tool)
  --no-release                Exclude bumpy when using --all
  --release-npm               Also publish to npm (default: GitHub releases only)
  --tool=<name>               Select a specific tool (repeatable; an unknown name is an error)
  --update, -u                Reconcile existing configs with this release's defaults (see below)
  --overwrite                 With --update: replace values you changed in files the CLI owns

TypeScript options (used with --yes):
  --ts-mode=bundler|tsc       Default: bundler
  --ts-dom / --ts-no-dom      Default: dom
  --ts-type=app|library|library-monorepo  Default: app
  --ts-jsx=react|react-jsx|preserve|none  Default: react-jsx for a DOM app, otherwise none
  --ts-outdir=<dir>           Default: dist
  --ts-type-module            Add "type": "module" to package.json

GitHub Actions options (used with --yes):
  --claude-runner=anthropic|bedrock  Claude PR review auth. Default: anthropic

Workspace options (with --tool=workspace):
  --workspace-packages=<glob> Package glob for a new workspace (repeatable, default: packages/*)
  --workspace-update-all      Existing workspace: link every package without asking
  --workspace-catalog         Move external versions repeated across manifests into the pnpm catalog
  --workspace-catalog-resolve=<name>@<range>  Catalog <name> at <range> where manifests disagree (repeatable)

  --help, -h                  Show help

Examples

# All tools, accept defaults, no release tooling
pnpm exec gingacodemonkey-config --all --no-release --yes

# Specific tools only
pnpm exec gingacodemonkey-config --tool=ts --tool=eslint --yes

# Full TypeScript + React app
pnpm exec gingacodemonkey-config --all --yes --ts-mode=bundler --ts-dom --ts-type=app --ts-jsx=react-jsx

# Releases to GitHub and npm
pnpm exec gingacodemonkey-config --tool=bumpy --release-npm --yes

# New pnpm workspace, or link every package in an existing one
pnpm exec gingacodemonkey-config --tool=workspace --yes --workspace-update-all

pnpm workspaces

--tool=workspace puts the TypeScript and ESLint rules in one place, sharedConfig/ at the workspace root, and links every package to it. It replaces the single-package ts and eslint setup, so selecting those tools alongside it does nothing extra.

package.json                  lint / lint:ts / lint:fix / test / build run across packages; check runs them all
pnpm-workspace.yaml           packages globs, the default catalog + pnpm settings
tsconfig.json                 solution config: references every package, for tsc --build
sharedConfig/
  tsconfig.base.json          extends the preset chosen with the --ts-* flags
  eslint.config.ts            the shared rules; add your own to extraRules
  eslint.config.style.ts
packages/<name>/
  tsconfig.json               extends ../../sharedConfig/tsconfig.base.json; build options + references
  eslint.config.ts            re-exports ../../sharedConfig/eslint.config.ts
  eslint.config.style.ts

Run it from the workspace root. TypeScript, ESLint and @gingacodemonkey/config are installed there, not in each package. Paths are relative, so packages at any depth (apps/web/site) resolve the shared configs.

New workspace. Without a packages: list in pnpm-workspace.yaml (or with no package.json at all), setup creates the workspace with the globs from --workspace-packages (default packages/*) and adds a sample package (packages/example) whose source and node:test test import each other through #src/…. pnpm check passes straight away.

Root scripts. These are the commands to run from the workspace root, locally and in CI. With githubActions selected (or run later in an existing workspace), setup writes .github/workflows/ci.yml, which installs once with the frozen lockfile and runs pnpm lint, pnpm lint:ts, pnpm test and pnpm build on pull requests and pushes to main. It's read-only, and a newer push cancels the run it supersedes. --update keeps ci.yml current like the other workflows, and in a workspace doesn't look for the single-package ci_test.yml, lint.yml or ts-check.yml.

| Script | Runs | |---|---| | lint, lint:fix, lint:ts, test, build | pnpm -r --if-present <script>: every package that has the script, once each; packages without it are skipped | | check | pnpm lint && pnpm lint:ts && pnpm test && pnpm build |

A failure in any package fails the root command. pnpm -r never includes the workspace root, so these can't call themselves. If the root already has one of these scripts with your own command, setup keeps it and says so; an earlier generated value (pnpm -r lint) is replaced.

Existing workspace. Setup writes the shared configs and root scripts, keeps your globs and pnpm settings, then offers to link every discovered package. In interactive mode it asks once. With --tool=workspace it only links them when you pass --workspace-update-all; otherwise it reports them as skipped. For each linked package:

  • Missing lint, lint:fix and lint:ts (tsc --build) scripts are added, and an earlier generated lint:ts (tsc --noEmit) is replaced. A script with your own command is kept and reported; other scripts stay.
  • tsconfig.json gets the shared base as its first extends, so the package's own extends and compilerOptions still win.
  • eslint.config.ts and eslint.config.style.ts are overwritten with re-exports of the shared configs. Put package-specific rules in sharedConfig/eslint.config.ts or restore your own file from git.
  • package.json gets the #src package import, mapped to the package's own src/ whatever its depth (in tsc mode, plus rootDir: "src" in its tsconfig.json if it sets none).

Setup prints each package as updated, unchanged, skipped or could not be migrated. A package is left untouched and reported when its package.json or tsconfig.json isn't plain JSON (for example, a tsconfig.json with comments). Rerunning setup changes nothing.

Internal dependencies (workspace: protocol). A linked package's dependencies on other packages of the workspace use pnpm's workspace protocol, so install always links the local package and fails, rather than downloading a registry copy, when it's missing. Setup and --update apply this to dependencies, devDependencies, optionalDependencies and peerDependencies, and never move an entry to another section:

  • A plain version range the local package's version satisfies (^1.0.0, ~1.2.0, *) becomes workspace:^. An npm: alias of a workspace package becomes workspace:<name>@^.
  • An existing workspace: spec of any form (workspace:*, workspace:~, workspace:^1.2.0) is kept.
  • Anything else is reported and left as is: a range the local version doesn't satisfy (rewriting it would change which version you asked for, so fix the range or the local version), a dist-tag such as latest, a file:, link: or git spec, or a local package without a valid version.
  • Only exact names count, so a registry package @acme/lib-extra is never mistaken for the workspace's @acme/lib.

pnpm pack and pnpm publish turn workspace:^ into a caret range on the local version, for example "@acme/lib": "^1.2.0", so published packages carry ordinary semver ranges.

Undeclared imports. Setup and --update also scan each linked package's source files (.ts, .tsx, .mts, .cts, .js, .jsx, .mjs, .cjs; not node_modules/, dist/, dot-directories or nested packages) for import, export … from, import() and require() of another workspace package, including subpaths such as @acme/lib/utils. Each one the package declares in no dependency section is reported as <importer> imports <imported> but doesn't declare it. Nothing is added for you, since whether it's a dependency, dev dependency or peer is your call: add it with workspace:^.

Library packages. A linked package is a library when the shared base extends a library or library-monorepo preset (--ts-type), its package.json isn't "private": true and it has src/index.ts. Everything else is an application and never gets publishing fields, so mark apps "private": true. Libraries need --ts-mode=tsc: bundler mode type-checks with noEmit and leaves building to a bundler the CLI doesn't choose, so setup fails and names the library packages instead of writing exports that point at nothing (--update reports them as skipped). For each library, setup and --update manage:

| Where | Value | |---|---| | tsconfig.json | outDir (--ts-outdir, default dist), rootDir: "src", tsBuildInfoFile: "<outDir>/.tsbuildinfo"; values already set are the package's own and are used as is | | package.json | "type": "module", exports["."] (types → dist/index.d.ts, default → dist/index.js), main and types (the same files, for resolvers that ignore exports), files (dist without compiled tests or the build state), scripts.build: "tsc" |

Nothing points at src/, so pnpm pack ships compiled JavaScript and declarations only. Further entry points are opt-in, as source paths that are exported as their compiled output (* patterns work):

"gingacodemonkey": { "subpathExports": { "./utils": "./src/utils.ts", "./icons/*": "./src/icons/*.ts" } }

Subpaths you add to exports yourself, a custom root export (a string or conditions object), your own imports aliases and extra files entries are kept; one of your exports that resolves to TypeScript source is reported. A library's generated #src/*.ts import points only at dist/, so the tarball holds every target. A value of yours that differs from a managed one (say your own main or build) is kept and reported by setup, and is a conflict for --update. With a custom root export, main and types are left to you.

Project references. After linking, setup makes the workspace build with tsc --build, so TypeScript checks packages incrementally and each dependency before its consumers. pnpm exec tsc --build from the root builds every package; pnpm lint:ts runs each package's tsc --build (dependencies first), which also builds what it references.

  • Which packages. Every package whose tsconfig.json extends the shared base. Others (not linked, or a tsconfig.json that isn't plain JSON) are left out and listed; a package without a tsconfig.json isn't TypeScript, so it's skipped silently.
  • Root tsconfig.json. A solution config with "files": [] and one reference per package. An existing root tsconfig.json keeps its own files/include and references; "files": [] is only added when it has neither.
  • Package references come from the dependencies on other workspace packages, in every section (dependencies, devDependencies, optionalDependencies, peerDependencies). A reference to a workspace package is added when you add the dependency and removed when you remove it, each package once. References to anything else are yours and are kept.
  • Build options. Each package gets composite: true, outDir (--ts-outdir, default dist) and tsBuildInfoFile: "<outDir>/.tsbuildinfo", but only those it doesn't set itself: your own outDir, tsBuildInfoFile and every other option win. (The presets' own tsBuildInfoFile points inside node_modules/@gingacodemonkey/config, a file every package would share.) Bundler presets set noEmit, and a referenced project must emit, so with a bundler preset packages also get noEmit: false and emitDeclarationOnly: true: tsc --build writes only .d.ts files and your bundler still builds the JavaScript. With a tsc preset it emits JavaScript as well. Switching the shared base from bundler to tsc later leaves those two options in place; remove them from the packages yourself. These bundler-mode options only apply to applications and private packages: a publishable library needs --ts-mode=tsc (see Library packages above).
  • Output. Declarations, JavaScript and build info go only to each package's outDir (plus its own tsBuildInfoFile, if you set one elsewhere). Setup adds those locations to the root .gitignore unless a line already ignores them: dist/ matches every package's dist; a custom path such as out/types is anchored to its package.
  • Can't take part. A package whose own tsconfig.json sets composite: false or noEmit: true is left out and reported. Options set in a package's own extra extends aren't inspected; tsc --build reports those itself.
  • Errors. Setup fails, and --update stops with nothing written, on a dependency cycle (the message lists every package in it), a workspace: dependency no package provides, a dependency on a package that can't take part, or a reference to a config that doesn't exist. Fix the dependency or reference and rerun.

--update keeps the references, build options, root solution and .gitignore entries in sync the same way.

Package boundaries

The default ESLint config, and so sharedConfig/eslint.config.ts, includes gingacodemonkey/workspace-boundaries: a package may use another workspace package only by name, through what that package's package.json exports declares. A package without exports exposes only its bare name. The rule ships in @gingacodemonkey/config/eslint rather than in the generated file, so a shared config from any release, edited or not, picks it up when you upgrade the package; outside a pnpm workspace it does nothing.

| Import from @scope/web | | |---|---| | @scope/lib, @scope/lib/utils (declared in exports) | allowed | | #src/local.ts, ./local.ts (the package's own files) | allowed | | external packages, node: builtins | allowed | | @scope/lib/src/internal, @scope/lib/utils.ts, @scope/lib/index.js (not in exports, or null there) | error | | ../../packages/lib/src/internal.ts, or an absolute path / file: URL into another package | error | | #lib/* mapped in @scope/web's own imports to ../../packages/lib/src/* or @scope/lib/src/* | error |

Packages come from the globs in the pnpm-workspace.yaml above the linted file (nested globs, single directories and ! exclusions included); a package nested inside another is its own boundary. Every import is resolved to where it really lands before it's judged, following symlinks and the file system's case, so spellings like ./../, //, /./, a trailing /, .. that re-enters a package, node_modules/@scope/lib/… or a different extension can't get round it. A bare import must be written canonically (@scope/lib/./utils is rejected; write @scope/lib/utils). Files outside every package, such as root tooling, aren't checked. An editor's long-running ESLint server reads the packages once, so restart it after adding a package or changing exports.

For exceptions such as generated code or tooling, add an allow list to extraRules in sharedConfig/eslint.config.ts. Each glob matches what the import reaches, written as <package name>/<path in the package>, so it covers both bare and relative spellings:

export const extraRules: Array<Linter.Config> = [
  { rules: { "gingacodemonkey/workspace-boundaries": [ "error", { allow: [ "@scope/lib/src/generated/**" ] } ] } },
];

For a single import, use // eslint-disable-next-line gingacodemonkey/workspace-boundaries with a reason.

Catalogs

pnpm catalogs keep one range per external dependency in pnpm-workspace.yaml, and manifests use it with catalog:, in whatever section they declare it. pnpm swaps catalog: for the catalog's range when it packs or publishes, so consumers of a published package see ordinary ranges.

New workspace. The shared toolchain (typescript, @types/node, eslint, jiti) goes in the default catalog, and the root package.json refers to it with catalog:. A version the root already declared is kept as the catalog's range, and an entry the catalog already has wins. @gingacodemonkey/config stays pinned in the root manifest, since it must match the CLI that wrote sharedConfig/.

Existing versions. Setup then looks for external versions repeated across the root and package manifests. In interactive mode it offers to move them, and asks about each dependency declared with different ranges. With --tool=workspace, identical ranges move only with --workspace-catalog, and a dependency with differing ranges only with --workspace-catalog-resolve=<name>@<range>. Otherwise setup lists what it left. A range moves when:

  • it's a registry range (^1.2.3, ~1.2, 1.x, >=1 <2). workspace:, file:, link:, npm: aliases, git and tarball URLs, owner/repo shorthands and dist-tags (latest) stay as declared,
  • the dependency isn't another package of the workspace, whatever its spec,
  • it's in dependencies, devDependencies or optionalDependencies. peerDependencies stay literal: a peer range is a promise to consumers and often deliberately wider than what's installed,
  • every manifest that declares the dependency uses the same range, and at least two do (or the default catalog already holds that exact range).

A dependency declared with different ranges, or with a range that differs from its catalog entry, is reported with every range and where it's used, and nothing changes until you pick one. Existing catalog entries, named catalogs (catalogs:) and every other pnpm-workspace.yaml setting and comment are kept. When setup rewrites a manifest and the workspace has a pnpm-lock.yaml, it runs pnpm install so the lockfile holds the catalog: references and a frozen install still passes.

--update. Update reports the same candidates: repeated ranges as skipped (moved and updated with --workspace-catalog) and differing ranges as customized. A catalog: reference must resolve: a missing toolchain entry is added with this release's range, and any other missing entry is reported. Update installs nothing, so run pnpm install after it moves versions. Rerunning setup or update after a move changes nothing.


Updating an existing project

--update brings a project set up by an older release up to the current defaults. It doesn't run setup and doesn't install packages; it only compares and rewrites the files and keys the CLI manages.

pnpm exec gingacodemonkey-config --update                # pick from the tools it detects
pnpm exec gingacodemonkey-config --update --yes          # every detected tool
pnpm exec gingacodemonkey-config --update --tool=husky --yes

Rerunning setup to add a tool installs packages again, and the minimumReleaseAge: 4320 setting written by the first run applies. If the lockfile holds a dependency published less than three days ago (often a peer installed with this package), pnpm refuses the install. Setup then fails and names the package. When pnpm can look up the publish time and your minimumReleaseAge, it also says when the package becomes installable. Wait until then, or use --update for config-only changes, since it installs nothing.

A tool counts as set up when its main file exists (tsconfig.json, eslint.config.ts, .husky/, commitlint.config.js, .lintstagedrc, knip.json, .jscpd.json, .github/workflows/, .bumpy/, sharedConfig/). Without --tool, the pnpm settings in pnpm-workspace.yaml and engines in package.json are updated too; an explicit --tool updates only those tools.

Each managed value ends up as one of:

| Result | When | What happens | |---|---|---| | unchanged | Already the current default (ignoring trailing newlines) | Nothing | | updated | Matches a default an earlier release wrote, or sits in the old ignored pnpm: block. Also an internal dependency with a plain range the local version satisfies | Replaced with the current default (workspace:^ for an internal dependency) | | added | Missing | Written | | customized | A starter file you're expected to edit has your changes: eslint.config.ts, sharedConfig/eslint.config.ts, commitlint.config.js, .lintstagedrc, knip.json, .jscpd.json, .bumpy/_config.json. Also a workspace package tsconfig.json that no longer extends the shared base, a sharedConfig/tsconfig.base.json that doesn't extend a preset, an internal dependency the workspace: policy leaves as is, or an undeclared internal import | Kept as is, even with --overwrite | | conflict | You changed a file or value the CLI owns: hooks, workflows, the setup action, eslint.config.style.ts, package ESLint re-exports, generated scripts, pnpm settings | See below | | skipped | An optional file is missing, such as a workflow you deleted | Left missing |

Files are replaced only when they match, byte for byte apart from trailing whitespace, a version this CLI wrote (hashes in src/update/known-versions.ts). JSON files are compared by value. Scripts and engines are compared entry by entry, and allowBuilds entries you added are kept.

Conflicts. In interactive mode, update asks about each conflict and keeps your value unless you say yes. With --yes or --tool, a conflict stops the run with a report and nothing is written; rerun with --overwrite to replace those values. Running update twice in a row changes nothing the second time.


Releases (Bumpy)

The bumpy tool installs @varlock/bumpy, adds bump (bumpy add) and bump:status (bumpy status) scripts, and writes .bumpy/_config.json plus three workflows: bumpy-check.yml, bumpy-comment.yml and release.yml. It also writes .github/actions/setup/action.yml if it doesn't exist yet.

The flow: each PR adds a bump file (pnpm bump, or label the PR no-bump). Merging to main opens or updates a bumpy/version-packages PR. Merging that PR tags the release and creates a GitHub release.

GitHub releases (default). Nothing is published to npm. The release packs the package with pnpm pack and attaches the tarball to the GitHub release.

npm (opt-in). Answer yes to the npm prompt, or pass --release-npm. The release also publishes to npm with provenance. Configure npm trusted publishing for release.yml. If your package needs a build before publishing, run it from a prepack script.

RC releases (npm only). Add the release-rc label to the version PR to publish a snapshot of main's pending release to the @next dist-tag, e.g. 1.4.0-rc-a1b2c3d. Bumpy comments the install command on the PR. Snapshots create no git tags, GitHub releases or commits. To publish another rc, remove the label and add it again.

BUMPY_GH_TOKEN (recommended). Without it, CI does not run on the version PR. Run pnpm exec bumpy ci setup for guidance.


Claude PR Review Skill

The Claude PR review skill (cc-pr-review-ci) is installed from adrianbrowning/agent-skills. It's automatically installed when you select the GitHub Actions PR review option during setup.

To manually install or update to the latest version:

pnpm dlx skills add adrianbrowning/agent-skills --skill cc-pr-review-ci -a claude-code --copy -y

TypeScript Config Reference

If you need to extend a tsconfig manually rather than using the CLI:

{
  "extends": "@gingacodemonkey/config/<mode>/<dom>/<type>"
}

<mode> — how TypeScript compiles your files:

  • tsc — TypeScript transpiles .ts → .js directly
  • bundler — an external bundler (Vite, Rollup, esbuild, etc.) handles transpilation

<dom>:

  • dom — code runs in the browser
  • no-dom — Node.js / server-only code

<type>:

  • app — standalone application
  • library — published package
  • library-monorepo — published package inside a monorepo

All available presets

@gingacodemonkey/config/tsc/dom/app
@gingacodemonkey/config/tsc/dom/library
@gingacodemonkey/config/tsc/dom/library-monorepo
@gingacodemonkey/config/tsc/no-dom/app
@gingacodemonkey/config/tsc/no-dom/library
@gingacodemonkey/config/tsc/no-dom/library-monorepo

@gingacodemonkey/config/bundler/dom/app
@gingacodemonkey/config/bundler/dom/library
@gingacodemonkey/config/bundler/dom/library-monorepo
@gingacodemonkey/config/bundler/no-dom/app
@gingacodemonkey/config/bundler/no-dom/library
@gingacodemonkey/config/bundler/no-dom/library-monorepo

Overrides

// Add JSX support
{
  "extends": "@gingacodemonkey/config/bundler/dom/app",
  "compilerOptions": { "jsx": "react-jsx" }
}

// Custom output directory
{
  "extends": "@gingacodemonkey/config/tsc/no-dom/library",
  "compilerOptions": { "outDir": "dist" }
}

Explicit .ts imports

Every preset accepts relative imports that end in .ts, .tsx, .mts or .cts:

import { value } from "./value.ts";

The base config sets rewriteRelativeImportExtensions, so tsc presets emit ./value.js in the JavaScript output and Node runs it as-is. Bundler presets don't emit, and the bundler resolves the .ts file itself.

Declaration files keep the .ts specifier (export { value } from "./value.ts" in dist/index.d.ts). That's fine: TypeScript resolves ./value.ts in a .d.ts to the ./value.d.ts next to it, so consumers get the real types.

Limitations:

  • Needs TypeScript 5.7 or later.
  • Only relative specifiers are rewritten. A .ts path that reaches tsc through an alias (paths, or a package.json#imports entry that points at .ts files) stays as .ts in the output. The generated #src import is built for that: in tsc mode it resolves #src/value.ts to the compiled .js file.

#src package imports

Setup gives every TypeScript project, and every linked workspace package, a native package.json#imports entry, so source files import each other from anywhere under src/ without ../../:

// src/features/billing/invoice.ts
import { formatMoney } from "#src/utils/money.ts";

It's on by default in interactive and --yes setup; there's no tool or flag to pick. Write the .ts extension, as with explicit .ts imports (#30). The key is #src/*.ts rather than #src/* because TypeScript refuses a .ts specifier that an alias passes through unchanged (TS2877, since the base config sets rewriteRelativeImportExtensions); extensionless #src/utils/money doesn't resolve anywhere. .tsx files aren't covered by the alias.

The mapping depends on the TypeScript mode, read from the preset the tsconfig extends (sharedConfig/tsconfig.base.json in a workspace):

| Mode | imports["#src/*.ts"] | | |---|---|---| | bundler | "./src/*.ts" | The type-checker, your bundler and Node's type stripping all read the source | | tsc, app or private package | { "types": "./dist/*.d.ts", "gingacodemonkey:source": "./src/*.ts", "default": "./dist/*.js" } | Compiled JavaScript keeps #src/utils/money.ts, which Node resolves to dist/utils/money.js: nothing loads TypeScript from src/ | | tsc, publishable library | { "types": "./dist/*.d.ts", "default": "./dist/*.js" } | Same, with every target inside the published dist/ |

dist is the package's own outDir (--ts-outdir, default dist; one the tsconfig already sets wins, as does declarationDir for types). In tsc mode, setup also sets rootDir: "src" when the tsconfig has none, so src/x.ts compiles to dist/x.js where the mapping points; a single-package tsconfig.json in tsc mode therefore no longer includes eslint.config.ts (ESLint loads it itself). TypeScript maps dist/*.d.ts back to src/ while compiling, so a clean checkout type-checks and builds without existing output. A publishable library is the same as for library packages: a library/library-monorepo preset, not "private": true, and a src/index.ts.

Tests without a build (tsc mode). Run tests with the gingacodemonkey:source condition so #src resolves to src/ and Node strips the types itself:

node --conditions=gingacodemonkey:source --test

The workspace sample package's test script is exactly that in tsc mode (node --test in bundler mode). The condition is for a package's own tests: published consumers and other runs never pass it and use dist/. A publishable library has no src/ in its tarball, so it gets no such condition; build before testing it (tsc --build && node --test).

Existing imports. The entry is merged in: your other aliases (#config, #utils/*, …) keep their values and order. A #src/*.ts with a different value, or a hand-written #src/* (which #src/*.ts would take over for .ts files), is never silently replaced: interactive setup asks, --yes setup keeps it and reports kept your own package.json › imports["#src/*.ts"], and --update reports a conflict that only --overwrite replaces. A mapping this CLI generated for another mode, or for an app that has since become a library, is updated. Rerunning setup or update changes nothing.


ESLint

Two exports:

| Export | Use for | |--------|---------| | @gingacodemonkey/config/eslint | Logic & correctness — run in CI | | @gingacodemonkey/config/styled | Formatting — run pre-commit via lint-staged |

eslint.config.ts

import defaultConfig from "@gingacodemonkey/config/eslint";
export default [...defaultConfig];

eslint.config.style.ts

import styledConfig from "@gingacodemonkey/config/styled";
export default [...styledConfig];

Customizing: see ESLint's shareable config docs.


ESLint Plugins Included

Rules are auto-enabled based on what's installed in your project.

Always enabled

| Plugin | Rules | |--------|-------| | @eslint/js | recommended | | eslint-plugin-sonarjs | recommended — code quality & bug detection | | eslint-plugin-depend | Detects redundant polyfills and bloated deps | | eslint-plugin-no-barrel-files | Prevents barrel/index re-export anti-pattern | | eslint-plugin-promise | Promise best practices (always-return, catch-or-return) | | eslint-plugin-unicorn | unicorn/prefer-node-protocol | | gingacodemonkey (built in) | workspace-boundaries — pnpm workspace packages import each other only through their exports (Package boundaries) |

When typescript is installed

| Plugin | Key rules | |--------|-----------| | typescript-eslint | no-explicit-any, no-floating-promises, no-misused-promises, no-unnecessary-condition, await-thenable, promise-function-async, method-signature-style, array-type: generic |

When react is installed

| Plugin | Key rules | |--------|-----------| | @eslint-react/eslint-plugin | no-nested-component-definitions, no-array-index-key, no-unstable-context-value, DOM safety rules | | eslint-plugin-react | jsx-props-no-spreading, jsx-no-bind | | eslint-plugin-react-hooks | rules-of-hooks, exhaustive-deps + compiler silent failure detection | | eslint-plugin-react-compiler | React Compiler compatibility | | eslint-plugin-react-refresh | Fast Refresh compatibility | | eslint-plugin-react-you-might-not-need-an-effect | Warns on avoidable useEffect patterns | | eslint-plugin-jsx-a11y | Accessibility — the full recommended set, with anchor-is-valid, click-events-have-key-events, no-static-element-interactions as errors |

When vitest / @testing-library / @testing-library/jest-dom are installed

| Plugin | Applied to | |--------|------------| | @vitest/eslint-plugin | Test files | | eslint-plugin-testing-library | Test files | | eslint-plugin-jest-dom | Test files |

/styled export — additional formatting rules

| Plugin | Rules | |--------|-------| | @stylistic/eslint-plugin | Indent (2), quotes (double), semi, trailing commas, brace style (stroustrup), max-len (400) | | eslint-plugin-unused-imports | Removes unused imports on fix | | eslint-plugin-perfectionist | Natural, case-insensitive sorting of imports (builtin → external → internal/#… → parent → sibling → index), named imports/exports, union types, interfaces, object types and JSX props | | typescript-eslint | consistent-type-imports (separate type imports) |