🔧 chore: enforce monorepo best practices with automated validation

Fix critical issues and add validation to prevent future violations:

**Fixes:**
- Remove turbo recursion in 5 app packages (infinite loop risk)
- Add "private": true to 11 packages (prevent accidental publishing)
- Rename @mana-core/nestjs-integration → @manacore/nestjs-integration
- Remove prepublishOnly scripts from 3 private packages

**New:**
- Add scripts/validate-monorepo.mjs with 4 critical checks
- Add validate:monorepo command to package.json
- Integrate validation into CI pipeline (.github/workflows/ci.yml)
- Document validation in CLAUDE.md

All 80 package.json files now pass validation 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Wuesteon 2025-12-25 17:57:00 +01:00
parent 079b55a796
commit 5b7d3c649b
29 changed files with 304 additions and 25 deletions

View file

@ -51,6 +51,9 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Validate monorepo best practices
run: pnpm run validate:monorepo
- name: Type check
run: pnpm run type-check

View file

@ -149,12 +149,41 @@ Generated from `.env.development` via `pnpm setup:env` (auto-runs after install)
pnpm install # Install dependencies
pnpm dev:{app}:full # Start app with DB setup
pnpm type-check # Type check all packages
pnpm lint # Lint all packages
pnpm format # Format code
pnpm build # Build all packages
pnpm docker:up # Start local infrastructure
pnpm setup:env # Regenerate .env files
pnpm validate:monorepo # Validate monorepo best practices
```
## Validation & CI
### Monorepo Best Practices Validation
The `validate:monorepo` command checks for common monorepo issues:
```bash
pnpm validate:monorepo
```
**What it checks:**
1. **No Turborepo recursion** - Ensures child packages don't have `turbo run` commands (prevents infinite loops)
2. **Private packages** - All internal packages in `packages/` and `services/` have `"private": true`
3. **Workspace protocol** - All internal dependencies use `workspace:*` (no hardcoded versions)
4. **No obsolete scripts** - Warns about `prepublishOnly` in private packages
**When it runs:**
- Locally: `pnpm validate:monorepo`
- CI/CD: Automatically on every PR (`.github/workflows/ci.yml`)
**Example output:**
```
✅ All checks passed! Monorepo follows best practices.
```
This prevents issues before they reach production! 🛡️
## Documentation
- **Code Patterns:** [.claude/GUIDELINES.md](.claude/GUIDELINES.md) - Detailed technical guidelines

View file

@ -4,7 +4,7 @@
"private": true,
"description": "Calendar App - Personal and Shared Calendars with CalDAV/iCal Sync",
"scripts": {
"dev": "turbo run dev",
"dev": "pnpm run --filter=@calendar/* --parallel dev",
"dev:backend": "pnpm --filter @calendar/backend dev",
"dev:web": "pnpm --filter @calendar/web dev",
"dev:landing": "pnpm --filter @calendar/landing dev",

View file

@ -4,6 +4,6 @@
"private": true,
"description": "Chat project - AI chat application with mobile, web and landing",
"scripts": {
"dev": "turbo run dev"
"dev": "pnpm run --filter=@chat/* --parallel dev"
}
}

View file

@ -4,7 +4,7 @@
"private": true,
"description": "Contacts App - Contact Management with Manacore Integration",
"scripts": {
"dev": "turbo run dev",
"dev": "pnpm run --filter=@contacts/* --parallel dev",
"dev:backend": "pnpm --filter @contacts/backend dev",
"dev:web": "pnpm --filter @contacts/web dev",
"dev:landing": "pnpm --filter @contacts/landing dev",

View file

@ -3,6 +3,6 @@
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "turbo run dev"
"dev": "pnpm --filter @context/mobile dev"
}
}

View file

@ -21,7 +21,7 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@mana-core/nestjs-integration": "workspace:*",
"@manacore/nestjs-integration": "workspace:*",
"@manacore/shared-errors": "workspace:*",
"@google/genai": "^1.14.0",
"@manacore/manadeck-database": "workspace:*",

View file

@ -3,7 +3,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
import { ClsModule } from 'nestjs-cls';
import { TerminusModule } from '@nestjs/terminus';
import { HttpModule } from '@nestjs/axios';
import { ManaCoreModule } from '@mana-core/nestjs-integration';
import { ManaCoreModule } from '@manacore/nestjs-integration';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ApiController } from './controllers/api.controller';

View file

@ -11,9 +11,9 @@ import {
Logger,
BadRequestException,
} from '@nestjs/common';
import { AuthGuard } from '@mana-core/nestjs-integration/guards';
import { CurrentUser } from '@mana-core/nestjs-integration/decorators';
import { CreditClientService } from '@mana-core/nestjs-integration';
import { AuthGuard } from '@manacore/nestjs-integration/guards';
import { CurrentUser } from '@manacore/nestjs-integration/decorators';
import { CreditClientService } from '@manacore/nestjs-integration';
import { isOk, CreditError, ServiceError } from '@manacore/shared-errors';
import {
CreditOperationType,

View file

@ -1,7 +1,7 @@
import { Controller, Get } from '@nestjs/common';
import { HealthCheckService, HttpHealthIndicator, HealthCheck } from '@nestjs/terminus';
import { ConfigService } from '@nestjs/config';
import { Public } from '@mana-core/nestjs-integration/decorators';
import { Public } from '@manacore/nestjs-integration/decorators';
@Controller('health')
export class HealthController {

View file

@ -1,6 +1,6 @@
import { Controller, Get, UseGuards, Query, Logger } from '@nestjs/common';
import { OptionalAuthGuard } from '@mana-core/nestjs-integration/guards';
import { CurrentUser, Public } from '@mana-core/nestjs-integration/decorators';
import { OptionalAuthGuard } from '@manacore/nestjs-integration/guards';
import { CurrentUser, Public } from '@manacore/nestjs-integration/decorators';
import { DeckRepository, UserStatsRepository, DeckTemplateRepository } from '../database';
@Controller('public')

View file

@ -19,7 +19,7 @@
},
"dependencies": {
"@aws-sdk/client-s3": "^3.700.0",
"@mana-core/nestjs-integration": "workspace:*",
"@manacore/nestjs-integration": "workspace:*",
"@manacore/shared-errors": "workspace:*",
"@manacore/shared-nestjs-auth": "workspace:*",
"@manacore/shared-nestjs-cors": "workspace:*",

View file

@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ManaCoreModule } from '@mana-core/nestjs-integration';
import { ManaCoreModule } from '@manacore/nestjs-integration';
import { DatabaseModule } from './db/database.module';
import { HealthModule } from './health/health.module';
import { ModelModule } from './model/model.module';

View file

@ -9,7 +9,7 @@ import {
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { eq } from 'drizzle-orm';
import { CreditClientService } from '@mana-core/nestjs-integration';
import { CreditClientService } from '@manacore/nestjs-integration';
import { DATABASE_CONNECTION } from '../db/database.module';
import { Database } from '../db/connection';
import { imageGenerations, images, models } from '../db/schema';

View file

@ -4,13 +4,10 @@
"private": true,
"description": "Todo App - Task Management for ManaCore Ecosystem",
"scripts": {
"dev": "turbo run dev",
"dev": "pnpm run --filter=@todo/* --parallel dev",
"dev:backend": "pnpm --filter @todo/backend dev",
"dev:web": "pnpm --filter @todo/web dev",
"dev:landing": "pnpm --filter @todo/landing dev",
"build": "turbo run build",
"lint": "turbo run lint",
"clean": "turbo run clean",
"db:push": "pnpm --filter @todo/backend db:push",
"db:studio": "pnpm --filter @todo/backend db:studio",
"db:seed": "pnpm --filter @todo/backend db:seed"

View file

@ -16,6 +16,7 @@
"clean": "turbo run clean",
"format": "prettier --config .prettierrc.json --write \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",
"format:check": "prettier --config .prettierrc.json --check \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",
"validate:monorepo": "node scripts/validate-monorepo.mjs",
"validate:runtime-config": "node scripts/validate-runtime-config.mjs",
"svelte-check": "./scripts/svelte-check-staged.sh",
"build:check": "./scripts/build-check-staged.sh",
@ -160,8 +161,8 @@
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {
"@mana-core/nestjs-integration>@nestjs/common": "^11.0.0",
"@mana-core/nestjs-integration>@nestjs/core": "^11.0.0",
"@manacore/nestjs-integration>@nestjs/common": "^11.0.0",
"@manacore/nestjs-integration>@nestjs/core": "^11.0.0",
"react-native>react": ">=18.0.0",
"react-native>@types/react": ">=18.0.0",
"@sveltejs/vite-plugin-svelte>vite": ">=6.0.0",

View file

@ -1,6 +1,7 @@
{
"name": "@manacore/better-auth-types",
"version": "1.0.0",
"private": true,
"description": "Centralized Better Auth type definitions for the Mana Core monorepo",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View file

@ -1,6 +1,7 @@
{
"name": "@mana-core/nestjs-integration",
"name": "@manacore/nestjs-integration",
"version": "1.0.0",
"private": true,
"description": "NestJS integration package for Mana Core authentication and credits",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View file

@ -1,6 +1,7 @@
{
"name": "@manacore/shared-auth-stores",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
".": {

View file

@ -1,6 +1,7 @@
{
"name": "@manacore/shared-auth-ui",
"version": "1.0.0",
"private": true,
"description": "Shared authentication UI components for Mana apps",
"type": "module",
"svelte": "./src/index.ts",

View file

@ -1,6 +1,7 @@
{
"name": "@manacore/shared-auth",
"version": "0.1.0",
"private": true,
"description": "Shared authentication utilities for Manacore apps",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View file

@ -1,6 +1,7 @@
{
"name": "@manacore/shared-credit-service",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
".": {

View file

@ -1,6 +1,7 @@
{
"name": "@manacore/shared-error-tracking",
"version": "1.0.0",
"private": true,
"description": "Centralized error tracking for ManaCore applications - NestJS and frontend clients",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -25,7 +26,6 @@
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"prepublishOnly": "pnpm build",
"lint": "eslint .",
"type-check": "tsc --noEmit"
},

View file

@ -1,6 +1,7 @@
{
"name": "@manacore/shared-landing-ui",
"version": "0.1.0",
"private": true,
"description": "Shared Astro landing page components for Manacore monorepo",
"type": "module",
"exports": {

View file

@ -1,13 +1,13 @@
{
"name": "@manacore/shared-nestjs-auth",
"version": "1.0.0",
"private": true,
"description": "Shared authentication utilities for NestJS backends - JWT validation via Mana Core Auth",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"prepublishOnly": "pnpm build",
"lint": "eslint .",
"type-check": "tsc --noEmit"
},

View file

@ -18,7 +18,6 @@
"build": "tsc",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist",
"prepublishOnly": "pnpm build",
"lint": "eslint ."
},
"dependencies": {

View file

@ -1,6 +1,7 @@
{
"name": "@manacore/shared-tags",
"version": "0.1.0",
"private": true,
"description": "Shared tags client for Manacore apps - connects to central tags service",
"main": "dist/index.js",
"types": "dist/index.d.ts",

241
scripts/validate-monorepo.mjs Executable file
View file

@ -0,0 +1,241 @@
#!/usr/bin/env node
/**
* Validate Monorepo Best Practices
*
* Checks:
* 1. No "turbo run" commands in child package.json files (prevents infinite loops)
* 2. All internal packages have "private": true
* 3. All internal dependencies use "workspace:*" protocol
* 4. No prepublishOnly scripts in private packages
*/
import { readFileSync, readdirSync, statSync } from 'fs';
import { join, relative } from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const MONOREPO_ROOT = join(__dirname, '..');
const errors = [];
const warnings = [];
// Colors for terminal output
const RED = '\x1b[31m';
const YELLOW = '\x1b[33m';
const GREEN = '\x1b[32m';
const RESET = '\x1b[0m';
/**
* Find all package.json files in the monorepo
*/
function findPackageJsonFiles(dir, files = []) {
const entries = readdirSync(dir);
for (const entry of entries) {
const fullPath = join(dir, entry);
const stat = statSync(fullPath);
// Skip node_modules and hidden directories
if (entry === 'node_modules' || entry.startsWith('.')) {
continue;
}
if (stat.isDirectory()) {
findPackageJsonFiles(fullPath, files);
} else if (entry === 'package.json') {
files.push(fullPath);
}
}
return files;
}
/**
* Check if a package.json has turbo run commands (infinite loop risk)
*/
function checkTurboRecursion(packagePath, packageJson) {
const relativePath = relative(MONOREPO_ROOT, packagePath);
// Skip root package.json
if (relativePath === 'package.json') {
return;
}
// Check if package is in apps/, games/, packages/, or services/
const isChildPackage =
relativePath.startsWith('apps/') ||
relativePath.startsWith('games/') ||
relativePath.startsWith('packages/') ||
relativePath.startsWith('services/');
if (!isChildPackage) {
return;
}
const scripts = packageJson.scripts || {};
for (const [scriptName, scriptCommand] of Object.entries(scripts)) {
if (typeof scriptCommand === 'string' && scriptCommand.includes('turbo run')) {
// Exception: "dev" script with turbo run is sometimes OK if it filters correctly
if (scriptName === 'dev' && scriptCommand.includes('--filter')) {
warnings.push(
`⚠️ ${relativePath}: "dev" script uses "turbo run" with --filter. Make sure it doesn't create infinite loops.`
);
} else {
errors.push(
`${relativePath}: "${scriptName}" script contains "turbo run" which can cause infinite loops. Remove it and let root turbo.json handle orchestration.`
);
}
}
}
}
/**
* Check if all internal packages are marked private
*/
function checkPrivateFlag(packagePath, packageJson) {
const relativePath = relative(MONOREPO_ROOT, packagePath);
// Skip root
if (relativePath === 'package.json') {
return;
}
// Only check packages in packages/ and services/
const isInternalPackage =
relativePath.startsWith('packages/') || relativePath.startsWith('services/');
if (!isInternalPackage) {
return;
}
if (packageJson.private !== true) {
errors.push(
`${relativePath}: Missing "private": true. All internal packages should be private to prevent accidental npm publishing.`
);
}
}
/**
* Check if all internal dependencies use workspace protocol
*/
function checkWorkspaceProtocol(packagePath, packageJson) {
const relativePath = relative(MONOREPO_ROOT, packagePath);
// Skip root
if (relativePath === 'package.json') {
return;
}
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
const internalScopes = [
'@manacore/',
'@mana-core/',
'@chat/',
'@picture/',
'@calendar/',
'@contacts/',
'@todo/',
'@manadeck/',
'@zitare/',
'@voxel-lava/',
'@mana-games/',
'@figgos/',
'@worldream/',
'@context/',
];
for (const [depName, depVersion] of Object.entries(deps)) {
// Check if dependency is internal
const isInternal = internalScopes.some((scope) => depName.startsWith(scope));
if (isInternal && !depVersion.startsWith('workspace:')) {
errors.push(
`${relativePath}: Dependency "${depName}" should use "workspace:*" instead of "${depVersion}"`
);
}
}
}
/**
* Check for prepublishOnly scripts in private packages
*/
function checkPrepublishOnlyScripts(packagePath, packageJson) {
const relativePath = relative(MONOREPO_ROOT, packagePath);
// Skip root
if (relativePath === 'package.json') {
return;
}
// Only check private packages
if (packageJson.private !== true) {
return;
}
const scripts = packageJson.scripts || {};
if (scripts.prepublishOnly) {
warnings.push(
`⚠️ ${relativePath}: Has "prepublishOnly" script but is marked private. This script won't execute. Consider removing it.`
);
}
}
/**
* Main validation function
*/
function validateMonorepo() {
console.log('🔍 Validating Monorepo Best Practices...\n');
const packageJsonFiles = findPackageJsonFiles(MONOREPO_ROOT);
for (const packagePath of packageJsonFiles) {
try {
const content = readFileSync(packagePath, 'utf-8');
const packageJson = JSON.parse(content);
checkTurboRecursion(packagePath, packageJson);
checkPrivateFlag(packagePath, packageJson);
checkWorkspaceProtocol(packagePath, packageJson);
checkPrepublishOnlyScripts(packagePath, packageJson);
} catch (error) {
errors.push(`❌ Failed to parse ${relative(MONOREPO_ROOT, packagePath)}: ${error.message}`);
}
}
// Print results
console.log(`\n📊 Validation Results:`);
console.log(` Checked ${packageJsonFiles.length} package.json files\n`);
if (errors.length === 0 && warnings.length === 0) {
console.log(`${GREEN}✅ All checks passed! Monorepo follows best practices.${RESET}\n`);
process.exit(0);
}
if (errors.length > 0) {
console.log(`${RED}❌ Found ${errors.length} error(s):${RESET}\n`);
errors.forEach((error) => console.log(` ${error}`));
console.log('');
}
if (warnings.length > 0) {
console.log(`${YELLOW}⚠️ Found ${warnings.length} warning(s):${RESET}\n`);
warnings.forEach((warning) => console.log(` ${warning}`));
console.log('');
}
if (errors.length > 0) {
console.log(`${RED}❌ Validation failed. Please fix the errors above.${RESET}\n`);
process.exit(1);
} else {
console.log(`${GREEN}✅ Validation passed with warnings. Consider fixing them.${RESET}\n`);
process.exit(0);
}
}
// Run validation
validateMonorepo();

View file

@ -1,6 +1,7 @@
{
"name": "mana-core-auth",
"version": "0.1.0",
"private": true,
"description": "Mana Core Authentication and Credit System",
"main": "dist/main.js",
"scripts": {