AI Prompt: Forma3D.Connect — Phase 0: Foundation ✅¶
Purpose: This prompt instructs an AI to generate the complete Phase 0 foundation for Forma3D.Connect
Estimated Effort: 20 hours
Output: Working Nx monorepo with all infrastructure in place
Status: ✅ COMPLETED — January 2026
🎯 Mission¶
You are building the foundation for Forma3D.Connect, an integration platform that connects a 3D print farm's e-commerce (Shopify) with print farm management (SimplyPrint). Your task is to execute Phase 0: Foundation — setting up the complete project infrastructure.
📋 Project Context¶
What is Forma3D.Connect?¶
Forma3D.Connect automates the print-on-demand workflow: 1. Shopify orders trigger print jobs in SimplyPrint 2. Completed prints trigger fulfillment in Shopify 3. Shipping labels are generated via Sendcloud
Vision Statement¶
"To create a seamless, automated bridge between e-commerce orders and 3D print production, enabling true print-on-demand operations where every Shopify order automatically triggers production, and every completed print automatically fulfills customer orders."
🛠️ Tech Stack (MANDATORY)¶
You MUST use exactly these technologies. Do not substitute or add others without explicit approval.
Frontend¶
| Technology | Version | Purpose |
|---|---|---|
| React | 19.x | UI framework |
| TypeScript | 5.x | Language |
| Vite | Latest | Bundler |
| Tailwind CSS | 3.x | Styling |
| TanStack Query | 5.x | Server state |
| React Router | 6.x | Routing |
Backend¶
| Technology | Version | Purpose |
|---|---|---|
| Node.js | 20.x LTS | Runtime |
| NestJS | 10.x | Framework |
| TypeScript | 5.x | Language |
| Socket.IO | 4.x | Real-time |
Database¶
| Technology | Version | Purpose |
|---|---|---|
| PostgreSQL | 16.x | Database |
| Prisma | 5.x | ORM |
Infrastructure¶
| Technology | Purpose |
|---|---|
| Nx | 19.x |
| pnpm | 9.x |
| Vitest | Frontend testing |
| Jest | Backend testing |
| Azure DevOps Pipelines | CI/CD |
📁 Required Project Structure¶
Create this EXACT folder structure:
forma-3d-connect/
├── apps/
│ ├── api/ # NestJS backend
│ │ ├── src/
│ │ │ ├── main.ts
│ │ │ ├── app.module.ts
│ │ │ ├── app.controller.ts
│ │ │ ├── app.service.ts
│ │ │ ├── config/
│ │ │ │ ├── config.module.ts
│ │ │ │ ├── configuration.ts
│ │ │ │ └── env.validation.ts
│ │ │ ├── health/
│ │ │ │ ├── health.module.ts
│ │ │ │ └── health.controller.ts
│ │ │ └── database/
│ │ │ ├── database.module.ts
│ │ │ └── prisma.service.ts
│ │ ├── test/
│ │ │ └── app.e2e-spec.ts
│ │ ├── project.json
│ │ ├── tsconfig.app.json
│ │ ├── tsconfig.spec.json
│ │ └── jest.config.ts
│ │
│ └── web/ # React 19 frontend
│ ├── src/
│ │ ├── main.tsx
│ │ ├── app.tsx
│ │ ├── router.tsx
│ │ ├── index.css
│ │ ├── components/
│ │ │ └── layout/
│ │ │ ├── root-layout.tsx
│ │ │ ├── sidebar.tsx
│ │ │ └── header.tsx
│ │ ├── pages/
│ │ │ ├── dashboard.tsx
│ │ │ └── not-found.tsx
│ │ ├── hooks/
│ │ │ └── use-health.ts
│ │ └── lib/
│ │ └── api-client.ts
│ ├── index.html
│ ├── public/
│ ├── project.json
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ ├── vite.config.ts
│ ├── tailwind.config.js
│ └── postcss.config.js
│
├── libs/
│ ├── domain/ # Shared types and business logic
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── entities/
│ │ │ │ ├── order.ts
│ │ │ │ ├── line-item.ts
│ │ │ │ ├── print-job.ts
│ │ │ │ ├── product-mapping.ts
│ │ │ │ ├── assembly-part.ts
│ │ │ │ ├── printer.ts
│ │ │ │ └── event-log.ts
│ │ │ ├── enums/
│ │ │ │ ├── order-status.ts
│ │ │ │ ├── line-item-status.ts
│ │ │ │ ├── print-job-status.ts
│ │ │ │ └── event-severity.ts
│ │ │ └── types/
│ │ │ └── index.ts
│ │ ├── project.json
│ │ └── tsconfig.json
│ │
│ ├── api-client/ # Typed API client for frontend
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── client.ts
│ │ │ └── types.ts
│ │ ├── project.json
│ │ └── tsconfig.json
│ │
│ ├── utils/ # Generic utilities
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── date.ts
│ │ │ └── string.ts
│ │ ├── project.json
│ │ └── tsconfig.json
│ │
│ └── config/ # Shared configuration
│ ├── src/
│ │ ├── index.ts
│ │ └── constants.ts
│ ├── project.json
│ └── tsconfig.json
│
├── prisma/
│ ├── schema.prisma
│ ├── migrations/
│ └── seed.ts
│
├── .azuredevops/
│ └── pipelines/
│ ├── ci.yml
│ └── templates/
│ ├── install-dependencies.yml
│ └── setup-database.yml
│
├── azure-pipelines.yml # Main pipeline entry point
│
├── .vscode/
│ ├── settings.json
│ ├── extensions.json
│ └── launch.json
│
├── .cursor/
│ └── rules/
│ └── derived-cursor-rules.mdc # (preserve existing)
│
├── docs/ # (preserve existing)
│ ├── vision.md
│ ├── requirements.md
│ ├── implementation-plan.md
│ └── prompts/
│ └── prompt-phase0.md
│
├── nx.json
├── package.json
├── pnpm-workspace.yaml
├── tsconfig.base.json
├── .gitignore
├── .env.example
├── .prettierrc
├── .eslintrc.json
└── README.md
🗄️ Database Schema (Prisma)¶
Schema Design Philosophy¶
The schema supports assemblies: a single Shopify product can consist of multiple 3D parts that need to be printed separately in SimplyPrint.
Key relationships: - ProductMapping (1) → AssemblyPart (many): One product can have multiple 3D files/parts - LineItem (1) → PrintJob (many): One order line item creates one print job per assembly part - PrintJob references which AssemblyPart it's printing
Shopify Product (SKU: "robot-kit")
│
└── ProductMapping
│
├── AssemblyPart: "robot-body.gcode" (part 1 of 3)
├── AssemblyPart: "robot-arm-left.gcode" (part 2 of 3)
└── AssemblyPart: "robot-arm-right.gcode" (part 3 of 3)
│
└── When ordered → 3 PrintJobs created
Create the following schema in prisma/schema.prisma:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ============================================================================
// ENUMS
// ============================================================================
enum OrderStatus {
PENDING
PROCESSING
PARTIALLY_COMPLETED // Some parts printed, others pending
COMPLETED
FAILED
CANCELLED
}
enum LineItemStatus {
PENDING
PRINTING // At least one part is printing
PARTIALLY_COMPLETED // Some parts done, others pending
COMPLETED // All parts printed
FAILED
}
enum PrintJobStatus {
QUEUED
ASSIGNED
PRINTING
COMPLETED
FAILED
CANCELLED
}
enum EventSeverity {
INFO
WARNING
ERROR
}
// ============================================================================
// ORDER MODELS
// ============================================================================
model Order {
id String @id @default(uuid())
shopifyOrderId String @unique
shopifyOrderNumber String
status OrderStatus @default(PENDING)
customerName String
customerEmail String?
shippingAddress Json
totalPrice Decimal @db.Decimal(10, 2)
currency String @default("EUR")
// Fulfillment
shopifyFulfillmentId String?
trackingNumber String?
trackingUrl String?
// Counts for quick status checks
totalParts Int @default(0) // Total parts across all line items
completedParts Int @default(0) // Parts successfully printed
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
completedAt DateTime?
// Relations
lineItems LineItem[]
eventLogs EventLog[]
@@index([status])
@@index([createdAt])
@@index([shopifyOrderNumber])
}
model LineItem {
id String @id @default(uuid())
orderId String
shopifyLineItemId String
productMappingId String? // Reference to product mapping used
productSku String
productName String
variantTitle String?
quantity Int // Quantity ordered (each creates N print jobs per part)
unitPrice Decimal @db.Decimal(10, 2)
status LineItemStatus @default(PENDING)
// Assembly tracking
totalParts Int @default(0) // Number of parts in this product
completedParts Int @default(0) // Parts completed for this line item
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
productMapping ProductMapping? @relation(fields: [productMappingId], references: [id], onDelete: SetNull)
printJobs PrintJob[] // One print job per assembly part (per quantity unit)
@@unique([orderId, shopifyLineItemId])
@@index([productSku])
@@index([status])
}
// ============================================================================
// PRINT JOB MODELS
// ============================================================================
model PrintJob {
id String @id @default(uuid())
lineItemId String
assemblyPartId String? // Which part of the assembly this job prints
simplyPrintJobId String? @unique
status PrintJobStatus @default(QUEUED)
// For quantity > 1: which copy is this? (1, 2, 3...)
copyNumber Int @default(1)
// Print details from SimplyPrint
printerId String?
printerName String?
// File details (denormalized from AssemblyPart for historical record)
fileId String?
fileName String?
// Timing
queuedAt DateTime @default(now())
startedAt DateTime?
completedAt DateTime?
estimatedDuration Int? // Estimated print time in seconds
actualDuration Int? // Actual print time in seconds
// Error handling
errorMessage String?
retryCount Int @default(0)
maxRetries Int @default(3)
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
lineItem LineItem @relation(fields: [lineItemId], references: [id], onDelete: Cascade)
assemblyPart AssemblyPart? @relation(fields: [assemblyPartId], references: [id], onDelete: SetNull)
@@index([status])
@@index([simplyPrintJobId])
@@index([lineItemId])
@@index([assemblyPartId])
}
// ============================================================================
// PRODUCT MAPPING MODELS
// ============================================================================
model ProductMapping {
id String @id @default(uuid())
shopifyProductId String
shopifyVariantId String?
sku String @unique
productName String
description String?
// Assembly configuration
isAssembly Boolean @default(false) // True if product has multiple parts
// Default print profile (can be overridden per part)
defaultPrintProfile Json? // { material, quality, infill, supports, etc. }
// Status
isActive Boolean @default(true)
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
assemblyParts AssemblyPart[] // Parts that make up this product
lineItems LineItem[] // Line items that used this mapping
@@index([shopifyProductId])
@@index([isActive])
@@index([sku])
}
model AssemblyPart {
id String @id @default(uuid())
productMappingId String
// Part identification
partName String // e.g., "Main Body", "Left Arm", "Right Arm"
partNumber Int @default(1) // Order/sequence number in assembly
// SimplyPrint file reference
simplyPrintFileId String // File ID in SimplyPrint
simplyPrintFileName String? // Human-readable filename
// Print profile (overrides ProductMapping.defaultPrintProfile if set)
printProfile Json? // { material, quality, infill, supports, etc. }
// Part metadata
estimatedPrintTime Int? // Estimated print time in seconds
estimatedFilament Decimal? @db.Decimal(10, 2) // Estimated filament in grams
// Quantity per product (e.g., need 2 of this part per product)
quantityPerProduct Int @default(1)
// Status
isActive Boolean @default(true)
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
productMapping ProductMapping @relation(fields: [productMappingId], references: [id], onDelete: Cascade)
printJobs PrintJob[] // Print jobs for this part
@@unique([productMappingId, partNumber]) // Part numbers unique within a product
@@index([simplyPrintFileId])
@@index([isActive])
}
// ============================================================================
// SYSTEM MODELS
// ============================================================================
model EventLog {
id String @id @default(uuid())
orderId String?
printJobId String? // Can also log events for specific print jobs
eventType String
severity EventSeverity @default(INFO)
message String
metadata Json?
// Timestamps
createdAt DateTime @default(now())
// Relations
order Order? @relation(fields: [orderId], references: [id], onDelete: SetNull)
@@index([eventType])
@@index([severity])
@@index([createdAt])
@@index([orderId])
@@index([printJobId])
}
model SystemConfig {
id String @id @default(uuid())
key String @unique
value Json
description String?
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ============================================================================
// OPTIONAL: Printer tracking (for dashboard visibility)
// ============================================================================
model Printer {
id String @id @default(uuid())
simplyPrintId String @unique // Printer ID in SimplyPrint
name String
model String?
// Status (cached from SimplyPrint)
isOnline Boolean @default(false)
currentStatus String? // idle, printing, error, etc.
currentJobId String? // Currently printing job
// Last sync
lastSyncAt DateTime @default(now())
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([isOnline])
@@index([currentStatus])
}
Schema Entity Relationship Diagram¶
Key Schema Features¶
| Feature | Description |
|---|---|
| Assembly Support | One Shopify product can have multiple 3D parts via AssemblyPart |
| Quantity Handling | copyNumber tracks which copy of ordered quantity this print is for |
| Part-per-Product | quantityPerProduct allows parts that need multiple copies (e.g., 4 wheels per car) |
| Progress Tracking | totalParts and completedParts on Order and LineItem for quick status |
| Partial Completion | PARTIALLY_COMPLETED status when some parts done |
| Historical Record | File details denormalized to PrintJob for audit trail |
| Printer Tracking | Optional Printer model for dashboard visibility |
🔧 Environment Configuration¶
.env.example¶
Create this file with ALL required environment variables:
# ============================================================================
# Forma3D.Connect Environment Configuration
# ============================================================================
# Copy this file to .env and fill in the values
# NEVER commit .env to version control
# ============================================================================
# ----------------------------------------------------------------------------
# Database
# ----------------------------------------------------------------------------
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/forma3d_connect?schema=public"
# ----------------------------------------------------------------------------
# Application
# ----------------------------------------------------------------------------
NODE_ENV="development"
APP_PORT=3000
APP_URL="http://localhost:3000"
# Frontend URL (for CORS)
FRONTEND_URL="http://localhost:4200"
# ----------------------------------------------------------------------------
# Shopify API
# ----------------------------------------------------------------------------
SHOPIFY_SHOP_DOMAIN="your-store.myshopify.com"
SHOPIFY_API_KEY="your-api-key"
SHOPIFY_API_SECRET="your-api-secret"
SHOPIFY_ACCESS_TOKEN="shpat_xxxxxxxxxxxxx"
SHOPIFY_WEBHOOK_SECRET="your-webhook-secret"
SHOPIFY_API_VERSION="2024-01"
# ----------------------------------------------------------------------------
# SimplyPrint API
# ----------------------------------------------------------------------------
SIMPLYPRINT_API_URL="https://api.simplyprint.io/v1"
SIMPLYPRINT_API_KEY="your-simplyprint-api-key"
# ----------------------------------------------------------------------------
# Sendcloud API
# ----------------------------------------------------------------------------
SENDCLOUD_PUBLIC_KEY="your-public-key"
SENDCLOUD_SECRET_KEY="your-secret-key"
# ----------------------------------------------------------------------------
# Optional: Logging
# ----------------------------------------------------------------------------
LOG_LEVEL="debug"
# ----------------------------------------------------------------------------
# Optional: Email Notifications
# ----------------------------------------------------------------------------
# SMTP_HOST=
# SMTP_PORT=
# SMTP_USER=
# SMTP_PASS=
# NOTIFICATION_EMAIL=
Environment Validation¶
Create strict environment validation in apps/api/src/config/env.validation.ts:
import { plainToInstance } from 'class-transformer';
import { IsEnum, IsNumber, IsString, IsUrl, validateSync } from 'class-validator';
enum Environment {
Development = 'development',
Production = 'production',
Test = 'test',
}
export class EnvironmentVariables {
@IsEnum(Environment)
NODE_ENV: Environment;
@IsNumber()
APP_PORT: number;
@IsUrl({ require_tld: false })
APP_URL: string;
@IsUrl({ require_tld: false })
FRONTEND_URL: string;
@IsString()
DATABASE_URL: string;
@IsString()
SHOPIFY_SHOP_DOMAIN: string;
@IsString()
SHOPIFY_API_KEY: string;
@IsString()
SHOPIFY_API_SECRET: string;
@IsString()
SHOPIFY_ACCESS_TOKEN: string;
@IsString()
SHOPIFY_WEBHOOK_SECRET: string;
@IsString()
SHOPIFY_API_VERSION: string;
@IsUrl()
SIMPLYPRINT_API_URL: string;
@IsString()
SIMPLYPRINT_API_KEY: string;
@IsString()
SENDCLOUD_PUBLIC_KEY: string;
@IsString()
SENDCLOUD_SECRET_KEY: string;
}
export function validate(config: Record<string, unknown>) {
const validatedConfig = plainToInstance(EnvironmentVariables, config, {
enableImplicitConversion: true,
});
const errors = validateSync(validatedConfig, {
skipMissingProperties: false,
});
if (errors.length > 0) {
throw new Error(errors.toString());
}
return validatedConfig;
}
🚀 CI/CD Pipeline (Azure DevOps)¶
azure-pipelines.yml (Root Pipeline Entry Point)¶
# ============================================================================
# Forma3D.Connect - Azure DevOps CI/CD Pipeline
# ============================================================================
# This is the main pipeline entry point
# ============================================================================
trigger:
branches:
include:
- main
- develop
paths:
exclude:
- '*.md'
- 'docs/**'
pr:
branches:
include:
- main
- develop
variables:
nodeVersion: '20.x'
pnpmVersion: '9'
isMain: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')]
isDevelop: $[eq(variables['Build.SourceBranch'], 'refs/heads/develop')]
pool:
vmImage: 'ubuntu-latest'
stages:
- stage: Validate
displayName: 'Validate'
jobs:
- job: Lint
displayName: 'Lint'
steps:
- template: .azuredevops/pipelines/templates/install-dependencies.yml
- script: pnpm nx affected --target=lint --parallel=3
displayName: 'Run Linting'
- job: TypeCheck
displayName: 'Type Check'
steps:
- template: .azuredevops/pipelines/templates/install-dependencies.yml
- script: pnpm prisma generate
displayName: 'Generate Prisma Client'
- script: pnpm nx run-many --target=typecheck --all --parallel=3
displayName: 'Run Type Check'
- stage: Test
displayName: 'Test'
dependsOn: Validate
jobs:
- job: UnitTests
displayName: 'Unit Tests'
services:
postgres:
image: postgres:16
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: forma3d_connect_test
steps:
- template: .azuredevops/pipelines/templates/install-dependencies.yml
- template: .azuredevops/pipelines/templates/setup-database.yml
- script: pnpm nx affected --target=test --parallel=3 --coverage
displayName: 'Run Unit Tests'
env:
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/forma3d_connect_test?schema=public'
- task: PublishTestResults@2
displayName: 'Publish Test Results'
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
mergeTestResults: true
- task: PublishCodeCoverageResults@1
displayName: 'Publish Code Coverage'
condition: succeededOrFailed()
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage/**/cobertura-coverage.xml'
- stage: Build
displayName: 'Build'
dependsOn: Test
jobs:
- job: BuildAll
displayName: 'Build All Projects'
steps:
- template: .azuredevops/pipelines/templates/install-dependencies.yml
- script: pnpm prisma generate
displayName: 'Generate Prisma Client'
- script: pnpm nx affected --target=build --parallel=3
displayName: 'Build Projects'
- task: PublishBuildArtifacts@1
displayName: 'Publish API Artifact'
inputs:
pathToPublish: 'dist/apps/api'
artifactName: 'api'
condition: and(succeeded(), or(eq(variables.isMain, true), eq(variables.isDevelop, true)))
- task: PublishBuildArtifacts@1
displayName: 'Publish Web Artifact'
inputs:
pathToPublish: 'dist/apps/web'
artifactName: 'web'
condition: and(succeeded(), or(eq(variables.isMain, true), eq(variables.isDevelop, true)))
- stage: DeployStaging
displayName: 'Deploy to Staging'
dependsOn: Build
condition: and(succeeded(), eq(variables.isDevelop, true))
jobs:
- deployment: DeployStaging
displayName: 'Deploy to Staging'
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploy to staging environment"
displayName: 'Deploy Staging (placeholder)'
- stage: DeployProduction
displayName: 'Deploy to Production'
dependsOn: Build
condition: and(succeeded(), eq(variables.isMain, true))
jobs:
- deployment: DeployProduction
displayName: 'Deploy to Production'
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploy to production environment"
displayName: 'Deploy Production (placeholder)'
.azuredevops/pipelines/templates/install-dependencies.yml¶
# ============================================================================
# Template: Install Dependencies
# ============================================================================
# Reusable template for installing Node.js, pnpm, and project dependencies
# ============================================================================
steps:
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '$(nodeVersion)'
- script: |
corepack enable
corepack prepare pnpm@$(pnpmVersion) --activate
displayName: 'Install pnpm'
- task: Cache@2
displayName: 'Cache pnpm store'
inputs:
key: 'pnpm | "$(Agent.OS)" | pnpm-lock.yaml'
restoreKeys: |
pnpm | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.pnpm-store
- script: |
pnpm config set store-dir $(Pipeline.Workspace)/.pnpm-store
pnpm install --frozen-lockfile
displayName: 'Install Dependencies'
- script: |
git fetch origin main:main --depth=1 || true
displayName: 'Fetch main for Nx affected'
condition: ne(variables['Build.SourceBranch'], 'refs/heads/main')
.azuredevops/pipelines/templates/setup-database.yml¶
# ============================================================================
# Template: Setup Database
# ============================================================================
# Reusable template for setting up the test database
# ============================================================================
steps:
- script: |
echo "Waiting for PostgreSQL to be ready..."
for i in {1..30}; do
pg_isready -h localhost -p 5432 -U postgres && break
sleep 1
done
displayName: 'Wait for PostgreSQL'
- script: pnpm prisma generate
displayName: 'Generate Prisma Client'
- script: pnpm prisma migrate deploy
displayName: 'Run Database Migrations'
env:
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/forma3d_connect_test?schema=public'
.azuredevops/pipelines/ci.yml (Alternative standalone CI file)¶
# ============================================================================
# Forma3D.Connect - CI Pipeline (Standalone)
# ============================================================================
# Use this if you prefer a simpler single-file pipeline
# ============================================================================
trigger:
- main
- develop
pr:
- main
- develop
pool:
vmImage: 'ubuntu-latest'
variables:
nodeVersion: '20.x'
pnpmVersion: '9'
stages:
- stage: CI
displayName: 'Continuous Integration'
jobs:
# -----------------------------------------------------------------------
# Lint Job
# -----------------------------------------------------------------------
- job: Lint
displayName: 'Lint'
steps:
- task: NodeTool@0
inputs:
versionSpec: '$(nodeVersion)'
displayName: 'Install Node.js'
- script: |
corepack enable
corepack prepare pnpm@$(pnpmVersion) --activate
displayName: 'Install pnpm'
- script: pnpm install --frozen-lockfile
displayName: 'Install Dependencies'
- script: pnpm nx affected --target=lint --parallel=3
displayName: 'Run Linting'
# -----------------------------------------------------------------------
# Test Job
# -----------------------------------------------------------------------
- job: Test
displayName: 'Test'
services:
postgres:
image: postgres:16
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: forma3d_connect_test
steps:
- task: NodeTool@0
inputs:
versionSpec: '$(nodeVersion)'
displayName: 'Install Node.js'
- script: |
corepack enable
corepack prepare pnpm@$(pnpmVersion) --activate
displayName: 'Install pnpm'
- script: pnpm install --frozen-lockfile
displayName: 'Install Dependencies'
- script: pnpm prisma generate
displayName: 'Generate Prisma Client'
- script: |
for i in {1..30}; do
pg_isready -h localhost -p 5432 -U postgres && break
sleep 1
done
displayName: 'Wait for PostgreSQL'
- script: pnpm prisma migrate deploy
displayName: 'Run Migrations'
env:
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/forma3d_connect_test?schema=public'
- script: pnpm nx affected --target=test --parallel=3 --coverage
displayName: 'Run Tests'
env:
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/forma3d_connect_test?schema=public'
# -----------------------------------------------------------------------
# Build Job
# -----------------------------------------------------------------------
- job: Build
displayName: 'Build'
dependsOn:
- Lint
- Test
steps:
- task: NodeTool@0
inputs:
versionSpec: '$(nodeVersion)'
displayName: 'Install Node.js'
- script: |
corepack enable
corepack prepare pnpm@$(pnpmVersion) --activate
displayName: 'Install pnpm'
- script: pnpm install --frozen-lockfile
displayName: 'Install Dependencies'
- script: pnpm prisma generate
displayName: 'Generate Prisma Client'
- script: pnpm nx affected --target=build --parallel=3
displayName: 'Build Projects'
# -----------------------------------------------------------------------
# Type Check Job
# -----------------------------------------------------------------------
- job: TypeCheck
displayName: 'Type Check'
steps:
- task: NodeTool@0
inputs:
versionSpec: '$(nodeVersion)'
displayName: 'Install Node.js'
- script: |
corepack enable
corepack prepare pnpm@$(pnpmVersion) --activate
displayName: 'Install pnpm'
- script: pnpm install --frozen-lockfile
displayName: 'Install Dependencies'
- script: pnpm prisma generate
displayName: 'Generate Prisma Client'
- script: pnpm nx run-many --target=typecheck --all --parallel=3
displayName: 'Run Type Check'
📦 Root Configuration Files¶
package.json¶
{
"name": "forma3d-connect",
"version": "0.0.1",
"private": true,
"description": "Integration services connecting Shopify and SimplyPrint for Forma 3D print farm",
"license": "UNLICENSED",
"scripts": {
"start": "nx serve",
"build": "nx build",
"test": "nx test",
"lint": "nx lint",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepare": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:migrate:deploy": "prisma migrate deploy",
"db:seed": "prisma db seed",
"db:studio": "prisma studio",
"api:dev": "nx serve api",
"web:dev": "nx serve web",
"dev": "nx run-many --target=serve --projects=api,web --parallel"
},
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
},
"engines": {
"node": ">=20.0.0",
"pnpm": ">=9.0.0"
},
"packageManager": "pnpm@9.0.0"
}
nx.json¶
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"defaultBase": "main",
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s",
"!{projectRoot}/.eslintrc.json"
],
"sharedGlobals": ["{workspaceRoot}/tsconfig.base.json"]
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"cache": true
},
"test": {
"inputs": ["default", "^default"],
"cache": true
},
"typecheck": {
"cache": true
}
},
"plugins": [
{
"plugin": "@nx/vite/plugin",
"options": {
"buildTargetName": "build",
"serveTargetName": "serve",
"previewTargetName": "preview",
"testTargetName": "test"
}
},
{
"plugin": "@nx/eslint/plugin",
"options": {
"targetName": "lint"
}
}
],
"generators": {
"@nx/react": {
"application": {
"style": "css",
"linter": "eslint",
"bundler": "vite"
},
"component": {
"style": "css"
},
"library": {
"style": "css",
"linter": "eslint"
}
}
}
}
tsconfig.base.json¶
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022", "DOM"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@forma3d/domain": ["libs/domain/src/index.ts"],
"@forma3d/api-client": ["libs/api-client/src/index.ts"],
"@forma3d/utils": ["libs/utils/src/index.ts"],
"@forma3d/config": ["libs/config/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]
}
.prettierrc¶
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
.eslintrc.json¶
{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "scope:api",
"onlyDependOnLibsWithTags": ["scope:shared"]
},
{
"sourceTag": "scope:web",
"onlyDependOnLibsWithTags": ["scope:shared"]
},
{
"sourceTag": "scope:shared",
"onlyDependOnLibsWithTags": ["scope:shared"]
}
]
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
]
}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nx/javascript"],
"rules": {}
}
]
}
.gitignore¶
# ============================================================================
# Forma3D.Connect .gitignore
# ============================================================================
# ----------------------------------------------------------------------------
# Dependencies
# ----------------------------------------------------------------------------
node_modules/
.pnpm-store/
# ----------------------------------------------------------------------------
# Build outputs
# ----------------------------------------------------------------------------
dist/
build/
out/
.next/
.nuxt/
# ----------------------------------------------------------------------------
# Nx
# ----------------------------------------------------------------------------
.nx/
.nx/cache
.nx/workspace-data
# ----------------------------------------------------------------------------
# TypeScript
# ----------------------------------------------------------------------------
*.tsbuildinfo
# ----------------------------------------------------------------------------
# Testing
# ----------------------------------------------------------------------------
coverage/
.nyc_output/
# ----------------------------------------------------------------------------
# Environment
# ----------------------------------------------------------------------------
.env
.env.local
.env.*.local
!.env.example
!.env.template
# ----------------------------------------------------------------------------
# IDE / Editor
# ----------------------------------------------------------------------------
.idea/
*.swp
*.swo
*~
# VS Code - ignore all except shared configs
.vscode/*
!.vscode/settings.json
!.vscode/extensions.json
!.vscode/launch.json
!.vscode/tasks.json
# ----------------------------------------------------------------------------
# Cursor AI
# ----------------------------------------------------------------------------
.cursor/*
!.cursor/rules/
# ----------------------------------------------------------------------------
# OS Files
# ----------------------------------------------------------------------------
.DS_Store
Thumbs.db
*.log
# ----------------------------------------------------------------------------
# Prisma
# ----------------------------------------------------------------------------
# Keep migrations, ignore generated client (it's generated on install)
# ----------------------------------------------------------------------------
# Vite
# ----------------------------------------------------------------------------
vite.config.ts.timestamp-*
# ----------------------------------------------------------------------------
# Tauri (future)
# ----------------------------------------------------------------------------
apps/desktop/src-tauri/target/
# ----------------------------------------------------------------------------
# Capacitor (future)
# ----------------------------------------------------------------------------
apps/mobile/android/
apps/mobile/ios/
# ----------------------------------------------------------------------------
# Temporary
# ----------------------------------------------------------------------------
tmp/
temp/
*.tmp
📝 README.md¶
Create a comprehensive README with detailed build, test, and run instructions:
# Forma3D.Connect
> Integration services connecting Shopify e-commerce with SimplyPrint 3D print farm management
[](https://dev.azure.com/YOUR_ORG/forma-3d-connect/_build/latest?definitionId=1&branchName=main)
## Overview
Forma3D.Connect automates the print-on-demand workflow for Forma 3D's 3D printing operations:
1. **Order Reception**: Shopify orders are received via webhooks
2. **Print Job Creation**: Orders automatically create print jobs in SimplyPrint
3. **Status Tracking**: Print job status is monitored and synced
4. **Order Fulfillment**: Completed prints trigger Shopify fulfillment
5. **Shipping**: Sendcloud generates shipping labels
---
## Tech Stack
| Layer | Technology | Version |
|-------|------------|---------|
| Frontend | React, TypeScript, Vite, Tailwind CSS | 19.x |
| Backend | NestJS, TypeScript, Socket.IO | 10.x |
| Database | PostgreSQL, Prisma ORM | 16.x / 5.x |
| Monorepo | Nx | 19.x |
| Package Manager | pnpm | 9.x |
| CI/CD | Azure DevOps Pipelines | - |
---
## Prerequisites
Before you begin, ensure you have the following installed:
| Tool | Version | Installation |
|------|---------|--------------|
| Node.js | 20.x LTS | [nodejs.org](https://nodejs.org/) or use nvm |
| pnpm | 9.x | `corepack enable && corepack prepare pnpm@9 --activate` |
| PostgreSQL | 16.x | [postgresql.org](https://www.postgresql.org/download/) or Docker |
| Docker | Latest | [docker.com](https://www.docker.com/) (optional) |
| Git | Latest | [git-scm.com](https://git-scm.com/) |
### Verify Prerequisites
```bash
# Check Node.js version
node --version # Should be v20.x.x
# Check pnpm version
pnpm --version # Should be 9.x.x
# Check PostgreSQL (if installed locally)
psql --version # Should be 16.x
# Check Docker (if using containerized database)
docker --version
Getting Started¶
1. Clone the Repository¶
# Clone via HTTPS
git clone https://YOUR_ORG@dev.azure.com/YOUR_ORG/forma-3d-connect/_git/forma-3d-connect
# Or clone via SSH
git clone git@ssh.dev.azure.com:v3/YOUR_ORG/forma-3d-connect/forma-3d-connect
# Navigate to project directory
cd forma-3d-connect
2. Install Dependencies¶
# Install all dependencies (this also generates Prisma client)
pnpm install
3. Environment Configuration¶
# Copy the example environment file
cp .env.example .env
# Edit the .env file with your configuration
# Required variables:
# - DATABASE_URL: PostgreSQL connection string
# - SHOPIFY_* : Shopify API credentials
# - SIMPLYPRINT_* : SimplyPrint API credentials
# - SENDCLOUD_* : Sendcloud API credentials
Minimum Required Environment Variables for Development:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/forma3d_connect?schema=public"
NODE_ENV="development"
APP_PORT=3000
APP_URL="http://localhost:3000"
FRONTEND_URL="http://localhost:4200"
4. Database Setup¶
Choose one of the following options:
Option A: Docker (Recommended for Development)¶
# Start PostgreSQL container
docker run --name forma3d-postgres \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=forma3d_connect \
-p 5432:5432 \
-d postgres:16
# Verify container is running
docker ps
# Run database migrations
pnpm db:migrate
# (Optional) Seed the database with test data
pnpm db:seed
Option B: Local PostgreSQL Installation¶
# Create the database
createdb forma3d_connect
# Run database migrations
pnpm db:migrate
# (Optional) Seed the database with test data
pnpm db:seed
Option C: Docker Compose (Full Stack)¶
# Create docker-compose.yml for local development
# (See Infrastructure section below for full compose file)
docker-compose up -d
pnpm db:migrate
5. Start Development Servers¶
# Start both API and Web simultaneously
pnpm dev
# API will be available at: http://localhost:3000
# Web will be available at: http://localhost:4200
Or start services individually:
# Terminal 1: Start Backend API
pnpm api:dev
# Terminal 2: Start Frontend Web
pnpm web:dev
6. Verify Installation¶
# Test API health endpoint
curl http://localhost:3000/health
# Expected: {"status":"ok","database":"connected","timestamp":"..."}
# Open frontend in browser
open http://localhost:4200
# Expected: Dashboard with "System Health: OK" indicator
Building the Application¶
Development Build¶
# Build all projects
pnpm build
# Build specific project
pnpm nx build api
pnpm nx build web
Production Build¶
# Build for production with optimizations
NODE_ENV=production pnpm build
# Build outputs are in:
# - dist/apps/api (NestJS backend)
# - dist/apps/web (React frontend)
Build Specific Libraries¶
# Build shared domain library
pnpm nx build domain
# Build all libraries
pnpm nx run-many --target=build --projects=domain,api-client,utils,config
Testing¶
Run All Tests¶
# Run all tests across the monorepo
pnpm test
# Run tests with coverage
pnpm test -- --coverage
Run Tests by Project¶
# Test backend API
pnpm nx test api
# Test frontend Web
pnpm nx test web
# Test shared libraries
pnpm nx test domain
pnpm nx test utils
Run Tests in Watch Mode¶
# Watch mode for specific project
pnpm nx test api --watch
# Watch mode for affected projects
pnpm nx affected --target=test --watch
Run E2E Tests¶
# Run end-to-end tests (requires running application)
pnpm nx e2e api-e2e
Test Coverage Report¶
# Generate coverage report
pnpm test -- --coverage
# Coverage reports are generated in:
# - coverage/apps/api/
# - coverage/apps/web/
# - coverage/libs/*/
Linting and Code Quality¶
Run Linting¶
# Lint all projects
pnpm lint
# Lint specific project
pnpm nx lint api
pnpm nx lint web
# Lint only affected projects
pnpm nx affected --target=lint
Fix Linting Errors Automatically¶
# Auto-fix linting issues
pnpm nx lint api --fix
pnpm nx lint web --fix
Format Code¶
# Check formatting
pnpm format:check
# Fix formatting
pnpm format
Type Checking¶
# Run TypeScript type checking
pnpm nx run-many --target=typecheck --all
# Type check specific project
pnpm nx typecheck api
Database Management¶
Migrations¶
# Create a new migration (after schema changes)
pnpm db:migrate
# Apply migrations to database
pnpm db:migrate:deploy
# Reset database (drop, create, migrate, seed)
pnpm prisma migrate reset
Prisma Studio¶
# Open Prisma Studio (visual database browser)
pnpm db:studio
# Opens at http://localhost:5555
Database Seeding¶
# Seed database with test data
pnpm db:seed
Generate Prisma Client¶
# Regenerate Prisma client after schema changes
pnpm prisma generate
Running in Production¶
Build for Production¶
# Build all projects for production
NODE_ENV=production pnpm build
Run Production API¶
# Navigate to API dist folder
cd dist/apps/api
# Start with Node.js
node main.js
# Or use PM2 for process management
pm2 start main.js --name forma3d-api
Serve Production Frontend¶
The frontend build output (dist/apps/web) is a static site that can be served by:
- Nginx
- Azure Static Web Apps
- Any CDN/static hosting service
Example Nginx configuration:
server {
listen 80;
server_name forma3d.example.com;
root /var/www/forma3d/web;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Infrastructure Setup¶
Docker Compose (Development)¶
Create docker-compose.yml in project root:
version: '3.8'
services:
postgres:
image: postgres:16
container_name: forma3d-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: forma3d_connect
ports:
- '5432:5432'
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 10s
timeout: 5s
retries: 5
api:
build:
context: .
dockerfile: apps/api/Dockerfile
container_name: forma3d-api
environment:
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/forma3d_connect?schema=public
NODE_ENV: development
ports:
- '3000:3000'
depends_on:
postgres:
condition: service_healthy
web:
build:
context: .
dockerfile: apps/web/Dockerfile
container_name: forma3d-web
ports:
- '4200:80'
depends_on:
- api
volumes:
postgres_data:
Azure DevOps Setup¶
- Create Pipeline:
- Go to Azure DevOps > Pipelines > New Pipeline
- Select your repository
- Choose "Existing Azure Pipelines YAML file"
-
Select
azure-pipelines.yml -
Configure Environments:
- Create
stagingandproductionenvironments in Azure DevOps -
Add approval gates for production deployments
-
Add Service Connections:
- Configure Azure service connection for deployments
- Add any required variable groups for secrets
Project Structure¶
forma-3d-connect/
├── apps/
│ ├── api/ # NestJS backend application
│ │ ├── src/
│ │ │ ├── config/ # Configuration module
│ │ │ ├── database/ # Prisma service
│ │ │ ├── health/ # Health check endpoints
│ │ │ └── main.ts # Application entry point
│ │ └── test/ # E2E tests
│ │
│ └── web/ # React 19 frontend application
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── hooks/ # Custom hooks
│ │ ├── pages/ # Page components
│ │ └── lib/ # Utilities
│ └── public/ # Static assets
│
├── libs/
│ ├── domain/ # Shared types, entities, enums
│ ├── api-client/ # Typed API client for frontend
│ ├── utils/ # Generic utilities
│ └── config/ # Shared configuration
│
├── prisma/
│ ├── schema.prisma # Database schema
│ ├── migrations/ # Database migrations
│ └── seed.ts # Seed script
│
├── .azuredevops/
│ └── pipelines/ # Azure DevOps pipeline templates
│
├── docs/ # Project documentation
│
├── azure-pipelines.yml # Main CI/CD pipeline
├── nx.json # Nx workspace configuration
├── package.json # Root package.json
├── tsconfig.base.json # Base TypeScript configuration
└── .env.example # Environment variables template
Available Scripts¶
| Script | Description |
|---|---|
pnpm dev |
Start API and Web in development mode |
pnpm api:dev |
Start only the API in development mode |
pnpm web:dev |
Start only the Web in development mode |
pnpm build |
Build all projects |
pnpm test |
Run all tests |
pnpm lint |
Lint all projects |
pnpm format |
Format all files with Prettier |
pnpm format:check |
Check formatting without fixing |
pnpm db:migrate |
Create and run database migrations |
pnpm db:migrate:deploy |
Apply migrations to database |
pnpm db:seed |
Seed the database with test data |
pnpm db:studio |
Open Prisma Studio |
Nx Commands¶
# View project graph
pnpm nx graph
# Run affected tests
pnpm nx affected --target=test
# Run affected builds
pnpm nx affected --target=build
# List all projects
pnpm nx show projects
# View project details
pnpm nx show project api
Troubleshooting¶
Common Issues¶
1. Database Connection Failed
# Check if PostgreSQL is running
docker ps # or: systemctl status postgresql
# Verify connection string in .env
echo $DATABASE_URL
# Test connection
psql $DATABASE_URL -c "SELECT 1"
2. Port Already in Use
# Find process using port 3000
lsof -i :3000
# Kill the process
kill -9 <PID>
3. Prisma Client Not Generated
# Regenerate Prisma client
pnpm prisma generate
4. Node Modules Issues
# Clean install
rm -rf node_modules
pnpm install
5. Nx Cache Issues
# Clear Nx cache
pnpm nx reset
Documentation¶
- Vision Document - Project vision and objectives
- Requirements - Functional and non-functional requirements
- Implementation Plan - Development roadmap
Contributing¶
- Create a feature branch from
develop - Make your changes
- Ensure all tests pass:
pnpm test - Ensure linting passes:
pnpm lint - Create a pull request to
develop
License¶
UNLICENSED - Proprietary
---
## ✅ Validation Checklist
After completing Phase 0, verify ALL of the following:
### Infrastructure
- [x] `pnpm install` completes without errors ✅
- [x] `pnpm nx graph` shows correct project dependencies ✅
- [x] All path aliases resolve correctly (`@forma3d/domain`, etc.) ✅
### Backend (API)
- [x] `pnpm nx serve api` starts NestJS on port 3000 ✅
- [x] `GET http://localhost:3000/health` returns `{ status: 'ok' }` ✅
- [x] Environment variables validated on startup ✅
- [x] Prisma Client connects to database ✅
### Frontend (Web)
- [x] `pnpm nx serve web` starts Vite on port 4200 ✅
- [x] Dashboard page loads with basic layout ✅
- [x] Health check hook fetches from API ✅
### Database
- [x] `pnpm db:migrate` creates all tables ✅
- [x] `pnpm db:studio` opens Prisma Studio ✅
- [x] All 8 tables exist: Order, LineItem, PrintJob, ProductMapping, AssemblyPart, EventLog, SystemConfig, Printer ✅
### Quality
- [x] `pnpm lint` passes with no errors ✅
- [x] `pnpm test` passes (at least placeholder tests) ✅
- [x] `pnpm build` completes successfully ✅
- [x] TypeScript strict mode enabled (no `any` types) ✅
### CI/CD (Azure DevOps)
- [x] `azure-pipelines.yml` exists in root ✅
- [ ] `.azuredevops/pipelines/` folder with templates exists (using simplified pipeline)
- [x] Pipeline YAML is valid and would pass (lint, test, build stages) ✅
**Status: Phase 0 COMPLETE** ✅ (Completed January 2026)
---
## 🚫 Constraints and Rules
### MUST DO
- Use TypeScript strict mode everywhere
- Use exact versions from tech stack
- Follow Nx project boundaries
- Keep files under 300 lines
- Write explicit types (no inference for function returns)
- Use DTOs for all API inputs/outputs
- Log all errors with context
### MUST NOT
- Use `any` type
- Use `@ts-ignore` or `eslint-disable`
- Put Prisma calls outside repository layer
- Import `apps` from `libs`
- Mix frontend and backend code
- Console.log in production paths
- Store secrets in code
---
## 🎬 Execution Order
Execute tasks in this order:
1. **Initialize Nx workspace** with pnpm
2. **Create apps/api** (NestJS)
3. **Create apps/web** (React + Vite)
4. **Create libs/domain** (types)
5. **Create libs/api-client**
6. **Create libs/utils**
7. **Create libs/config**
8. **Set up Prisma** with schema
9. **Configure environment** validation
10. **Create health endpoints** (API + frontend hook)
11. **Configure ESLint/Prettier**
12. **Create Azure DevOps pipeline** (`azure-pipelines.yml` + templates)
13. **Write comprehensive README** (with build/test/run instructions)
14. **Run validation checklist**
---
## 📊 Expected Output
When Phase 0 is complete, you should be able to:
```bash
# Install dependencies
pnpm install
# Start development
pnpm dev
# In another terminal, test health
curl http://localhost:3000/health
# Returns: {"status":"ok","database":"connected","timestamp":"..."}
# Open frontend
open http://localhost:4200
# Shows: Dashboard with "System Health: OK" indicator
# Run all checks
pnpm lint && pnpm test && pnpm build
# All pass
END OF PROMPT
This prompt is designed for AI-first development. The AI should execute all steps and produce a complete, working foundation that passes all validation criteria.