How External Libraries Are Managed in Kaneo: A Complete Guide to PNPM Workspaces and Monorepo Dependency Control
Kaneo uses a PNPM monorepo with workspace references to manage external libraries across three tiers—root tooling, app-specific runtime dependencies, and internal shared packages linked via workspace:* specifiers.
This guide breaks down exactly how the usekaneo/kaneo repository structures its dependencies, from global dev tooling to per-app library installation and automated version updates.
PNPM Workspace Architecture
Kaneo's dependency management rests on a PNPM workspace defined in pnpm-workspace.yaml. This configuration partitions the repository into three distinct layers:
| Layer | Location | Purpose |
|---|---|---|
| Root | package.json |
Shared dev-dependencies, scripts, and workspace definition |
| Apps | apps/api/package.json, apps/web/package.json |
Runtime dependencies for deployable services |
| Packages | packages/*/package.json |
Reusable internal libraries consumed by apps |
The workspace file at pnpm-workspace.yaml declares which directories belong to the monorepo:
apps/*— contains the API server and web clientpackages/*— houses shared libraries like@kaneo/email,@kaneo/libs, and@kaneo/permissions
Root Package.json: Global Tooling Control
The root package.json centralizes development dependencies that every workspace needs. Rather than repeating Biome, Turborepo, TypeScript, or Husky across multiple files, they're declared once at the top level.
Key characteristics of the root configuration:
- Package manager lock: PNPM 10.32.1 enforced via
"packageManager": "pnpm@10.32.1" - Single runtime dependency:
dotenv-monofor environment loading - Shared scripts: Build orchestration and quality checks through Turborepo pipelines
This design avoids version drift in tooling while keeping per-package package.json files focused on production requirements.
Per-App Dependencies: API and Web
Each application maintains its own dependency manifest scoped to its runtime needs.
API Application (apps/api/package.json)
{
"dependencies": {
"@hono/node-server": "^2.1.0",
"hono": "^4.13.0",
"drizzle-orm": "^0.45.2",
"valibot": "^1.4.2"
}
}
The API layer relies on Hono for HTTP handling, Drizzle ORM for database operations, and Valibot for schema validation.
Web Application (apps/web/package.json)
{
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@tanstack/react-query": "^5.0.0",
"tailwindcss": "^4.0.0"
}
}
The web client uses React 19, TanStack Query for server state, and Tailwind CSS v4 for styling.
Internal Package Linking with Workspace References
Kaneo avoids publishing internal utilities to npm by using workspace references. When the API needs the email package, it declares:
{
"dependencies": {
"@kaneo/email": "workspace:*"
}
}
The workspace:* specifier instructs PNPM to create a symlink to the local package at packages/email/ rather than fetching from a registry. This enables:
- Immediate reflection of changes across consuming apps
- No version bumping required during development
- Atomic releases when packages do need publishing
Internal libraries follow the same pattern—@kaneo/libs, @kaneo/permissions, and @kaneo/mcp all connect via workspace links.
Installing External Libraries: Scoped Commands
Adding external libraries requires PNPN's filter syntax to target the correct workspace.
Add to a specific app:
pnpm add axios -F @kaneo/api
This updates apps/api/package.json and pnpm-lock.yaml in one operation.
Add to root (all workspaces):
pnpm add dayjs -w
Root additions become available everywhere through PNPM's hoisting behavior.
Example usage after installation:
// apps/api/src/health/controllers/get-status.ts
import axios from "axios";
export async function getStatus() {
const { data } = await axios.get("https://example.com/health");
return { ok: true, upstream: data };
}
// apps/web/src/hooks/use-current-time.ts
import dayjs from "dayjs";
export function useCurrentTime() {
return dayjs().format("YYYY-MM-DD HH:mm:ss");
}
Automated Dependency Maintenance
Kaneo uses Dependabot (configured in .github/dependabot.yml) to propose version bumps for each package.json in the monorepo. The bot opens individual pull requests per dependency, allowing targeted review and rollback.
For packages that do publish to npm—like @kaneo/mcp—the repository includes a dedicated workflow at .github/workflows/publish-mcp.yml that handles versioning and registry publication through GitHub Actions.
Lockfile Consistency and Reproducibility
All workspaces share a single pnpm-lock.yaml at the repository root. After any dependency change, running pnpm install recomputes the lockfile to ensure:
- Identical resolution across all developer machines
- Deterministic builds in CI/CD pipelines
- Deduplicated dependency trees through PNPM's content-addressable store
The lockfile captures exact versions, tarball URLs, and peer dependency resolutions for the entire monorepo.
Summary
- PNPM workspaces in
pnpm-workspace.yamldefine the monorepo boundary and enable cross-package linking - Root
package.jsonconsolidates dev-tooling to prevent version fragmentation workspace:*specifiers connect internal packages without registry publication- Scoped
pnpm add -Fcommands install external libraries to specific apps - Dependabot and CI workflows automate version updates and package publishing
- Single lockfile guarantees reproducible builds across all environments
Frequently Asked Questions
What package manager does Kaneo use and why?
Kaneo uses PNPM 10.32.1 exclusively. According to the root package.json, the "packageManager" field enforces this version to ensure consistent behavior across developer machines and CI environments. PNPM's workspace features and disk-efficient content-addressable store make it well-suited for monorepos with multiple interdependent packages.
How do I add a library that's shared across all apps in Kaneo?
Use the -w (workspace) flag: pnpm add <package> -w. This installs to the root package.json and makes the library available through PNPM's hoisting mechanism. However, for runtime dependencies, prefer installing directly in each app to keep dependency graphs explicit and bundler-friendly.
What happens when I change code in packages/email?
Changes reflect immediately in consuming apps like apps/api because @kaneo/email is linked via workspace:*. No pnpm install or version bump is required during development. The symlink points directly to the source, so builds and dev servers pick up modifications in real time.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →