A minimal, fast monorepo manager for Node.js/TypeScript projects with advanced dependency resolution.
- Central node_modules - Single installation point via pnpm
- Cross-version resolution - Different workspaces can use different package versions
- Alias imports - Use
@orders/utils/helperstyle imports - Parallel execution - Run tasks concurrently with dependency awareness
- Dependency graph - Automatic topological ordering and cycle detection
- Simple configuration - One
spinx.config.jsfile - Clean CLI - Easy-to-use commands
npm install -g spinx
# or
pnpm add -g spinx/** @type {import('spinx/types').spinxConfig} */
module.exports = {
manager: "pnpm",
concurrency: 4,
workspace: [
{
path: "./packages/utils",
alias: "@utils",
command: {
build: "npm run build",
customcommand: "echo 'k xa bro ?'",
},
},
{
path: "./services/orders",
alias: "@orders",
dependsOn: ["@utils"],
command: {
build: "npm run build",
start: "npm run dev",
live: "npm run start:prod",
customcommand: "echo 'Hi from Nirikshan'",
},
},
{
path: "./services/cart",
alias: "@cart",
dependsOn: ["@orders", "@utils"],
command: {
build: "npm run build",
start: "npm run dev",
live: "npm run start:prod",
customcommand: "echo 'Hey This is Nirikshan Bhusal'",
},
},
],
defaults: {
build: "npm run build",
start: "npm run dev",
},
};packages:
- "packages/*"
- "services/*"pnpm installspinx buildBuild all workspaces in dependency order:
spinx buildBuild only changed workspaces since a git ref:
spinx build --since=origin/mainBuild specific workspaces:
spinx build --filter=@orders,@cartStart a workspace in development mode:
spinx start @ordersStart with dependencies:
spinx start @orders --with-depsRun all workspaces in production mode:
spinx liveAdd workspace-to-workspace dependency:
spinx add @cart @ordersAdd npm package:
spinx add @orders express@5.0.0 --exact
spinx add @cart lodash --devspinx remove @cart @orders
spinx rm @orders expressSee package version conflicts:
spinx conflictsSee how a package is resolved:
spinx explain @orders expressDisplay dependency graph:
spinx graphspinx's killer feature is cross-version resolution. Different workspaces can depend on different versions of the same package:
// @orders uses express 5.x
{
"dependencies": {
"express": "^5.0.0"
}
}
// @cart uses express 4.x
{
"dependencies": {
"express": "^4.18.0"
}
}Both will work correctly at runtime! spinx:
- Analyzes all workspace dependencies
- Generates a resolution map (
.spinx/resolutions.json) - Creates a custom Node.js resolver hook (
.spinx/resolver.js) - Automatically loads the hook when running your code
The resolver intercepts require() and import calls to load the correct version for each workspace.
Workspace Request Flow:
┌─────────────┐
│ @orders │ import express → express 5.0.0
└─────────────┘
┌─────────────┐
│ @cart │ import express → express 4.18.3
└─────────────┘
Both packages installed in node_modules, correct version loaded per workspace!
$ spinx conflicts
⚠️ Found 1 package(s) with version conflicts:
📦 express
5.0.0:
- @orders
4.18.3:
- @cart
💡 These conflicts will be automatically resolved at runtime.interface spinxConfig {
manager: "pnpm";
concurrency?: number; // Max parallel tasks (default: # of workspaces)
workspace: Workspace[];
defaults?: Partial<Commands>; // Fallback commands
watch?: {
include?: string[];
ignore?: string[];
};
}
interface Workspace {
path: string; // Path to workspace
alias?: string; // Import alias (e.g., "@orders")
dependsOn?: string[]; // Workspace dependencies
command?: Commands; // Override commands
}
interface Commands {
build?: string;
start?: string;
live?: string;
}Perfect for:
- Multiple services with shared packages
- Services that need different versions of dependencies
- Gradual migrations (e.g., Express 4 → 5)
Great for:
- Multiple packages that depend on each other
- Testing packages together
- Version management across packages
my-monorepo/
├── spinx.config.js
├── pnpm-workspace.yaml
├── package.json
├── packages/
│ ├── utils/
│ │ ├── package.json
│ │ └── src/
│ └── shared/
│ ├── package.json
│ └── src/
└── services/
├── orders/
│ ├── package.json
│ └── src/
└── cart/
├── package.json
└── src/
-
Central node_modules with Version Management
- Single
node_modulesvia pnpm - Advanced package resolution for conflicting versions
- Automatic resolution map generation
- Runtime resolver hook that intercepts
require()andimport - Each workspace gets the correct package version automatically
- Single
-
Alias Support
- Use
@orders/utils/helper.tsstyle imports - Clean, readable import statements
- Automatic path mapping
- Use
-
Parallel Execution
- Dependency-aware parallel builds
- Configurable concurrency
- Batched execution respecting dependency graph
- Smart topological ordering
-
Conflict Resolution
- Automatic detection of version conflicts
- Visual conflict display via
spinx conflicts - Detailed resolution explanation with
spinx explain - Works seamlessly at runtime
spinx/
├── src/
│ ├── cli.ts # Main CLI entry point
│ ├── config.ts # Configuration loader & validator
│ ├── graph.ts # Dependency graph (DAG, cycles, topo sort)
│ ├── resolution.ts # Version conflict resolution system ⭐
│ ├── tasks.ts # Parallel task execution
│ ├── add.ts # Dependency management
│ └── utils.ts # Helper functions
├── types.d.ts # TypeScript definitions
├── package.json
├── tsconfig.json
└── README.md # Main documentation
Generated at runtime:
.spinx/
├── resolutions.json # Package version mappings
└── resolver.js # Runtime resolver hook
You can also design your own structure
This is the killer feature. Here's how it works:
// spinx analyzes all workspace package.json files
@orders → express@5.0.0
@cart → express@4.18.3// .spinx/resolutions.json
{
"@orders": {
"express": {
"version": "5.0.0",
"resolvedPath": "/node_modules/.pnpm/express@5.0.0/..."
}
},
"@cart": {
"express": {
"version": "4.18.3",
"resolvedPath": "/node_modules/.pnpm/express@4.18.3/..."
}
}
}// .spinx/resolver.js intercepts require()
Module._resolveFilename = function (request, parent) {
// Determine which workspace is calling
const workspace = findWorkspace(parent.filename);
// Look up correct version for that workspace
const resolution = resolutions[workspace][request];
// Return correct path
return resolution.resolvedPath;
};// In @orders/src/index.ts
import express from "express"; // Gets express 5.0.0 ✅
// In @cart/src/index.ts
import express from "express"; // Gets express 4.18.3 ✅cd spinx
npm install
npm run build
# Create a test project
cd ..
mkdir test-monorepo && cd test-monorepo
../spinx/scripts/quickstart.sh
# Start using it!
pnpm install
npx spinx build
npx spinx start @orderscd spinx
npm install
npm run build
npm link
# Now use anywhere
cd /path/to/your/project
spinx init
spinx build# View version
spinx -v
# Build everything
spinx build
# Build only changed
spinx build --since=origin/main
# Start a service
spinx start @orders
spinx start @orders --with-deps
# Production mode
spinx live
# Manage dependencies
spinx add @cart @orders # Link workspaces
spinx add @orders express@5.0.0 -E # Add npm package
# Check conflicts
spinx conflicts
spinx explain @orders express
# View graph
spinx graph
# Run your custom command
spinx run <command key from spinup.config.js > workspace > command object >Your monorepo:
├── @new-api (wants Express 5.x - new async/await API)
└── @legacy-api (stuck on Express 4.x - old callbacks)
Traditional approach: Either migrate everything at once (risky!)
or split into separate repos (overhead!)
# 1. Set up both services with different Express versions
cd services/new-api
pnpm add express@5.0.0
cd ../legacy-api
pnpm add express@4.18.3
# 2. Check conflicts
spinx conflicts
# ⚠️ express: 5.0.0 (@new-api), 4.18.3 (@legacy-api)
# 3. Build and run - it just works!
spinx build
spinx start @new-api # Uses Express 5 ✅
spinx start @legacy-api # Uses Express 4 ✅
# 4. Gradually migrate at your own pace
# No rush, no breaking changes, both services work perfectly- Uses Kahn's algorithm for topological sort
- Detects cycles with full path display
- Calculates affected workspaces
- Generates parallel execution batches
- Scans all workspace dependencies
- Detects version conflicts automatically
- Generates resolution map
- Creates runtime resolver hook
- Fast path for non-conflicting packages
- Parallel execution with p-limit
- Respects dependency order
- Configurable concurrency
- Streams output with colored prefixes
- Fail-fast or continue on error
- Zod schema validation
- Helpful error messages
- Workspace validation
- Default command fallbacks
cd spinx
npm install
npm test
# Run specific test
npm test graph.test.ts| Feature | spinx | Nx | Turborepo | Lerna |
|---|---|---|---|---|
| Cross-version resolution | ✅ Unique! | ❌ | ❌ | ❌ |
| Config simplicity | ✅ Simple | ❌ Complex | ✅ Simple | |
| Learning curve | ✅ Low | ❌ High | ✅ Low | |
| Parallel builds | ✅ | ✅ | ✅ | ✅ |
| Dependency graph | ✅ | ✅ | ✅ | ✅ |
| TypeScript support | ✅ | ✅ | ✅ | ✅ |
Looking for future enhancements for following features :
- Watch mode with hot reload
- TUI dashboard (spinx view)
- Cache system for faster builds
- Remote cache support ( Contact Me )
- Better TypeScript project references
My core focus for this project was:
✅ Central dependency management
✅ Version conflict resolution
✅ Parallel execution
✅ Clean aliases
Contributions welcome! Please read our contributing guidelines.
MIT
GitHub: https://github.com/nirikshan/spinx
Built with: