Skip to content

Monorepo Migration Plan: Step-by-Step Implementation

Date: December 2025
Status: Implementation Plan
Related: Repository-Structure-Decision.md, BFF-Implementation-Plan.md


Overview

This document provides a detailed, step-by-step plan to migrate bast-admin-ui from a single-package repository to a monorepo structure that includes both the frontend (admin-ui) and the BFF (bff) applications.

Target Structure:

bast-admin-ui/                    # Monorepo root
├── apps/
│   ├── admin-ui/                 # React frontend (current codebase)
│   │   ├── src/
│   │   ├── package.json
│   │   ├── vite.config.ts
│   │   └── index.html
│   │
│   └── bff/                      # Node.js BFF (new)
│       ├── src/
│       ├── package.json
│       └── tsconfig.json
│
├── packages/
│   └── shared/                   # Shared types & utilities (optional, future)
│       ├── types/
│       └── package.json
│
├── package.json                  # Root workspace
├── pnpm-workspace.yaml           # Workspace config (or npm workspaces)
├── Dockerfile                    # Updated for monorepo
├── bitbucket-pipelines.yml       # Updated CI/CD
└── .gitignore                    # Updated


Prerequisites

  • Git repository is clean (no uncommitted changes)
  • Create a backup branch: git checkout -b backup/pre-monorepo
  • Ensure all team members are aware of the migration
  • Decide on package manager: pnpm (recommended) or npm workspaces

Phase 1: Repository Restructuring

Step 1.1: Create Monorepo Directory Structure

# Create apps directory
mkdir -p apps/admin-ui
mkdir -p apps/bff
mkdir -p packages/shared  # Optional for future use

Step 1.2: Move Current Frontend Code to apps/admin-ui/

Files/Directories to Move: - src/ → apps/admin-ui/src/ - index.html → apps/admin-ui/index.html - vite.config.ts → apps/admin-ui/vite.config.ts - package.json → apps/admin-ui/package.json - package-lock.json → apps/admin-ui/package-lock.json (if using npm)

Commands:

# Move source code
git mv src apps/admin-ui/
git mv index.html apps/admin-ui/
git mv vite.config.ts apps/admin-ui/
git mv package.json apps/admin-ui/
git mv package-lock.json apps/admin-ui/  # if exists

# Move build output if needed (or add to .gitignore)
# git mv build apps/admin-ui/build  # Optional

Step 1.3: Update Frontend Package.json

Update apps/admin-ui/package.json: - Change name to @bast/admin-ui or admin-ui - Update any paths in scripts if needed - Ensure all dependencies are preserved

Example:

{
  "name": "@bast/admin-ui",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite --host 0.0.0.0",
    "build": "vite build",
    "preview": "vite preview"
  },
  // ... rest of dependencies
}

Step 1.4: Update Vite Config Paths

Update apps/admin-ui/vite.config.ts: - Update path aliases to reflect new structure - Ensure @ alias still points to ./src

Example:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
    alias: {
      '@': path.resolve(__dirname, './src'),
      // ... other aliases
    },
  },
  build: {
    target: 'esnext',
    outDir: 'build',
  },
  server: {
    port: 3000,
    open: false,
    allowedHosts: true,
  },
});


Phase 2: Workspace Configuration

Step 2.1: Create Root Package.json

Create package.json at root with workspace configuration:

For pnpm (Recommended):

{
  "name": "bast-admin-monorepo",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "pnpm --filter admin-ui dev",
    "dev:bff": "pnpm --filter bff dev",
    "dev:all": "pnpm --parallel --filter '*' dev",
    "build": "pnpm --filter '*' build",
    "build:admin-ui": "pnpm --filter admin-ui build",
    "build:bff": "pnpm --filter bff build",
    "install": "pnpm install",
    "clean": "pnpm --filter '*' clean"
  },
  "devDependencies": {
    "typescript": "^5.0.0"
  }
}

For npm workspaces:

{
  "name": "bast-admin-monorepo",
  "version": "0.1.0",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "dev": "npm run dev --workspace=admin-ui",
    "dev:bff": "npm run dev --workspace=bff",
    "dev:all": "npm run dev --workspaces",
    "build": "npm run build --workspaces",
    "build:admin-ui": "npm run build --workspace=admin-ui",
    "build:bff": "npm run build --workspace=bff"
  }
}

Step 2.2: Create Workspace Configuration File

For pnpm: Create pnpm-workspace.yaml:

packages:
  - 'apps/*'
  - 'packages/*'

For npm: Workspace config is in package.json (see Step 2.1).

Step 2.3: Update .gitignore

Add monorepo-specific ignores:

# Existing ignores...
node_modules/
build/
dist/

# Monorepo specific
apps/*/node_modules/
apps/*/build/
apps/*/dist/
packages/*/node_modules/
packages/*/dist/
.pnpm-store/


Phase 3: BFF Application Setup

Step 3.1: Initialize BFF Package

cd apps/bff
npm init -y  # or pnpm init

Step 3.2: Create BFF Package.json

Create apps/bff/package.json:

{
  "name": "@bast/bff",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "main": "./dist/index.js",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "type-check": "tsc --noEmit"
  },
  "dependencies": {
    "@trpc/server": "^10.45.0",
    "@trpc/client": "^10.45.0",
    "express": "^4.18.2",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/cors": "^2.8.17",
    "@types/node": "^20.10.0",
    "tsx": "^4.7.0",
    "typescript": "^5.3.3"
  }
}

Step 3.3: Create BFF TypeScript Config

Create apps/bff/tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022"],
    "moduleResolution": "node",
    "rootDir": "./src",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Step 3.4: Create Basic BFF Structure

mkdir -p apps/bff/src

Create apps/bff/src/index.ts (basic starter):

import express from 'express';
import cors from 'cors';
import { createExpressMiddleware } from '@trpc/server/adapters/express';
import { appRouter } from './trpc/router';

const app = express();
const PORT = process.env.PORT || 4000;

app.use(cors());
app.use(express.json());

app.use('/trpc', createExpressMiddleware({
  router: appRouter,
  createContext: () => ({}),
}));

app.listen(PORT, () => {
  console.log(`🚀 BFF server running on port ${PORT}`);
});

Create apps/bff/src/trpc/router.ts (basic starter):

import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.create();

export const appRouter = t.router({
  health: t.procedure.query(() => ({
    status: 'ok',
    timestamp: new Date().toISOString(),
  })),
});

export type AppRouter = typeof appRouter;

Note: Full BFF implementation will follow the BFF-Implementation-Plan.md.


Phase 4: Type Sharing Setup (tRPC)

Step 4.1: Install tRPC Client in Admin UI

Update apps/admin-ui/package.json:

{
  "dependencies": {
    // ... existing dependencies
    "@trpc/client": "^10.45.0",
    "@trpc/react-query": "^10.45.0",
    "@tanstack/react-query": "^5.0.0"
  }
}

Step 4.2: Create tRPC Client in Admin UI

Create apps/admin-ui/src/lib/trpc.ts:

import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../../bff/src/trpc/router';

// Direct import - types are shared!
export const trpc = createTRPCReact<AppRouter>();

Step 4.3: Set Up tRPC Provider

Create apps/admin-ui/src/lib/trpc-provider.tsx:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import { trpc } from './trpc';

const queryClient = new QueryClient();
const trpcClient = trpc.createClient({
  links: [
    httpBatchLink({
      url: import.meta.env.VITE_BFF_URL || 'http://localhost:4000/trpc',
    }),
  ],
});

export function TRPCProvider({ children }: { children: React.ReactNode }) {
  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        {children}
      </QueryClientProvider>
    </trpc.Provider>
  );
}


Phase 5: Docker Configuration Updates

Step 5.1: Update Dockerfile for Monorepo

Update root Dockerfile:

Option A: Multi-stage build (Recommended for production)

# Stage 1: Build admin-ui
FROM node:20-alpine AS admin-ui-builder
WORKDIR /app

# Copy workspace files
COPY package.json pnpm-workspace.yaml ./
COPY apps/admin-ui/package.json ./apps/admin-ui/
COPY apps/bff/package.json ./apps/bff/

# Install dependencies
RUN corepack enable && corepack prepare pnpm@latest --activate
RUN pnpm install --frozen-lockfile

# Copy source and build
COPY apps/admin-ui ./apps/admin-ui
RUN pnpm --filter admin-ui build

# Stage 2: Production image
FROM node:20-alpine
WORKDIR /app

# Copy built files
COPY --from=admin-ui-builder /app/apps/admin-ui/build ./build
COPY --from=admin-ui-builder /app/apps/admin-ui/package.json ./

# Install serve
RUN npm install -g serve

EXPOSE 9090
CMD ["serve", "-n", "-s", "build", "-p", "9090"]

Option B: Simple build (for development)

FROM node:20-alpine

WORKDIR /app

# Copy workspace configuration
COPY package.json pnpm-workspace.yaml ./
COPY apps/admin-ui/package.json ./apps/admin-ui/
COPY apps/bff/package.json ./apps/bff/

# Install dependencies
RUN corepack enable && corepack prepare pnpm@latest --activate
RUN pnpm install --frozen-lockfile

# Copy source code
COPY . .

# Build admin-ui
RUN pnpm --filter admin-ui build

# Install serve
RUN npm install -g serve

EXPOSE 9090
CMD ["serve", "-n", "-s", "apps/admin-ui/build", "-p", "9090"]

Note: Choose Option A for production, Option B for development/testing.

Step 5.2: Create .dockerignore

Create .dockerignore:

node_modules
apps/*/node_modules
apps/*/dist
apps/*/build
packages/*/node_modules
packages/*/dist
.git
.gitignore
README.md
docs
*.md
.env
.env.local


Phase 6: CI/CD Pipeline Updates

Step 6.1: Update Bitbucket Pipeline

Update bitbucket-pipelines.yml to handle monorepo structure:

image: atlassian/default-image:3

definitions:
  steps:
    - step: &build-and-deploy-ui
        name: Build UI Docker Image and Deploy to ECR
        size: 2x
        services:
          - docker
        caches:
          - docker
        script:
          # Install required tools
          - apt-get update && apt-get install -y jq curl unzip python3-pip

          # Install Node.js and pnpm
          - curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
          - apt-get install -y nodejs
          - corepack enable && corepack prepare pnpm@latest --activate

          # Install AWS CLI
          - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
          - unzip awscliv2.zip
          - ./aws/install

          # Install Duplo CLI tools (existing code)
          - |
            DUPLO_VERSION=$(curl -s https://api.github.com/repos/duplocloud/duplo-jit/releases/latest | jq -r '.tag_name')
            VERSION_NUM=$(echo $DUPLO_VERSION | sed 's/v//')
            echo "Installing duplo-jit version: ${DUPLO_VERSION}"
            curl -L -o duplo-jit.zip "https://github.com/duplocloud/duplo-jit/releases/download/${DUPLO_VERSION}/duplo-jit_${VERSION_NUM}_linux_amd64.zip"
            unzip -o duplo-jit.zip
            chmod +x duplo-jit
            mv duplo-jit /usr/local/bin/duplo-jit
            rm -f duplo-jit.zip CHANGELOG.md LICENSE README.md duplo-aws-credential-process
            pip3 install duplocloud-client

          # Configure Duplo
          - export DUPLO_HOST=${DUPLO_HOST}
          - export DUPLO_TOKEN=${DUPLO_TOKEN}
          - export DUPLO_TENANT=${DUPLO_TENANT}

          # Authenticate with Duplo
          - |
            echo "Getting AWS credentials from Duplo..."
            CREDS=$(duplo-jit aws --admin --host ${DUPLO_HOST} --token ${DUPLO_TOKEN})
            export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r '.AccessKeyId')
            export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r '.SecretAccessKey')
            export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r '.SessionToken')
            echo "✅ AWS credentials obtained"

          # Get short SHA for tagging
          - export SHORT_SHA=$(git rev-parse --short HEAD)
          - echo "Building with tag admin-ui-${SHORT_SHA}"

          # Set SERVICE_NAME based on branch
          - |
            if [ -z "${SERVICE_NAME}" ]; then
              if [ "${BITBUCKET_BRANCH}" = "dev" ]; then
                export SERVICE_NAME="dev-admin-ui"
              elif [ "${BITBUCKET_BRANCH}" = "main" ]; then
                export SERVICE_NAME="demo-admin-ui"
              fi
            fi

          # Login to ECR
          - aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com

          # Build Docker image
          - |
            docker build \
              --platform linux/amd64 \
              -f Dockerfile \
              -t <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/bastai-cat:admin-ui-${SHORT_SHA} \
              .

          # Push to ECR
          - docker push <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/bastai-cat:admin-ui-${SHORT_SHA}

          # Validate SERVICE_NAME
          - |
            if [ -z "${SERVICE_NAME}" ]; then
              echo "❌ ERROR: SERVICE_NAME is not set."
              exit 1
            fi

          # Update Duplo service
          - |
            echo "Updating service ${SERVICE_NAME} with image admin-ui-${SHORT_SHA}"
            duploctl service update_image ${SERVICE_NAME} <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/bastai-cat:admin-ui-${SHORT_SHA}

          - echo "✅ UI deployed successfully with tag admin-ui-${SHORT_SHA}"

pipelines:
  branches:
    dev:
      - step:
          <<: *build-and-deploy-ui
          variables:
            SERVICE_NAME: "dev-admin-ui"
    main:
      - step:
          <<: *build-and-deploy-ui
          variables:
            SERVICE_NAME: "demo-admin-ui"

  custom:
    deploy-ui-manual:
      - step:
          <<: *build-and-deploy-ui
          deployment: production

