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:
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¶
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¶
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:
Create apps/bff/.env.example:
Step 7.2: Update .gitignore¶
Add environment files:
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 installworks at root -
pnpm devstarts admin-ui successfully -
pnpm dev:bffstarts BFF successfully -
pnpm buildbuilds 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¶
- Phase 1: Restructure repository (move files)
- Phase 2: Set up workspace configuration
- Phase 3: Create BFF structure
- Phase 4: Set up type sharing (tRPC)
- Phase 5: Update Docker configuration
- Phase 6: Update CI/CD pipeline
- Phase 7: Configure environment variables
- Phase 8: Update documentation
- Phase 9: Test everything
- 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¶
- Backup Branch: Always work from a backup branch
- Incremental Commits: Commit after each phase
- Test Frequently: Test after each major change
- Team Communication: Keep team informed of progress
- Rollback Ready: Have rollback plan ready
Next Steps After Migration¶
- Implement full BFF according to BFF-Implementation-Plan.md
- Set up shared packages if needed (
packages/shared) - Add Turborepo or Nx for faster builds (optional)
- Set up integration tests
- Configure monitoring and logging
Plan created for Bast Admin UI Monorepo Migration