name: monorepo-overview description: GROWI monorepo structure, workspace organization, and architectural principles. Auto-invoked for all GROWI development work.
GROWI is a team collaboration wiki platform built as a monorepo using pnpm workspace + Turborepo.
growi/
├── apps/ # Applications
│ ├── app/ # Main GROWI application (Next.js + Express + MongoDB)
│ ├── pdf-converter/ # PDF conversion microservice (Ts.ED + Puppeteer)
│ └── slackbot-proxy/ # Slack integration proxy (Ts.ED + TypeORM + MySQL)
├── packages/ # Shared libraries
│ ├── core/ # Core utilities and shared logic
│ ├── core-styles/ # Common styles (SCSS)
│ ├── editor/ # Markdown editor components
│ ├── ui/ # UI component library
│ ├── pluginkit/ # Plugin framework
│ ├── slack/ # Slack integration utilities
│ ├── presentation/ # Presentation mode
│ ├── pdf-converter-client/ # PDF converter client library
│ └── remark-*/ # Markdown plugins (remark-lsx, remark-drawio, etc.)
└── Configuration files
├── pnpm-workspace.yaml
├── turbo.json
├── package.json
└── .changeset/
All packages are managed via pnpm workspace. Package references use the workspace: protocol:
{
"dependencies": {
"@growi/core": "workspace:^",
"@growi/ui": "workspace:^"
}
}
Turborepo handles task orchestration with caching and parallelization:
# Run tasks across all workspaces
turbo run dev
turbo run test
turbo run lint
turbo run build
# Filter to specific package
turbo run test --filter @growi/app
turbo run lint --filter @growi/core
Build dependencies in this monorepo are not declared with dependsOn: ["^build"] (the automatic workspace-dependency mode). Instead, they are declared explicitly — either in the root turbo.json for legacy entries, or in per-package turbo.json files for newer packages.
When to update: whenever a package gains a new workspace dependency on another buildable package (one that produces a dist/), declare the build-order dependency explicitly. Without it, Turborepo may build in the wrong order, causing missing dist/ files or type errors.
Pattern — per-package turbo.json (preferred for new dependencies):
// packages/my-package/turbo.json
{
"extends": ["//"],
"tasks": {
"build": { "dependsOn": ["@growi/some-dep#build"] },
"dev": { "dependsOn": ["@growi/some-dep#dev"] }
}
}
"extends": ["//"] inherits all root task definitions; only add the extra dependsOnturbo.json clean — package-level overrides live with the package that owns the dependencyExisting examples:
packages/slack/turbo.json — build/dev depend on @growi/loggerpackages/remark-attachment-refs/turbo.json — all tasks depend on @growi/core, @growi/logger, @growi/remark-growi-directive, @growi/uiturbo.json — @growi/ui#build depends on @growi/core#build (pre-dates the per-package pattern)All packages should prefer feature-based organization:
{package}/src/
├── features/ # Feature modules
│ ├── {feature-name}/
│ │ ├── index.ts # Main export
│ │ ├── interfaces/ # TypeScript types
│ │ ├── server/ # Server-side logic (if applicable)
│ │ ├── client/ # Client-side logic (if applicable)
│ │ └── utils/ # Shared utilities
Benefits:
For full-stack packages (like apps/app), separate server and client logic:
This enables better code splitting and prevents server-only code from being bundled into client.
Common code should be extracted to packages/:
@growi/core is the foundational shared package depended on by all other packages (10 consumers). Its responsibilities:
IPage, IUser, IRevision, Ref<T>, HasObjectId, etc.)serializeUserSecurely())RegExp.escape() via declare global in index.ts)Key patterns:
@growi/core — Not duplicated per-package. declare global in index.ts propagates to all consumers through the module graph.@growi/core/dist/utils/page-path-utils instead of barrel imports from root.bson-objectid; ~70% types. Safe to import from both server and client contexts.interfaces/server/.preserveModules: true and vite-plugin-dts (copyDtsFiles: true).GROWI uses Changesets for version management and release notes:
# Add a changeset (after making changes)
npx changeset
# Version bump (generates CHANGELOGs and updates versions)
pnpm run version-subpackages
# Publish packages to npm (for @growi/core, @growi/pluginkit)
pnpm run release-subpackages
npx changeset and describe the change.changeset/*.md filepnpm run version-subpackagesCHANGELOG.md and package.json versionsapps/app): Manual versioning with RC prereleases
pnpm run version:patch, pnpm run version:prereleasepackages/core, packages/pluginkit): Changeset-managedapps/pdf-converter, apps/slackbot-proxy): Independent versioning| Package | Description | Tech Stack |
|---|---|---|
| @growi/app | Main wiki application | Next.js (Pages Router), Express, MongoDB, Jotai, SWR |
| @growi/pdf-converter | PDF export service | Ts.ED, Puppeteer |
| @growi/slackbot-proxy | Slack bot proxy | Ts.ED, TypeORM, MySQL |
| Package | Description | Published to npm |
|---|---|---|
| @growi/core | Core utilities | ✅ |
| @growi/pluginkit | Plugin framework | ✅ |
| @growi/ui | UI components | ❌ (internal) |
| @growi/editor | Markdown editor | ❌ (internal) |
| @growi/core-styles | Common styles | ❌ (internal) |
# Install dependencies for all packages
pnpm install
# Bootstrap (install + build dependencies)
turbo run bootstrap
# Start all dev servers (apps/app + dependencies)
turbo run dev
# Run a specific test file (from package directory)
pnpm vitest run yjs.integ
# Run ALL tests / lint for a package
turbo run test --filter @growi/app
turbo run lint --filter @growi/core
When modifying shared libraries (packages/*), ensure dependent apps reflect changes:
packages/coreapps/app to verify