options:
  docker: true
  size: 2x

Key Changes: - Added Node.js and pnpm installation - Docker build now handles monorepo structure (via updated Dockerfile)

Step 6.2: Future: Add BFF Pipeline Step (Optional)

For future BFF deployment, add a separate step:

- step: &build-and-deploy-bff
    name: Build BFF Docker Image and Deploy
    # Similar structure but builds apps/bff


Phase 7: Environment Variables

Step 7.1: Create Environment Files

Create apps/admin-ui/.env.example:

VITE_BFF_URL=http://localhost:4000/trpc

Create apps/bff/.env.example:

PORT=4000
NODE_ENV=development

Step 7.2: Update .gitignore

Add environment files:

.env
.env.local
.env.*.local
apps/*/.env
apps/*/.env.local


Phase 8: Documentation Updates

Step 8.1: Update Root README.md

Update README.md:

# Bast Admin Monorepo

This monorepo contains:
- **admin-ui**: React/TypeScript frontend application
- **bff**: Node.js/TypeScript Backend-for-Frontend (BFF) layer

## Getting Started

### Prerequisites
- Node.js 20+
- pnpm (or npm)

### Installation
\`\`\`bash
pnpm install
\`\`\`

### Development

Run frontend:
\`\`\`bash
pnpm dev
# or
pnpm --filter admin-ui dev
\`\`\`

Run BFF:
\`\`\`bash
pnpm dev:bff
# or
pnpm --filter bff dev
\`\`\`

Run both:
\`\`\`bash
pnpm dev:all
\`\`\`

### Building

Build all:
\`\`\`bash
pnpm build
\`\`\`

Build specific app:
\`\`\`bash
pnpm build:admin-ui
pnpm build:bff
\`\`\`

## Structure

\`\`\`
bast-admin-ui/
├── apps/
│   ├── admin-ui/     # Frontend React app
│   └── bff/          # BFF tRPC server
├── packages/         # Shared packages (future)
└── package.json      # Root workspace config
\`\`\`

## Architecture

See [docs/architecture/](./docs/architecture/) for detailed architecture documentation.

Step 8.2: Create Migration Notes

Document any breaking changes or migration notes for the team.


Phase 9: Testing & Validation

Step 9.1: Local Testing Checklist

  • pnpm install works at root
  • pnpm dev starts admin-ui successfully
  • pnpm dev:bff starts BFF successfully
  • pnpm build builds both apps
  • Frontend can connect to BFF via tRPC
  • TypeScript types are shared correctly
  • All existing functionality works

Step 9.2: CI/CD Testing

  • Pipeline runs successfully
  • Docker image builds correctly
  • Deployment works as expected

Step 9.3: Rollback Plan

If issues arise: 1. Revert to backup branch: git checkout backup/pre-monorepo 2. Or revert specific commits if needed


Phase 10: Cleanup

Step 10.1: Remove Old Files

After successful migration: - [ ] Remove any unused files at root - [ ] Clean up old build artifacts - [ ] Update any hardcoded paths in scripts

Step 10.2: Update Team Documentation

  • Update onboarding docs
  • Update development workflow docs
  • Communicate changes to team

Execution Order Summary

  1. Phase 1: Restructure repository (move files)
  2. Phase 2: Set up workspace configuration
  3. Phase 3: Create BFF structure
  4. Phase 4: Set up type sharing (tRPC)
  5. Phase 5: Update Docker configuration
  6. Phase 6: Update CI/CD pipeline
  7. Phase 7: Configure environment variables
  8. Phase 8: Update documentation
  9. Phase 9: Test everything
  10. Phase 10: Cleanup

Estimated Timeline

  • Phase 1-2: 1-2 hours (restructuring + workspace setup)
  • Phase 3-4: 2-3 hours (BFF setup + tRPC)
  • Phase 5-6: 1-2 hours (Docker + CI/CD)
  • Phase 7-8: 1 hour (env + docs)
  • Phase 9: 2-3 hours (testing)
  • Phase 10: 30 minutes (cleanup)

Total Estimated Time: 8-12 hours


Risk Mitigation

  1. Backup Branch: Always work from a backup branch
  2. Incremental Commits: Commit after each phase
  3. Test Frequently: Test after each major change
  4. Team Communication: Keep team informed of progress
  5. Rollback Ready: Have rollback plan ready

Next Steps After Migration

  1. Implement full BFF according to BFF-Implementation-Plan.md
  2. Set up shared packages if needed (packages/shared)
  3. Add Turborepo or Nx for faster builds (optional)
  4. Set up integration tests
  5. Configure monitoring and logging

Plan created for Bast Admin UI Monorepo Migration