gen-import
v1.11.11
Published
Generate a TypeScript barrel file for your Express/Node project using the TypeScript compiler API
Maintainers
Readme
Table of contents
- What it does
- Console output
- Installation
- Quick start
- Studio
- Generated files
- CLI reference
- Config file
- Circular dependency & barrel safety analysis
- Lazy re-exports
- Module file deferral
- Globals mode
- Watch mode
- Export map
- Programmatic API
- Example output
- Requirements
What it does
gen-import walks your src/ directory, analyses every exported symbol (values, types, and defaults) via the TypeScript compiler API, and writes a deduplicated barrel file. On each run it also:
- Builds a full module dependency graph (static imports,
export *, dynamicimport(),require(), class heritage, decorators) and detects circular dependencies - Classifies the barrel itself as safe / type-safe / ordered / unsafe, and explains exactly which line breaks it at runtime
- Sorts files topologically so the barrel import order is safe for CommonJS
- Optionally emits lazy (getter-based) re-exports so the barrel itself never causes a circular-require crash
- Detects export name collisions across source files
- Diffs against the previous barrel to report newly added exports
- Prints diagnostics (with fix suggestions), a summary box, and an import/export graph, using
boxen+chalk - Opens an optional interactive Studio for exploring files, exports, usages, barrels, unused symbols, and cycles
Console output
Every run prints diagnostics (if any), then two styled boxes.
Diagnostics — one line per finding, worst severity first, with a concrete fix:
error GI002 src/gen-import.ts is inside a cycle with an init-time read — this fails at runtime
src/user/user.service.ts → src/user/user.repository.ts → src/user/user.service.ts
fix: Breaks at src/user/user.repository.ts:4 — class heritage clause (`class X extends Y`).
Run with --safe-barrels to withhold the offending exports and print direct-import lines.Summary box — stats for the current run:
╭──────────────────────── gen-import ────────────────────────╮
│ Source files 6 │
│ Total exports 18 │
│ Language TypeScript │
│ Output file src/gen-import.ts │
│ Module cjs │
│ Globals off │
│ Lazy on │
│ Topo sort on │
│ Import edges 11 │
│ Cycles none │
│ Barrel safe │
│ Collisions none │
│ New exports +3: UserDto, CreateUserDto, UpdateUserDto │
╰──────────────────────────────────────────────────────────────╯Import / Export Graph — shows every source file, its exports, and the barrel it feeds into:
╭────────────────── Import / Export Graph ──────────────────╮
│ ./config/env ──► src/gen-import.ts │
│ ├─ [V] PORT │
│ ├─ [V] NODE_ENV │
│ └─ [V] JWT_SECRET │
│ │
│ ./user/user.dto ──► src/gen-import.ts │
│ ├─ [T] UserDto │
│ ├─ [T] CreateUserDto │
│ └─ [T] UpdateUserDto │
│ │
│ ./middleware/auth.middleware ──► src/gen-import.ts │
│ └─ [V] authMiddleware │
│ │
│ ./user/user.service ──► src/gen-import.ts │
│ └─ [V] UserService │
│ │
│ ./user/user.router ──► src/gen-import.ts │
│ └─ [V] userRouter │
╰─────────────────────────────────────────────────────────────╯Legend: [T] type export · [V] value export · [D] default export (aliased)
Installation
npm install --save-dev gen-import
# or
pnpm add -D gen-import
# or
yarn add -D gen-importQuick start
# Generate source barrel only
npx gen-import
# Source barrel + globals mode (register all exports on Node.js global)
npx gen-import --globals
# Source barrel + app-config barrel
npx gen-import --app-config
# Withhold exports that would put the barrel in a cycle, instead of failing at runtime
npx gen-import --safe-barrels
# Watch src/ and regenerate on every change
npx gen-import --watch
# Open the interactive dependency Studio
npx gen-import studioAdd to package.json scripts:
{
"scripts": {
"gen": "gen-import --app-config"
}
}Then import from the barrel instead of individual files:
// Before
import { UserService } from './user/user.service'
import { UserDto } from './user/user.dto'
import { authMiddleware } from './middleware/auth.middleware'
// After
import { UserService, UserDto, authMiddleware } from './gen-import'Studio
Studio is a local, read-only dependency explorer for TypeScript and JavaScript projects. It scans the complete project (excluding dependencies, build output, and generated barrels), follows tsconfig path aliases, and updates automatically while source files change.
npx gen-import studio
npx gen-import studio --port 5000
npx gen-import studio --host 0.0.0.0
npx gen-import studio --no-openIf the preferred port is occupied, Studio automatically tries the next available port. The browser interface includes:
- File and symbol-level import/export graphs with zoom, pan, minimap, fit view, folder aggregation, and dependency-path highlighting
- Export Explorer with source locations, declaration kinds, default/named status, aliases, and per-file reference counts
- Reverse dependencies, re-export/barrel chains, dynamic imports, unused exports, and circular dependency groups
- Global file/symbol search plus filters for functions, classes, interfaces, enums, defaults, React components, controllers, services, and backend files
- Project statistics, file metrics, dark/light themes, virtualized export lists, and live indexing over server-sent events
Studio uses the TypeScript compiler's previous Program as its in-memory analysis cache. Graph rendering is capped until a file is selected, keeping navigation responsive for large projects while preserving the complete searchable index.
Generated files
| File | Command | Description |
|---|---|---|
| src/gen-import.ts | (default) | Source barrel re-exporting all source exports (TS projects) |
| src/gen-import.js | (default) | JS runtime barrel (JS projects) |
| src/gen-import.d.ts | (default) | Type declaration companion (JS projects only) |
| src/gen-app-config.ts | --app-config | Aggregator that re-exports from gen-import (TS projects) |
| src/gen-app-config.js | --app-config | JS runtime companion for the aggregator |
| src/gen-app-config.d.ts | --app-config | Type declaration companion for aggregator (JS projects only) |
| docs/export-map.json | --map | JSON export/import map, always written alongside the requested --map-format output |
CLI reference
Usage:
npx gen-import [options]
npx gen-import studio [options]
Studio (interactive import/export explorer):
studio Start Studio, watch the project, and open the browser
--port <port> Preferred port (default: 3000; finds a free port if busy)
--host <host> Bind host (default: 127.0.0.1)
--no-open Do not open the default browser
Source barrel (gen-import.ts for TS projects, gen-import.js for JS projects):
-r, --root <dir> Project root (default: cwd)
-s, --src <dir> Source directory relative to root (default: src)
-o, --out <filename> Output filename inside src (default: auto-detected)
-m, --module-pattern <pat> Module file pattern deferred to end (default: .module.ts)
-g, --globals Register all exports on Node.js global (no per-file imports needed)
--safe-barrels Withhold exports that would put the barrel inside a dependency cycle
(types are kept — they are erased before runtime; dropped value
exports are reported with the direct-import line to use instead)
--strict[=<mode>] Exit 1 on blocking findings. mode: cycles | barrels | collisions | all
(default all). cycles=GI001, barrels=GI002/GI004, collisions=GI006
--strict-cycles Deprecated alias for --strict=cycles
--no-topo-sort Skip topological sort and use alphabetical order (legacy behaviour)
--lazy Force lazy re-exports to prevent circular-dep errors
(default for CJS; CJS-only, ignored with a warning on ESM)
--no-lazy Force static re-exports (default for ESM)
--skip <pattern> Skip files matching pattern (repeatable)
--pure-reexport <path> Mark a file as pure re-export to skip (repeatable)
-w, --watch Watch src and auto-regenerate barrels on every change
Shared:
--no-js Skip generating .js companion files
-h, --help Show this help
App-server config:
--app-config Generate an aggregator barrel re-exporting from gen-import
--app-config-out <filename> Config output filename (default: auto-detected)
--no-auto-update Skip auto-appending new source exports to gen-import
Export map (visualization):
--map Generate export map visualization
--map-format <fmt> Output format: console (default), json, mermaid
--map-out <file> Write map to file instead of stdout
--no-imports Skip import relationship analysis (exports only)Examples
# Custom output filename
npx gen-import --out barrel.ts
# Skip additional paths
npx gen-import --skip src/types/ --skip src/app.ts
# Mark a file already re-exported by another barrel
npx gen-import --pure-reexport src/config/index.ts
# Fail CI only on runtime-breaking cycles (skip warnings on collisions/ordered barrels)
npx gen-import --strict=cycles
# Fail CI on any blocking finding (cycles, unsafe barrels, name collisions)
npx gen-import --strict
# Withhold cycle-causing exports instead of failing, and report the direct imports to use
npx gen-import --safe-barrels
# App-config without auto-updating gen-import.ts
npx gen-import --app-config --no-auto-update
# Generate a mermaid export map and write it to a file
npx gen-import --map --map-format mermaid --map-out docs/exports.mdConfig file
Place gen-import.config.js (or gen-import.config.cjs for ESM projects) in your project root. CLI flags always override config values.
// gen-import.config.js
module.exports = {
srcDir: 'src',
outFileName: 'gen-import.ts',
skipPatterns: [
'src/types/',
'src/app.ts',
'src/app.module.ts',
],
pureReexports: [
'src/config/index.ts',
],
}| Field | Type | Description |
|---|---|---|
| srcDir | string | Source directory relative to root |
| outFileName | string | Barrel output filename |
| moduleFilePattern | string \| string[] | Module file patterns deferred to end of barrel |
| skipPatterns | string[] | Substrings — any file path containing one is skipped |
| pureReexports | string[] | Paths relative to rootDir to skip (they re-export externally) |
| generateJs | boolean | Force or suppress .js companion generation |
| globals | boolean | Register all value exports on Node.js global |
| lazy | boolean | Force lazy (getter-based) or static re-exports |
| safeBarrels | boolean | Withhold exports that would put the barrel in a cycle |
| strict | 'off' \| 'cycles' \| 'barrels' \| 'collisions' \| 'all' | Exit 1 on the matching class of blocking finding |
| noTopoSort | boolean | Skip topological sort and use alphabetical order |
| watch | boolean | Watch srcDir and regenerate on every change |
Any field from the genImport options table may be set here — the whole file is spread into the genImport call, with CLI flags applied on top. Only srcDir, skipPatterns, pureReexports, moduleFilePattern, and generateJs are also forwarded to --app-config's source scan.
Built-in skip patterns (always active): __tests__, .test., .spec.
Circular dependency & barrel safety analysis
gen-import builds a full module dependency graph (static/dynamic imports, require(), export *, class heritage, decorators, static initializers) using the TypeScript compiler, then runs Tarjan's SCC algorithm to find cycles and classify the barrel's safety.
Barrel safety (shown in the summary box's Barrel row):
| Safety | Meaning |
|---|---|
| safe | The barrel isn't part of any dependency cycle |
| type-safe | Only a type-only cycle — erased before runtime, harmless today |
| ordered ⚠ | Barrel is inside a cycle, but every read is deferred — initialisation still completes, though it's fragile |
| unsafe ✖ | Barrel is inside a cycle with an init-time read — this crashes at runtime |
Diagnostic codes:
| Code | Severity | Meaning |
|---|---|---|
| GI001 | error | Circular dependency with an init-time read |
| GI002 | error | Barrel is inside a cycle with an init-time read |
| GI003 | warn | Circular dependency, but all reads are deferred |
| GI004 | warn | Barrel is inside a cycle, but all reads are deferred |
| GI005 | info | Type-only cycle — harmless unless verbatimModuleSyntax is enabled or an import type is dropped |
| GI006 | warn | Export name collision — only the first occurrence is re-exported |
| GI007 | info | Direct import recommended (used for --safe-barrels withheld exports and repaired barrels) |
| GI008 | info | Dynamic import recommended |
| GI009 | info | NestJS forwardRef recommended for a decorator-time read in a cycle |
Each diagnostic names the exact file and line that breaks the cycle and suggests a fix (import directly, defer the read, or wrap it in forwardRef).
Two ways to act on this:
# Fail CI when a blocking finding is present
npx gen-import --strict # any of GI001, GI002, GI004, GI006
npx gen-import --strict=cycles # GI001 only
npx gen-import --strict=barrels # GI002, GI004
npx gen-import --strict=collisions # GI006
# Instead of failing, withhold the exports that cause the cycle and print
# the direct-import line to use for each one (types are kept, values dropped)
npx gen-import --safe-barrelsLazy re-exports
For CommonJS projects, gen-import defaults to lazy re-exports: value exports are installed on module.exports as getters that require() the source file on first access, instead of being imported eagerly at the top of the barrel. This means importing the barrel from one of its own source files no longer trips a circular-require error — the read is deferred until the getter actually runs.
- Default: on for CJS projects, off for ESM (
"type": "module") --lazyforces it on; ignored with a warning for ESM TypeScript projects (falls back to static re-exports)--no-lazyforces static re-exports even on CJS
Type-only exports are unaffected — export type { ... } is always static since types are erased before runtime.
Module file deferral
NestJS-style module files reference services and repositories that haven't been exported yet. gen-import automatically defers matching files to the end of the barrel to prevent circular-require errors at runtime.
Default deferred patterns: .module.ts · .routes.ts · .router.ts · .route.ts
Override with --module-pattern (repeatable) or moduleFilePattern in the config file.
Globals mode
With --globals, all value exports are registered on Node.js global when the barrel is imported once at your app entry point — no per-file imports needed anywhere else in the codebase.
// src/main.ts — import once at the very top
import './gen-import'
// Any other file — no import statement needed
const svc = new UserService()Generate with:
npx gen-import --globalsTypeScript's declare global block is emitted so you get full IDE type-checking on all globals.
Watch mode
--watch (or -w) watches srcDir recursively and re-runs the requested commands (barrel, app-config, export map) on every change, debounced by 150ms. It ignores changes to the generated barrel files themselves to avoid a regeneration loop.
npx gen-import --watch
npx gen-import --app-config --watchStop with Ctrl+C.
Export map
--map analyses every source file's exports and import relationships and prints (or writes) a report. A docs/export-map.json copy is always written in addition to the requested format.
# Console report (default)
npx gen-import --map
# JSON, written to stdout or a file
npx gen-import --map --map-format json --map-out docs/exports.json
# Mermaid flowchart, ready to paste into a markdown file
npx gen-import --map --map-format mermaid --map-out docs/exports.md
# Skip import-relationship analysis, exports only (faster on large trees)
npx gen-import --map --no-importsConsole output:
📦 Export Map — 6 files, 18 exports, 11 import edges
user/user.service.ts (1 exports, imported by 2)
├─ values: UserService
└─ imported by:
← user/user.router.ts
← gen-import.tsProgrammatic API
import { genImport, genAppConfig, genPackage, genExportMap, watchSrc, analyzeStudioProject, startStudio } from 'gen-import'genImport
genImport({
rootDir: process.cwd(), // default
srcDir: 'src', // default
outFileName: 'gen-import.ts',
moduleFilePattern: ['.module.ts', '.routes.ts'],
skipPatterns: ['src/types/'],
pureReexports: ['src/config/index.ts'],
generateJs: false, // default: false for TS, true for JS
globals: false, // default
lazy: undefined, // default: true for CJS, false for ESM
safeBarrels: false, // default
strict: 'off', // default; or 'cycles' | 'barrels' | 'collisions' | 'all'
strictCycles: false, // deprecated, equivalent to strict: 'cycles'
noTopoSort: false, // default
watch: false, // default
})| Option | Type | Default | Description |
|---|---|---|---|
| rootDir | string | process.cwd() | Project root (must contain tsconfig.json for TS detection) |
| srcDir | string | 'src' | Source directory relative to rootDir |
| outFileName | string | auto | Output filename inside srcDir |
| skipPatterns | string[] | [] | Extra path substrings to skip (merged with built-ins) |
| pureReexports | string[] | [] | Files already re-exported elsewhere (relative to rootDir) |
| moduleFilePattern | string \| string[] | ['.module.ts', '.routes.ts', '.router.ts', '.route.ts'] | Patterns for files deferred to end of barrel |
| generateJs | boolean | false | For TS projects: also emit a .js companion |
| globals | boolean | false | Register all value exports on Node.js global |
| lazy | boolean | true for CJS, false for ESM | Emit getter-based re-exports to avoid circular-require crashes |
| safeBarrels | boolean | false | Withhold exports that would put the barrel in a cycle (keeps types, drops/demotes values) |
| strict | StrictMode | 'off' | Exit with code 1 when a blocking diagnostic of the given class is found |
| strictCycles | boolean | false | Deprecated — equivalent to strict: 'cycles' |
| noTopoSort | boolean | false | Skip topological sort and use alphabetical order |
| watch | boolean | false | Watch srcDir and regenerate on every change |
genAppConfig
genAppConfig({
rootDir: process.cwd(),
outFileName: 'gen-app-config.ts',
genImportFile: 'gen-import.ts',
autoUpdate: true,
generateJs: false,
})| Option | Type | Default | Description |
|---|---|---|---|
| rootDir | string | process.cwd() | Project root |
| srcDir | string | 'src' | Source / output directory |
| outFileName | string | auto | Config output filename |
| genImportFile | string | auto | Source barrel to re-export |
| autoUpdate | boolean | true | Append newly found source exports to gen-import.ts |
| skipPatterns | string[] | [] | Passed through to source scanning during auto-update |
| pureReexports | string[] | [] | Passed through to source scanning during auto-update |
| moduleFilePattern | string \| string[] | (defaults) | Passed through to source scanning during auto-update |
| generateJs | boolean | false for TS, true for JS | Also emit a .js companion |
genPackage
Reads dependencies (and optionally devDependencies) from package.json and generates a package barrel. Available via programmatic API only — not exposed in the CLI.
genPackage({
rootDir: process.cwd(),
// includeDev: true, — also include devDependencies
// include: ['lodash'], — allowlist specific packages
// exclude: ['express'], — blocklist (use for packages with export =)
})CJS note: packages that use
export =(e.g.express,sequelize) are incompatible withexport * from. Exclude them from the package barrel and import them directly in source files.
genExportMap
import { genExportMap } from 'gen-import'
import type { ExportMapResult } from 'gen-import'
const result: ExportMapResult = genExportMap({
rootDir: process.cwd(),
srcDir: 'src',
format: 'json', // 'console' (default) | 'json' | 'mermaid'
outFile: 'docs/exports.json',
includeImports: true, // default
})Always writes docs/export-map.json in addition to printing/writing the requested format.
watchSrc
import { watchSrc } from 'gen-import'
const stop = watchSrc({
srcDir: 'src',
ignore: ['gen-import', 'gen-app-config', 'gen-package'],
debounceMs: 150, // default
onChange: () => genImport({ srcDir: 'src' }),
})
// stop() to close the watcherStudio API
import { analyzeStudioProject, startStudio } from 'gen-import'
const snapshot = analyzeStudioProject(process.cwd())
const studio = await startStudio({
rootDir: process.cwd(),
port: 3000,
host: '127.0.0.1',
open: false,
})
await studio.close()Graph, SCC, and diagnostics utilities
import {
buildDepGraph, detectCycles, topoSort, createTsProgram, findNameCollisions,
buildModuleGraph, withBarrelExports, tarjanScc, topoOrder, cyclicSccs,
shortestCycle, cycleEdges, condensation, analyzeBarrel, analyzeBarrelGraph,
selectSafeExports, collectCycleDiagnostics, collectBarrelDiagnostics,
formatDiagnostics, countBySeverity,
} from 'gen-import'
import type { DepGraph, CycleReport, ModuleGraph, Scc, SccResult, BarrelAnalysis, Diagnostic } from 'gen-import'
const program = createTsProgram(files, rootDir)
const graph = buildDepGraph(files, program) // Map<string, Set<string>>
const cycles = detectCycles(graph) // CycleReport[]
const sorted = topoSort(files, graph) // string[]
// Richer module-level analysis (used internally by genImport)
const moduleGraph = buildModuleGraph(files, { rootDir, program, barrelPaths: [] })
const scc = tarjanScc(moduleGraph)
const analysis: BarrelAnalysis = analyzeBarrel(moduleGraph, barrelId, exportedFiles, { lazy: true })Example output
src/gen-import.ts (standard mode)
/**
* gen-import.ts — AUTO-GENERATED, do not edit manually.
* Regenerate: npx gen-import
*/
export { PORT, NODE_ENV, JWT_SECRET } from './config/env';
export type { UserDto, CreateUserDto, UpdateUserDto } from './user/user.dto';
export { authMiddleware } from './middleware/auth.middleware';
export { UserRepository } from './user/user.repository';
export { UserService } from './user/user.service';
export { userRouter } from './user/user.router';src/gen-import.ts (lazy mode — default for CJS)
// @ts-nocheck — auto-generated barrel with lazy CJS re-exports
/**
* gen-import.ts — AUTO-GENERATED, do not edit manually.
* Regenerate: npx gen-import
*
* Value exports use lazy getters to prevent circular-dependency
* errors when source files import from this barrel (CJS).
*/
export type { UserDto, CreateUserDto } from './user/user.dto';
export declare const UserService: typeof import('./user/user.service').UserService;
Object.defineProperty(module.exports, 'UserService', { get() { return require('./user/user.service').UserService }, enumerable: true, configurable: true });src/gen-import.ts (globals mode)
/**
* gen-import.ts — AUTO-GENERATED, do not edit manually.
* Regenerate: npx gen-import --globals
*
* Import once in your entry point: import './gen-import'
* After that, all exports are available as globals — no per-file imports needed.
*/
export type { UserDto, CreateUserDto } from './user/user.dto';
import { UserService as _UserService } from './user/user.service';
import { UserRepository as _UserRepository } from './user/user.repository';
export { _UserService as UserService, _UserRepository as UserRepository };
Object.assign(global as any, { UserService: _UserService, UserRepository: _UserRepository });
declare global {
var UserService: typeof _UserService
var UserRepository: typeof _UserRepository
}src/gen-app-config.ts
/**
* gen-app-config.ts — AUTO-GENERATED, do not edit manually.
* Regenerate: npx gen-import --app-config
* Imports only from barrel files — no per-file imports.
*/
export * from './gen-import';See examples/express-app/ for a complete working Express project.
Requirements
- Node.js >= 16
- A
tsconfig.jsonin the project root triggers TypeScript mode; otherwise JavaScript mode is used
Author
License
MIT
• Projects •
