OneApp Docs
Guides

Team Workspaces

Create isolated development environments for teams within the monorepo — each team gets dedicated apps, packages, and infrastructure, while sharing common code and maintaining clear boundaries.

Know your team structure?

Why team workspaces matter

Without team isolation in monorepos:

  • Code ownership chaos — Unclear who maintains what, conflicting changes to shared code
  • Dependency conflicts — Teams step on each other's toes, breaking builds across the monorepo
  • Deployment complexity — All teams deploy together, can't ship independently
  • Permission nightmares — No granular access control, everyone touches everything
  • Onboarding confusion — New developers don't know where their team's code lives
  • Merge conflicts — Multiple teams editing the same files causes constant conflicts

Team workspaces with proper isolation — Dedicated directory structure (teams/team-name/{apps,packages,infra}), team-scoped package naming (@team-name/*), clear dependency rules (can use @repo/*, not other teams), independent deployment, and workspace filtering by team — ensures teams work independently while sharing infrastructure.

Production-ready with 3 active team workspaces (AI, iStudio, Engineering), 5 personal workspaces for experimentation, shared access to 35+ @repo/* packages, independent CI/CD per team, and clear promotion path from team to shared packages.

Use cases

Use team workspaces to:

  • Isolate team development — Teams work on features without affecting others
  • Manage ownership — Clear responsibility for apps, packages, infrastructure
  • Ship independently — Deploy team apps without waiting for other teams
  • Experiment safely — Prototype new ideas without breaking shared code
  • Scale organizations — Add new teams without restructuring the entire monorepo
  • Control access — Granular permissions per team workspace in CI/CD

Quick Start

Essential steps

Set up a team workspace in 3 steps:

1. Create team directory structure

# Create team workspace
mkdir -p teams/myteam/{apps,packages,infra}

# Create first team app
mkdir -p teams/myteam/apps/myteam-dashboard/src/app

2. Configure team app with Next.js 16

// teams/myteam/apps/myteam-dashboard/package.json
{
  "name": "myteam-dashboard",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev --port 3100",
    "build": "next build",
    "start": "next start",
    "lint": "eslint src/",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/utils": "workspace:*",
    "next": "^16.0.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "@repo/config": "workspace:*",
    "typescript": "^5.0.0"
  }
}

3. Install and run team app

# Install dependencies
pnpm install

# Run team app
pnpm --filter=myteam-dashboard dev

That's it! Your team workspace is ready. Team app runs on port 3100, isolated from other teams.

Workspace structure

Complete team directory layout

Every team workspace follows this structure:

teams/myteam/
├── apps/                      # Team applications
│   ├── myteam-dashboard/      # Main team app
│   │   ├── src/
│   │   ├── package.json       # name: "myteam-dashboard"
│   │   ├── tsconfig.json
│   │   ├── eslint.config.mjs
│   │   └── next.config.ts
│   └── myteam-mobile/         # Team mobile app
│       ├── app/
│       ├── package.json       # name: "myteam-mobile"
│       └── app.json
├── packages/                  # Team-specific packages
│   ├── myteam-utils/          # Shared team utilities
│   │   ├── src/
│   │   ├── package.json       # name: "@myteam/utils"
│   │   └── tsconfig.json
│   └── myteam-components/     # Team UI components
│       ├── src/
│       └── package.json       # name: "@myteam/components"
└── infra/                     # Infrastructure code
    ├── terraform/             # Infrastructure as code
    ├── kubernetes/            # K8s manifests
    ├── scripts/               # Deployment scripts
    └── docker/                # Dockerfiles

Active team workspaces

AI Team (teams/ai/):

  • Apps: AI model training dashboards, experiment tracking
  • Packages: ML utilities, model wrappers
  • Infra: GPU cluster configs, training pipelines

iStudio Team (teams/istudio/):

  • Apps: Content creation tools, media management
  • Packages: Video processing, image optimization
  • Infra: Media storage, CDN configuration

Engineering Team (teams/engineering/):

  • Apps: Developer tools, monitoring dashboards
  • Packages: Build utilities, testing helpers
  • Infra: CI/CD configs, deployment automation

Personal workspaces

For individual experimentation:

personal/
├── andy/
│   ├── apps/experiment-app/
│   └── packages/prototype-lib/
├── torin/
│   ├── apps/demo-app/
│   └── packages/test-utils/
├── michael/
├── vinay/
└── jeff/

Use for:

  • Learning new technologies
  • Prototyping features before team implementation
  • Personal tools and scripts
  • Experimenting without breaking anything

Creating team apps

Next.js 16 app setup

Complete package.json:

{
  "name": "myteam-dashboard",
  "version": "0.0.0",
  "private": true,
  "description": "Team dashboard for monitoring and analytics",
  "scripts": {
    "dev": "next dev --port 3100",
    "build": "next build",
    "start": "next start --port 3100",
    "lint": "eslint src/",
    "typecheck": "tsc --noEmit",
    "test": "vitest run"
  },
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/utils": "workspace:*",
    "@repo/auth": "workspace:*",
    "@repo/db-prisma": "workspace:*",
    "next": "^16.0.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "@repo/config": "workspace:*",
    "@types/node": "^22.0.0",
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "typescript": "^5.0.0",
    "vitest": "^4.0.0"
  }
}

tsconfig.json:

{
  "extends": "@repo/config/typescript/nextjs.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "#/*": ["./src/*"]
    },
    "plugins": [
      {
        "name": "next"
      }
    ]
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules", ".next", "out"]
}

eslint.config.mjs:

import nextConfig from "@repo/config/eslint/next";

export default [...nextConfig];

next.config.ts:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  reactStrictMode: true,
  transpilePackages: ["@repo/ui", "@repo/utils"],
  experimental: {
    typedRoutes: true
  }
};

export default nextConfig;

Create app page:

// src/app/page.tsx
import { Button } from "@repo/ui";

export default function Page(): JSX.Element {
  return (
    <div className="p-8">
      <h1 className="text-3xl font-bold">My Team Dashboard</h1>
      <Button variant="primary">Get Started</Button>

  );
}

Run the app:

pnpm --filter=myteam-dashboard dev

That's it! Your team app is running on http://localhost:3100.

React Native app setup

For mobile team apps:

{
  "name": "myteam-mobile",
  "version": "0.0.0",
  "private": true,
  "main": "expo-router",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "lint": "eslint app/",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@repo/uni-ui": "workspace:*",
    "@repo/utils": "workspace:*",
    "expo": "^52.0.0",
    "expo-router": "^4.0.0",
    "react-native": "^0.76.0"
  },
  "devDependencies": {
    "@repo/config": "workspace:*",
    "typescript": "^5.0.0"
  }
}

tsconfig.json:

{
  "extends": "@repo/config/typescript/react-native.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "#/*": ["./app/*"]
    }
  }
}

eslint.config.mjs:

import expoConfig from "@repo/config/eslint/expo";

export default [...expoConfig];

That's it! Your mobile app uses shared @repo/uni-ui components compatible with web and native.

Creating team packages

Team utility package

For code shared within the team:

# Create package
mkdir -p teams/myteam/packages/myteam-utils/src

package.json:

{
  "name": "@myteam/utils",
  "version": "0.0.0",
  "private": true,
  "description": "Shared utilities for myteam",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  },
  "scripts": {
    "lint": "eslint src/",
    "typecheck": "tsc --noEmit",
    "test": "vitest run"
  },
  "dependencies": {
    "@repo/types": "workspace:*",
    "zod": "^4.0.0"
  },
  "devDependencies": {
    "@repo/config": "workspace:*",
    "typescript": "^5.0.0",
    "vitest": "^4.0.0"
  }
}

tsconfig.json:

{
  "extends": "@repo/config/typescript/node.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

Create utility:

// src/index.ts
import type { Brand } from "@repo/types";

export type TeamId = Brand<string, "TeamId">;

/**
 * Formats team metrics for display.
 *
 * @param metrics - Raw metrics data
 * @returns Formatted metrics object
 */
export function formatTeamMetrics(metrics: { totalUsers: number; activeUsers: number; revenue: number }): {
  totalUsers: string;
  activeUsers: string;
  revenue: string;
  conversionRate: string;
} {
  return {
    totalUsers: metrics.totalUsers.toLocaleString(),
    activeUsers: metrics.activeUsers.toLocaleString(),
    revenue: `$${(metrics.revenue / 100).toFixed(2)}`,
    conversionRate: `${((metrics.activeUsers / metrics.totalUsers) * 100).toFixed(1)}%`
  };
}

Use in team app:

# Add to team app
pnpm --filter=myteam-dashboard add @myteam/utils
// In team app
import { formatTeamMetrics } from "@myteam/utils";

const formatted = formatTeamMetrics({
  totalUsers: 1000,
  activeUsers: 750,
  revenue: 50000
});

That's it! Team package is ready to use across team apps.

Team component package

For team-specific UI components:

{
  "name": "@myteam/components",
  "version": "0.0.0",
  "private": true,
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts",
    "./TeamCard": "./src/TeamCard.tsx"
  },
  "dependencies": {
    "@repo/ui": "workspace:*",
    "clsx": "^2.1.0"
  },
  "devDependencies": {
    "@repo/config": "workspace:*",
    "@types/react": "^19.0.0",
    "typescript": "^5.0.0"
  },
  "peerDependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}

Create component:

// src/TeamCard.tsx
import { Button } from "@repo/ui";

export interface TeamCardProps {
  title: string;
  description: string;
  onAction?: () => void;
}

export function TeamCard({ title, description, onAction }: TeamCardProps): JSX.Element {
  return (
    <div className="rounded-lg border p-6 shadow-sm">
      <h3 className="text-xl font-semibold">{title}</h3>
      <p className="mt-2 text-gray-600">{description}</p>
      {onAction && (
        <Button variant="primary" onClick={onAction} className="mt-4">
          View Details
        </Button>
      )}

  );
}

Export from index:

// src/index.ts
export { TeamCard } from "./TeamCard";
export type { TeamCardProps } from "./TeamCard";

That's it! Team components are ready to use, building on shared @repo/ui.

Dependency management

Dependency rules

Teams CAN use:

  1. All shared packages (@repo/*) — Authentication, UI, database, utilities
  2. Own team packages (@myteam/*) — Team-specific utilities and components
  3. External npm packages — Any public npm package

Teams SHOULD AVOID:

  1. Other team packages (@otherteam/*) — Creates tight coupling between teams
  2. Direct imports from other teams' apps — Breaks isolation

Correct dependencies:

// ✅ Good - using shared packages
import { Button } from "@repo/ui";
import { formatDate } from "@repo/utils";
import { db } from "@repo/db-prisma";

// ✅ Good - using own team packages
import { formatTeamMetrics } from "@myteam/utils";
import { TeamCard } from "@myteam/components";

// ✅ Good - external npm packages
import { z } from "zod";
import { clsx } from "clsx";

// ❌ Avoid - cross-team dependency
import { something } from "@ai/ml-utils"; // Don't use other team packages

// ❌ Avoid - direct app imports
import { config } from "../../../istudio/apps/studio/config"; // Never do this

When to use other team packages

Only when:

  1. Teams explicitly coordinate and agree
  2. Package is stable and won't change frequently
  3. Clear ownership and maintenance plan

Better approach:

Promote the package to packages/ as @repo/* so all teams can use it.

Promoting packages to shared

When to promote:

  • Package is used by 2+ teams
  • Package provides broadly useful functionality
  • Package is stable and well-tested

How to promote:

# 1. Move package
mv teams/myteam/packages/useful-utils packages/useful-utils

# 2. Update package.json name
# "@myteam/useful-utils" → "@repo/useful-utils"

# 3. Update imports in all team apps
# Find all usages
grep -r "@myteam/useful-utils" teams/

# 4. Update to new name
# "@myteam/useful-utils" → "@repo/useful-utils"

# 5. Reinstall dependencies
pnpm install

# 6. Create changeset
pnpm changeset
# Summary: "Promote @myteam/useful-utils to @repo/useful-utils"

That's it! Package is now available to all teams.

Running team commands

Filter by team workspace

# Run dev for all team apps
pnpm --filter="./teams/myteam/apps/*" dev

# Build all team packages
pnpm --filter="./teams/myteam/packages/*" build

# Lint everything in team workspace
pnpm --filter="./teams/myteam/**" lint

# Type check all team code
pnpm --filter="./teams/myteam/**" typecheck

Filter specific team app

# Run specific app
pnpm --filter=myteam-dashboard dev

# Build specific app
pnpm --filter=myteam-dashboard build

# Test specific app
pnpm --filter=myteam-dashboard test

Add convenience scripts

In root package.json:

{
  "scripts": {
    "dev:myteam": "pnpm --filter=./teams/myteam/apps/* dev",
    "build:myteam": "pnpm --filter=./teams/myteam/** build",
    "lint:myteam": "pnpm --filter=./teams/myteam/** lint",
    "test:myteam": "pnpm --filter=./teams/myteam/** test",

    "dev:ai": "pnpm --filter=./teams/ai/apps/* dev",
    "build:ai": "pnpm --filter=./teams/ai/** build",

    "dev:istudio": "pnpm --filter=./teams/istudio/apps/* dev",
    "build:istudio": "pnpm --filter=./teams/istudio/** build"
  }
}

Run with:

# Development
pnpm dev:myteam
pnpm dev:ai
pnpm dev:istudio

# Build
pnpm build:myteam
pnpm build:ai

That's it! Easy commands for running team workspaces.

Infrastructure setup

Team infrastructure directory

Not managed by pnpm — purely for infrastructure code:

teams/myteam/infra/
├── terraform/
│   ├── main.tf                # Terraform configuration
│   ├── variables.tf
│   └── outputs.tf
├── kubernetes/
│   ├── deployment.yaml        # K8s manifests
│   ├── service.yaml
│   └── ingress.yaml
├── scripts/
│   ├── deploy.sh              # Deployment scripts
│   ├── rollback.sh
│   └── migrate.sh
└── docker/
    ├── Dockerfile             # Docker images
    └── docker-compose.yml

Terraform example

# teams/myteam/infra/terraform/main.tf
terraform {
  required_providers {
    vercel = {
      source = "vercel/vercel"
      version = "~> 1.0"
    }
  }
}

resource "vercel_project" "myteam_dashboard" {
  name      = "myteam-dashboard"
  framework = "nextjs"

  git_repository = {
    type = "github"
    repo = "your-org/monorepo"
  }

  environment = [
    {
      key    = "DATABASE_URL"
      value  = var.database_url
      target = ["production"]
    }
  ]

  root_directory = "teams/myteam/apps/myteam-dashboard"
}

Kubernetes example

# teams/myteam/infra/kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myteam-dashboard
  namespace: myteam
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myteam-dashboard
  template:
    metadata:
      labels:
        app: myteam-dashboard
    spec:
      containers:
        - name: dashboard
          image: ghcr.io/your-org/myteam-dashboard:latest
          ports:
            - containerPort: 3000
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: myteam-secrets
                  key: database-url

Deployment script example

#!/bin/bash
# teams/myteam/infra/scripts/deploy.sh

set -e

echo "Deploying myteam-dashboard..."

# Build app
pnpm --filter=myteam-dashboard build

# Deploy to Vercel
vercel --prod --cwd teams/myteam/apps/myteam-dashboard

echo "✅ Deployment complete!"

That's it! Team infrastructure is isolated and manageable.

Best practices

1. Keep team boundaries clear

Don't reach into other teams' code:

// ❌ Bad - reaching into another team's code
import { helper } from "../../../ai/packages/utils/src/helper";

// ✅ Good - use shared packages
import { helper } from "@repo/utils";

// ✅ Good - coordinate with other team
// If they promote their package to @repo/*

2. Use shared packages first

Before creating team package, check if shared package exists:

# Check existing packages
ls packages/

# If exists, use it
pnpm --filter=myteam-dashboard add @repo/existing-utils

# Only create team package if truly team-specific

3. Promote reusable code early

If you find yourself duplicating code:

# Instead of copying utils to multiple team apps
# Move to team package
mkdir -p teams/myteam/packages/shared-utils

# If multiple teams need it
# Promote to @repo/*
mv teams/myteam/packages/shared-utils packages/shared-utils

4. Document team conventions

Create team README:

# teams/myteam/README.md

## My Team Workspace

Team workspace for the MyTeam product.

### Apps

- **myteam-dashboard** - Main dashboard application
- **myteam-mobile** - Mobile app

### Packages

- **@myteam/utils** - Shared team utilities
- **@myteam/components** - Team-specific UI components

### Running locally

\`\`\`bash pnpm dev:myteam \`\`\`

### Deployment

See `infra/scripts/deploy.sh` for deployment process.

### Team conventions

- Use `@repo/ui` for standard components
- Create team components in `@myteam/components` only for team-specific UI
- All API routes should use AsyncResult pattern
- Follow shared coding conventions

5. Use consistent naming

Clear ownership in names:

✅ Good naming:
- teams/ai/apps/ai-training-dashboard
- teams/ai/packages/ai-ml-utils
- teams/istudio/apps/istudio-editor
- teams/istudio/packages/istudio-media

❌ Bad naming:
- teams/ai/apps/dashboard (too generic)
- teams/ai/packages/utils (conflicts with @repo/utils)
- teams/istudio/apps/app (meaningless)

6. Independent CI/CD per team

GitHub Actions workflow per team:

# .github/workflows/myteam-ci.yml
name: MyTeam CI

on:
  push:
    branches: [main, develop]
    paths:
      - "teams/myteam/**"
  pull_request:
    paths:
      - "teams/myteam/**"

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: "pnpm"

      - run: pnpm install
      - run: pnpm --filter=./teams/myteam/** lint
      - run: pnpm --filter=./teams/myteam/** typecheck
      - run: pnpm --filter=./teams/myteam/** build

That's it! Team CI/CD runs only when team code changes.

Troubleshooting

Team package not found

Error:

Cannot find module '@myteam/utils' or its corresponding type declarations

Solution:

# 1. Verify package exists
ls teams/myteam/packages/

# 2. Install dependencies
pnpm install

# 3. Add to team app if needed
pnpm --filter=myteam-dashboard add @myteam/utils

# 4. Verify workspace configuration
# Check pnpm-workspace.yaml includes:
# - 'teams/*/apps/*'
# - 'teams/*/packages/*'

Cross-team dependency error

Error:

Package @ai/ml-utils not found in workspace

Solution:

// Don't use other team packages directly
// Instead:

// 1. Option A: Ask them to promote to @repo/*
// 2. Option B: Duplicate the functionality in your team package
// 3. Option C: Move to shared package yourself

// For now, remove the import:
// import { something } from "@ai/ml-utils"; // ❌

// Use shared package instead:
import { something } from "@repo/utils"; // ✅

Port conflict between team apps

Error:

Port 3000 is already in use

Solution:

// Assign unique ports per team app
// teams/myteam/apps/myteam-dashboard/package.json
{
  "scripts": {
    "dev": "next dev --port 3100"  // Unique port
  }
}

// teams/ai/apps/ai-dashboard/package.json
{
  "scripts": {
    "dev": "next dev --port 3200"  // Different port
  }
}

Port allocation guide:

  • 3000-3099: Shared apps
  • 3100-3199: MyTeam apps
  • 3200-3299: AI team apps
  • 3300-3399: iStudio team apps
  • 3400-3499: Engineering team apps
  • 3500+: Personal workspace apps

Next steps

For Developers: Advanced team workspace patterns

Advanced workspace patterns

Shared team infrastructure

For infrastructure used across team apps:

teams/myteam/infra/shared/
├── database/
│   ├── schema.prisma          # Shared database schema
│   └── migrations/
├── api/
│   ├── openapi.yaml           # API specification
│   └── postman/
└── configs/
    ├── env.example            # Environment variables template
    └── secrets.template.yaml

Team-specific environment configuration

Create team environment template:

# teams/myteam/.env.example
# Database
DATABASE_URL="postgresql://user:pass@localhost:5432/myteam_db"

# Authentication
AUTH_SECRET="your-secret-here"
AUTH_URL="http://localhost:3100"

# External APIs
TEAM_API_KEY="your-api-key"
TEAM_API_URL="https://api.example.com"

# Feature flags
ENABLE_TEAM_FEATURE_X="true"

Load in team apps:

// teams/myteam/apps/myteam-dashboard/src/env.ts
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

export const env = createEnv({
  server: {
    DATABASE_URL: z.string().url(),
    AUTH_SECRET: z.string().min(1),
    TEAM_API_KEY: z.string().min(1)
  },
  client: {
    NEXT_PUBLIC_TEAM_NAME: z.string().min(1)
  },
  experimental__runtimeEnv: {
    NEXT_PUBLIC_TEAM_NAME: process.env.NEXT_PUBLIC_TEAM_NAME
  },
  skipValidation: !!process.env.SKIP_ENV_VALIDATION,
  emptyStringAsUndefined: true
});

Monorepo-wide team registry

Track all teams in one place:

// packages/team-registry/src/index.ts
export const TEAMS = {
  ai: {
    id: "ai",
    name: "AI Team",
    apps: ["ai-training-dashboard", "ai-experiment-tracker"],
    packages: ["@ai/ml-utils", "@ai/model-wrappers"],
    portRange: [3200, 3299],
    owner: "ai-team@company.com"
  },
  istudio: {
    id: "istudio",
    name: "iStudio Team",
    apps: ["istudio-editor", "istudio-viewer"],
    packages: ["@istudio/media", "@istudio/components"],
    portRange: [3300, 3399],
    owner: "istudio-team@company.com"
  },
  myteam: {
    id: "myteam",
    name: "My Team",
    apps: ["myteam-dashboard", "myteam-mobile"],
    packages: ["@myteam/utils", "@myteam/components"],
    portRange: [3100, 3199],
    owner: "myteam@company.com"
  }
} as const;

export type TeamId = keyof typeof TEAMS;

export function getTeamById(id: TeamId) {
  return TEAMS[id];
}

Cross-team shared packages

When multiple teams need similar functionality:

packages/team-commons/          # Shared team utilities
├── src/
│   ├── analytics/              # Shared analytics setup
│   ├── auth/                   # Shared auth patterns
│   └── notifications/          # Shared notification system
└── package.json                # @repo/team-commons

Teams import:

import { setupAnalytics } from "@repo/team-commons/analytics";
import { withAuth } from "@repo/team-commons/auth";

Team-specific testing strategies

Shared test utilities per team:

// teams/myteam/packages/test-utils/src/index.ts
import { render as rtlRender } from "@testing-library/react";
import type { ReactElement } from "react";

// Team-specific test wrapper
export function render(ui: ReactElement) {
  return rtlRender(ui, {
    wrapper: ({ children }) => (
      <TeamProvider teamId="myteam">
        <AuthProvider>
          {children}
        </AuthProvider>
      </TeamProvider>
    ),
  });
}

// Team-specific test data factories
export function createTeamUser(overrides = {}) {
  return {
    id: "user_123",
    teamId: "myteam",
    email: "user@myteam.com",
    role: "member",
    ...overrides,
  };
}

Use in team tests:

// teams/myteam/apps/myteam-dashboard/src/components/Dashboard.test.tsx
import { render, createTeamUser } from "@myteam/test-utils";
import { Dashboard } from "./Dashboard";

test("renders dashboard for team user", () => {
  const user = createTeamUser({ role: "admin" });
  const { getByText } = render(<Dashboard user={user} />);

  expect(getByText("Team Dashboard")).toBeInTheDocument();
});

Team-specific code generators

Plop generators for team code:

// teams/myteam/plopfile.js
export default function (plop) {
  // Generate team component
  plop.setGenerator("component", {
    description: "Create a team component",
    prompts: [
      {
        type: "input",
        name: "name",
        message: "Component name:"
      }
    ],
    actions: [
      {
        type: "add",
        path: "packages/myteam-components/src/{{pascalCase name}}.tsx",
        templateFile: "templates/Component.tsx.hbs"
      },
      {
        type: "add",
        path: "packages/myteam-components/src/{{pascalCase name}}.test.tsx",
        templateFile: "templates/Component.test.tsx.hbs"
      }
    ]
  });

  // Generate team app page
  plop.setGenerator("page", {
    description: "Create a team app page",
    prompts: [
      {
        type: "input",
        name: "name",
        message: "Page name:"
      },
      {
        type: "list",
        name: "app",
        message: "Which app?",
        choices: ["myteam-dashboard", "myteam-mobile"]
      }
    ],
    actions: [
      {
        type: "add",
        path: "apps/{{app}}/src/app/{{kebabCase name}}/page.tsx",
        templateFile: "templates/Page.tsx.hbs"
      }
    ]
  });
}

Run:

cd teams/myteam
pnpm plop component
pnpm plop page

Team workspace CI/CD optimization

Only run CI for affected code:

# .github/workflows/myteam-ci.yml
name: MyTeam CI

on:
  pull_request:
    paths:
      - "teams/myteam/**"
      - "packages/**" # Shared packages affect all teams

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      team-changed: ${{ steps.filter.outputs.team }}
      packages-changed: ${{ steps.filter.outputs.packages }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v2
        id: filter
        with:
          filters: |
            team:
              - 'teams/myteam/**'
            packages:
              - 'packages/**'

  build:
    needs: detect-changes
    if: needs.detect-changes.outputs.team-changed == 'true' || needs.detect-changes.outputs.packages-changed == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: "pnpm"

      - run: pnpm install

      # Only test team code if it changed
      - if: needs.detect-changes.outputs.team-changed == 'true'
        run: pnpm --filter=./teams/myteam/** test

      # Always build (dependencies might have changed)
      - run: pnpm --filter=./teams/myteam/** build

Team-specific deployment strategies

Vercel monorepo deployment per team:

// teams/myteam/apps/myteam-dashboard/vercel.json
{
  "buildCommand": "cd ../../../ && pnpm --filter=myteam-dashboard build",
  "outputDirectory": ".next",
  "installCommand": "cd ../../../ && pnpm install",
  "framework": "nextjs",
  "ignoreCommand": "bash -c 'git diff HEAD^ HEAD --quiet teams/myteam/ packages/'",
  "env": {
    "DATABASE_URL": "@myteam-database-url",
    "AUTH_SECRET": "@myteam-auth-secret"
  }
}

Deploy only when team code changes:

#!/bin/bash
# teams/myteam/infra/scripts/should-deploy.sh

# Check if team code or shared packages changed
git diff HEAD^ HEAD --quiet teams/myteam/ packages/
if [ $? -ne 0 ]; then
  echo "Changes detected, deploying..."
  exit 0
else
  echo "No changes, skipping deployment"
  exit 1
fi

Resource isolation between teams

Database per team:

// teams/myteam/infra/database/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("MYTEAM_DATABASE_URL")
}

model TeamUser {
  id        String   @id @default(cuid())
  teamId    String   @default("myteam")
  email     String   @unique
  role      String
  createdAt DateTime @default(now())

  @@index([teamId])
}

Separate Prisma clients per team:

// teams/myteam/packages/db/src/index.ts
import { PrismaClient } from "@prisma/client";

// Team-specific Prisma client
export const teamDb = new PrismaClient({
  datasources: {
    db: {
      url: process.env.MYTEAM_DATABASE_URL
    }
  }
});

// Typed helper for team queries
export async function getTeamUser(email: string) {
  return teamDb.teamUser.findUnique({
    where: { email, teamId: "myteam" }
  });
}

Team workspace metrics and monitoring

Track team workspace health:

// packages/workspace-metrics/src/index.ts
import { readdir } from "node:fs/promises";
import { join } from "node:path";

export async function getTeamMetrics(teamId: string) {
  const teamPath = join(process.cwd(), "teams", teamId);

  const [apps, packages] = await Promise.all([readdir(join(teamPath, "apps")), readdir(join(teamPath, "packages"))]);

  return {
    teamId,
    appCount: apps.length,
    packageCount: packages.length,
    totalSize: await getDirectorySize(teamPath)
  };
}

async function getDirectorySize(path: string): Promise<number> {
  // Implementation
  return 0;
}

Dashboard for all teams:

// Platform dashboard showing team workspace metrics
import { getTeamMetrics } from "@repo/workspace-metrics";

const metrics = await Promise.all([getTeamMetrics("ai"), getTeamMetrics("istudio"), getTeamMetrics("myteam")]);

console.table(metrics);
// ┌─────────┬──────────┬───────────────┬───────────┐
// │ teamId  │ appCount │ packageCount  │ totalSize │
// ├─────────┼──────────┼───────────────┼───────────┤
// │ ai      │ 2        │ 3             │ 15.2 MB   │
// │ istudio │ 3        │ 2             │ 22.8 MB   │
// │ myteam  │ 2        │ 2             │ 8.5 MB    │
// └─────────┴──────────┴───────────────┴───────────┘

On this page