npm.io
0.8.3 • Published 4d agoCLI

@bemedev/dev-utils

Licence
MIT
Version
0.8.3
Deps
10
Size
285 kB
Vulns
0
Weekly
327

@bemedev/dev-utils

A collection of utilities for Node.js development, including build tools, testing utilities, and configuration management.

Modules

Module Description
vitestExclude Vitest plugin for glob-based include/exclude
vitestExtended Extended test helpers for Vitest
rolldownConfig Rolldown bundler configuration factory
buildTests CLI tooling to build and test packages

Note (v0.8.1): The ./vitest-alias export has been removed. Path alias resolution is now handled natively by Vite via resolve.alias with vite-tsconfig-paths or the built-in resolve.tsConfigPaths option. Remove any aliasTs() / createAlias() usage and configure aliases directly in your vite / vitest config.


Installation

pnpm add @bemedev/dev-utils
# or
npm install @bemedev/dev-utils
# or
yarn add @bemedev/dev-utils

Peer dependencies: typescript ^5 || ^6


Usage

Each module is available as a dedicated sub-path export — there is no root barrel export:

import { exclude } from '@bemedev/dev-utils/vitest-exclude';
import {
  createTests,
  createFakeWaiter,
} from '@bemedev/dev-utils/vitest-extended';
import { defineConfig } from '@bemedev/dev-utils/rolldown';
import { addTarball, cleanup } from '@bemedev/dev-utils/build-tests';

vitestExclude

Vitest plugin that uses glob patterns to dynamically build test.include and coverage.include lists — no need to maintain test.exclude manually.

exclude(args?)

Uses default patterns (src/**/*.ts for coverage, src/**/*.{test,spec}.{ts,js,tsx,jsx} for tests) and applies optional ignore lists.

import { exclude } from '@bemedev/dev-utils/vitest-exclude';

// vitest.config.ts
export default defineConfig({
  plugins: [
    exclude({
      ignoreTestFiles: ['src/fixtures/**'],
      ignoreCoverageFiles: ['src/**/*.types.ts'],
    }),
  ],
});

Parameters (args)

Property Type Description
ignoreTestFiles string[] | undefined Glob patterns to exclude from test discovery
ignoreCoverageFiles string[] | undefined Glob patterns to exclude from coverage

Returns a Vitest Plugin.


exclude.withPattern(patterns, args)

Same as exclude, but lets you provide custom glob patterns instead of the defaults.

exclude.withPattern(
  {
    patternTest: 'tests/**/*.test.ts',
    patternCov: 'src/**/*.ts',
  },
  { ignoreTestFiles: ['tests/fixtures/**'] },
);

Parameters

Name Type Description
patternTest string | string[] Glob(s) for test file discovery
patternCov string | string[] Glob(s) for coverage file discovery
args same as exclude Optional ignore lists

Returns a Vitest Plugin.


create(args?)

Plain async function (not a plugin) that resolves glob patterns and returns { files, coverage } string arrays using the default patterns.

import { create } from '@bemedev/dev-utils/vitest-exclude';

const { files, coverage } = await create({
  ignoreTestFiles: ['src/fixtures/**'],
  ignoreCoverageFiles: ['src/**/*.types.ts'],
});

Parameters (args)

Property Type Description
ignoreTestFiles string[] | undefined Glob patterns to exclude from test discovery
ignoreCoverageFiles string[] | undefined Glob patterns to exclude from coverage

Returns Promise<{ files: string[]; coverage: string[] }>.


create.withPattern(patterns, args)

Same as create, but accepts custom glob patterns for both test and coverage discovery.

import { create } from '@bemedev/dev-utils/vitest-exclude';

const { files, coverage } = await create.withPattern(
  {
    patternTest: 'tests/**/*.test.ts',
    patternCov: 'src/**/*.ts',
  },
  { ignoreTestFiles: ['tests/fixtures/**'] },
);

Parameters

Name Type Description
patternTest string | string[] Glob(s) for test file discovery
patternCov string | string[] Glob(s) for coverage file discovery
args same as create Optional ignore lists

Returns Promise<{ files: string[]; coverage: string[] }>.


vitestExtended

Extended helpers for writing structured, type-safe Vitest tests.

createTests(func, args?)

Creates a reusable test suite for a function with acceptation, success, and fails runners.

import { createTests } from '@bemedev/dev-utils/vitest-extended';

// add.ts
export const add = (a: number, b: number) => a + b;

// add.test.ts
const { acceptation, success, fails } = createTests(add);

describe('add', () => {
  describe('acceptation', acceptation());

  describe(
    'success',
    success(
      { invite: '1 + 2 = 3', parameters: [1, 2], expected: 3 },
      { invite: '0 + 0 = 0', parameters: [0, 0], expected: 0 },
    ),
  );
});

Note: When the first parameter is an array, wrap it in another array: parameters: [[1, 2, 3]].

Parameters

Name Type Description
func Fn The function under test
args.transform NextFn<F> | undefined Optional transformer applied to the return value
args.toError ToError_F<F> | undefined Optional function that derives an expected error from the input

Returns { acceptation, success, fails }:

Method Signature Description
acceptation() () => void Runs two checks: function is defined and is a function
success(...cases) () => void Runs test.concurrent.each for passing cases
fails(...cases) () => void Runs test.concurrent.each and expects thrown errors

createTests.withImplementation(f, opts)

Same as createTests but replaces the implementation with a vi.fn() mock before the test suite runs.

const { acceptation, success } = createTests.withImplementation(myFn, {
  name: 'myFn',
  instanciation: async () => realImpl,
});

opts properties

Property Type Description
instanciation () => Promise<F> | F Factory called in beforeAll to set mock implementation
name string Display name used in acceptation tests
transform NextFn<F> | undefined Optional return-value transformer
toError ToError_F<F> | undefined Optional error deriver

doneTest(invite, fn, options?)

Wraps a callback-style (done-based) test into a Vitest test. The test passes only when done() is called within the timeout. Supports both sync and async test functions.

import { doneTest } from '@bemedev/dev-utils/vitest-extended';

// Async example with await
doneTest('async callback', async done => {
  await service.start();
  await service.send('event');
});

// Sync example
doneTest('fires callback', done => {
  emitter.once('event', done);
  emitter.emit('event');
});

Parameters

Name Type Default Description
invite string Test description
fn (done: () => void) => void | Promise<void> Test body (sync or async, receives done)
options number | TestOptions 100 Timeout in ms or Vitest TestOptions

Variants

Variant Description
doneTest.fails(invite, fn, options?) Expects the test to fail
doneTest.concurrent(invite, fn, options?) Runs the test concurrently

createFakeWaiter(vi)

Creates a fake-timer-aware async waiter. If fake timers are active it advances them; otherwise it uses a real sleep.

import { createFakeWaiter } from '@bemedev/dev-utils/vitest-extended';

const wait = createFakeWaiter(vi);

test('advances time', async () => {
  vi.useFakeTimers();
  await wait(500); // advances fake timers by 500 ms
});

Parameters

Name Type Description
vi VitestUtils Vitest vi object

Returns (ms?: number, times?: number) => Promise<void>

Variants

Variant Signature Description
createFakeWaiter.withDefaultDelay(vi, ms?) (index, times?) => [string, () => Promise<void>] Returns a labelled waiter with a fixed delay
createFakeWaiter.all(vi) (index, ms?, times?) => [string, () => Promise<void>] Returns a labelled waiter with configurable delay and repetitions

useEach(func, transform?)

A typed wrapper around test.concurrent.each for synchronous functions.

import { useEach } from '@bemedev/dev-utils/vitest-extended';

const run = useEach(add);

run(['1+2', [1, 2], 3], ['0+0', [0, 0], 0]);

Parameters

Name Type Description
func Fn Function under test
transform NextFn<F> | undefined Optional return-value transformer

Returns (...cases: TestArgs2<F>) => void


useEachAsync(func, transform?)

Same as useEach but awaits the function result.


useErrorAsyncEach(func, transform?, toError?)

Runs async error cases using expect(fn).rejects.toThrowError(error).

const runErrors = vitestExtended.useErrorAsyncEach(
  divide,
  undefined,
  ([_, b]) => (b === 0 ? 'Division by zero' : undefined),
);

runErrors(['divide by 0', [1, 0], undefined]);

useTestFunctionAcceptation(f, name?) / useTFA

Runs two acceptation checks: the function is defined and is actually a function.

vitestExtended.useTFA(myFunction, 'myFunction');

Utility helpers
Export Signature Description
isDefined(value?) (value?: T | null) => value is T Type guard — not undefined or null
isFunction(arg) (arg: any) => boolean Type guard — checks if arg is a callable function
identity(value) <T>(value: T) => T Returns the value unchanged
defaultEquality(value, expected) (a, b) => void Strict equality; sorts arrays before comparing

rolldownConfig

Rolldown bundler configuration factory with built-in plugins for TypeScript declarations, path aliasing, circular dependency detection, and tree-shaking externals.

defineConfig(additionals?)

Shorthand for defineConfig.default.

defineConfig.default(params?)

Produces a full Rolldown config with the default plugin pipeline:

  1. alias — resolves tsconfig path aliases (rollup-plugin-tsc-alias)
  2. tsPaths — resolves tsconfig path imports at bundle time (rollup-plugin-tsconfig-paths)
  3. circulars — warns on circular dependencies (rollup-plugin-circular-dependency)
  4. externals — marks dependencies and peerDependencies as external (rollup-plugin-node-externals)
  5. typescript — emits .d.ts declarations via the TypeScript compiler API
  6. clean — removes JS outputs for files that produce empty chunks
// rolldown.config.ts
import { defineConfig } from '@bemedev/dev-utils/rolldown';

export default defineConfig.default({
  dir: 'lib',
  sourcemap: true,
  excludesTS: ['src/fixtures/**/*.ts'],
});

Params properties

Property Type Default Description
dir string 'lib' Output directory
sourcemap boolean false Emit sourcemaps
declarationMap boolean undefined Emit declaration maps
excludesTS string | string[] [] Extra TS files to exclude from declaration emit
circularDeps string | string[] [] Extra patterns exempt from circular-dep warnings
ignoresJS string | string[] [] Patterns whose empty-bundle warnings are suppressed
externals string | string[] [] Additional packages to mark as external
plugins (RolldownPluginOption | PluginKey)[] default order Custom plugin list; string keys select built-in plugins

Default excluded files (always applied):

**/node_modules/**/*
**/__tests__/**/*
**/*.test.ts  /  **/*.test-d.ts
**/*.fixtures.ts  /  **/fixtures.ts
src/fixtures/**/*.ts

Output format: both esm (.js) and cjs (.cjs), with preserveModules: true.


defineConfig.bemedev(params?)

Opinionated preset that extends defineConfig.default with:

  • Sourcemaps enabled by default
  • DEFAULT_CIRCULAR_DEPS merged into circularDeps (**/types.ts, **/*.types.ts, …)
  • DEFAULT_EXCLUDE merged into excludesTS
  • DEFAULT_CIRCULAR_DEPS merged into ignoresJS
export default defineConfig.bemedev({
  excludesTS: ['src/my-fixtures/**'],
});

Plugin builders (PLUGIN_BUILDERS)

Individual plugin factories are available for custom pipeline assembly:

Key Function Wraps
alias alias(options?) rollup-plugin-tsc-alias
tsPaths tsPaths(options?) rollup-plugin-tsconfig-paths
circulars circulars(options?) rollup-plugin-circular-dependency
externals externals(options?) rollup-plugin-node-externals
typescript typescript(options?) TypeScript compiler API + tsc-alias
clean clean(options?) Custom post-build JS cleanup

Pass plugin keys as strings in the plugins array to reorder or subset them:

defineConfig.default({
  plugins: ['alias', 'tsPaths', 'externals', 'typescript'],
});

buildTests

CLI tooling that packs your built library as a local tarball, installs it under the alias this-gen-1, runs your test suite against the real distributable, then tears down.

Available as a sub-path export:

import {
  addTarball,
  cleanup,
  customImport,
  THIS1,
} from '@bemedev/dev-utils/build-tests';
CLI usage

The binary is build-tests. It exposes three subcommands:

# Install the tarball (pre-test hook)
build-tests pre

# Run tests with pre/post lifecycle
build-tests test [--pretest] [--posttest]

# Clean up tarball + uninstall (post-test hook)
build-tests post

package.json integration (auto-configured):

{
  "scripts": {
    "pretest": "build && build-tests pre",
    "posttest": "fmt && build-tests post"
  }
}

addTarball()

Programmatic API for the pre step:

  1. Builds the package.json for the outDir (strips scripts, devDependencies, files; fixes paths)
  2. Runs pnpm pack into .pack/
  3. Installs the resulting .tgz as this-gen-1 in devDependencies
import { addTarball } from '@bemedev/dev-utils/build-tests';

await addTarball();

cleanup()

Programmatic API for the post step:

  1. Removes the .pack/ folder
  2. Runs pnpm remove this-gen-1
import { cleanup } from '@bemedev/dev-utils/build-tests';

cleanup();

customImport(fn?)

Dynamically imports the installed package (this-gen-1) and optionally transforms its exports via a mapper function.

import { customImport } from '@bemedev/dev-utils/build-tests';

// Import the default export as-is
const out = await customImport();

// Import and pick a specific export
const myFn = await customImport(mod => mod.myFn);

Parameters

Name Type Default Description
fn (out: IndexImport) => T identity Mapper applied to the imported module; defaults to no-op

Returns Promise<T> — the mapped module output.


THIS1

Constant string 'this-gen-1' — the package alias used by addTarball and cleanup when installing/removing the local tarball.

import { THIS1 } from '@bemedev/dev-utils/build-tests';

console.log(THIS1); // 'this-gen-1'

Types
Name Description
IndexImport Type of the module returned by a dynamic import(THIS1) call (any)


Licence

MIT

CHANGELOG

Read CHANGELOG.md for details about all changes and versions.


Author

chlbri (bri_lvi@icloud.com)

My github


Keywords