Configure a monorepo with workspaces
A workspace is a collection of folders, each with its own ende.json, managed
from a single root. Members can import each other by name, share dependencies,
and run tools like ende test and ende fmt across the whole repository at
once.
Create the workspace
The root ende.json lists the member directories:
{
"workspace": ["./utils", "./app"]
}Each member gets its own ende.json with a name, a version, and an
exports field pointing at its entry point:
{
"exports": "./mod.ts",
"name": "@acme/utils",
"version": "0.1.0"
}export function greet(name: string): string {
return `Hello, ${name}!`;
}{
"exports": "./main.ts",
"name": "@acme/app",
"version": "0.1.0"
}Other members import @acme/utils by its bare name: no relative paths and no
import map entry needed. Ende finds the member through the root workspace list
and resolves the import through its exports:
import { greet } from "@acme/utils";
console.log(greet("workspace"));$ ende run app/main.ts
Hello, workspace!
Matching members with globs
Listing every member gets tedious in larger repositories. Each /* segment
matches one folder depth: packages/* matches packages/foo but not
packages/foo/subpackage.
{
"workspace": ["packages/*", "examples/*/*"]
}Options that must live at the root
Some options control resolution for the whole workspace and are only read from
the root ende.json: nodeModulesDir, vendor, minimumDependencyAge,
links, lock, and allowScripts. Setting them in a member has no effect and
Ende will warn about it.
Note
name, version, and exports go the other way: they belong in members, not
in the root.
Sharing dependencies from the root
Members inherit the root imports map, so common dependencies can be declared
once:
{
"imports": {
"@ende/assert": "jsr:@ende/assert@^2026.10.15"
},
"workspace": ["./utils", "./app"]
}A member can declare its own imports too; within that member’s folder, its
entries override the root’s.