diff --git a/CLAUDE.md b/CLAUDE.md index 787d086..334ff25 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -131,6 +131,62 @@ composer test -- --filter=ClassName # Single test ./vendor/bin/pint --dirty # Format changed files ``` +## Local Package Linking + +Most packages depend on `host-uk/core` (the published name of `core-php`). Since these are private packages, `composer install` fails unless you configure local path repositories. + +### Quick Setup + +```bash +# macOS/Linux - setup all packages +./scripts/setup-local-packages.sh + +# Windows (PowerShell) +.\scripts\setup-local-packages.ps1 + +# Or setup a specific package +./scripts/setup-local-packages.sh core-tenant +``` + +### Installing Dependencies + +After running the setup script, install dependencies using the local config: + +```bash +# macOS/Linux +cd packages/core-tenant +COMPOSER=composer.local.json composer install + +# Windows (PowerShell) +cd packages\core-tenant +$env:COMPOSER='composer.local.json'; composer install +``` + +### How It Works + +The setup script generates a `composer.local.json` for each package by merging the original `composer.json` with path repositories pointing to sibling packages: + +```json +{ + "name": "host-uk/core-tenant", + "require": {"host-uk/core": "dev-main"}, + "repositories": [ + {"type": "path", "url": "../core-php", "options": {"symlink": true}}, + {"type": "path", "url": "../core-tenant", "options": {"symlink": true}} + ] +} +``` + +When Composer sees `host-uk/core` as a requirement, it resolves it to `../core-php` (symlinked) instead of trying to fetch from Packagist. + +### Packages That Need This + +Only `core-php` can be tested without local linking (it has no `host-uk/*` dependencies). All other packages require this setup: + +- core-tenant, core-admin, core-api, core-mcp, core-agentic +- core-bio, core-social, core-analytics, core-notify, core-trust, core-support +- core-commerce, core-content, core-tools, core-uptelligence, core-developer + ## Package Switching Workflow To work on a different package without navigating: @@ -176,6 +232,6 @@ Defined in `repos.yaml`: Don't add application code here. This repo only contains: - `repos.yaml` - Package registry with dependencies -- `scripts/` - Cross-platform setup scripts (install-deps, install-core) +- `scripts/` - Cross-platform setup scripts (install-deps, install-core, clone-repos, setup-local-packages) - `Makefile` - Setup orchestration - `.core/` - Workspace configuration and Claude Code integration diff --git a/scripts/setup-local-packages.ps1 b/scripts/setup-local-packages.ps1 new file mode 100644 index 0000000..893cb4f --- /dev/null +++ b/scripts/setup-local-packages.ps1 @@ -0,0 +1,118 @@ +# Setup local package linking for PHP dependency testing +# Usage: .\scripts\setup-local-packages.ps1 [package-name] +# Example: .\scripts\setup-local-packages.ps1 core-tenant + +param( + [string]$PackageName +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$RootDir = Split-Path -Parent $ScriptDir +$PackagesDir = Join-Path $RootDir "packages" + +function Write-Info { param($msg) Write-Host "[INFO] $msg" -ForegroundColor Green } +function Write-Warn { param($msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow } +function Write-Step { param($msg) Write-Host "[STEP] $msg" -ForegroundColor Cyan } + +# Path repositories to inject +$PathRepositories = @( + @{type = "path"; url = "../core-php"; options = @{symlink = $true}} + @{type = "path"; url = "../core-tenant"; options = @{symlink = $true}} + @{type = "path"; url = "../core-admin"; options = @{symlink = $true}} + @{type = "path"; url = "../core-api"; options = @{symlink = $true}} + @{type = "path"; url = "../core-mcp"; options = @{symlink = $true}} + @{type = "path"; url = "../core-agentic"; options = @{symlink = $true}} + @{type = "path"; url = "../core-commerce"; options = @{symlink = $true}} + @{type = "path"; url = "../core-content"; options = @{symlink = $true}} + @{type = "path"; url = "../core-tools"; options = @{symlink = $true}} +) + +function Setup-Package { + param([string]$pkg) + + $pkgDir = Join-Path $PackagesDir $pkg + $composerJson = Join-Path $pkgDir "composer.json" + $target = Join-Path $pkgDir "composer.local.json" + + if (-not (Test-Path $pkgDir)) { + Write-Warn "Package directory not found: $pkgDir" + return + } + + if (-not (Test-Path $composerJson)) { + Write-Warn "No composer.json found in $pkg (skipping)" + return + } + + if (Test-Path $target) { + Write-Warn "composer.local.json already exists in $pkg (skipping)" + return + } + + Write-Step "Setting up $pkg..." + + # Read original composer.json + $original = Get-Content $composerJson -Raw | ConvertFrom-Json + + # Add repositories array (path repos must come first) + $original | Add-Member -NotePropertyName "repositories" -NotePropertyValue $PathRepositories -Force + + # Write merged composer.local.json (UTF-8 without BOM for Composer compatibility) + $json = $original | ConvertTo-Json -Depth 10 + [System.IO.File]::WriteAllText($target, $json, [System.Text.UTF8Encoding]::new($false)) + + # Add to .gitignore if not already there + $gitignore = Join-Path $pkgDir ".gitignore" + if (Test-Path $gitignore) { + $content = Get-Content $gitignore -Raw + if ($content -notmatch "composer\.local\.json") { + Add-Content $gitignore "composer.local.json" + } + } else { + "composer.local.json" | Out-File $gitignore -Encoding utf8 + } + + Write-Info "Created $target" +} + +# Main +if ($PackageName) { + # Setup specific package + Setup-Package $PackageName +} else { + # Setup all packages that depend on host-uk/core + Write-Info "Setting up local package linking for all packages..." + + $packages = Get-ChildItem -Path $PackagesDir -Directory + + foreach ($dir in $packages) { + $pkg = $dir.Name + + # Skip packages that don't need it + if ($pkg -eq "core-php" -or $pkg -eq "core-template") { + continue + } + + # Check if composer.json exists and has host-uk dependencies + $composerJson = Join-Path $dir.FullName "composer.json" + if (Test-Path $composerJson) { + $content = Get-Content $composerJson -Raw + if ($content -match '"host-uk/') { + Setup-Package $pkg + } + } + } +} + +Write-Host "" +Write-Info "Done! To install dependencies with local packages:" +Write-Host "" +Write-Host " cd packages\" +Write-Host " `$env:COMPOSER='composer.local.json'; composer install" +Write-Host "" +Write-Host "Or on a single line:" +Write-Host "" +Write-Host " powershell -Command `"`$env:COMPOSER='composer.local.json'; composer install`"" +Write-Host "" diff --git a/scripts/setup-local-packages.sh b/scripts/setup-local-packages.sh new file mode 100644 index 0000000..d930b94 --- /dev/null +++ b/scripts/setup-local-packages.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# Setup local package linking for PHP dependency testing +# Usage: ./scripts/setup-local-packages.sh [package-name] +# Example: ./scripts/setup-local-packages.sh core-tenant + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +PACKAGES_DIR="$ROOT_DIR/packages" + +# Colour output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' # No colour + +info() { echo -e "${GREEN}[INFO]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +step() { echo -e "${CYAN}[STEP]${NC} $1"; } + +# Path repositories JSON to inject +REPOSITORIES='[ + {"type": "path", "url": "../core-php", "options": {"symlink": true}}, + {"type": "path", "url": "../core-tenant", "options": {"symlink": true}}, + {"type": "path", "url": "../core-admin", "options": {"symlink": true}}, + {"type": "path", "url": "../core-api", "options": {"symlink": true}}, + {"type": "path", "url": "../core-mcp", "options": {"symlink": true}}, + {"type": "path", "url": "../core-agentic", "options": {"symlink": true}}, + {"type": "path", "url": "../core-commerce", "options": {"symlink": true}}, + {"type": "path", "url": "../core-content", "options": {"symlink": true}}, + {"type": "path", "url": "../core-tools", "options": {"symlink": true}} +]' + +# Check for jq (required for JSON manipulation) +if ! command -v jq &> /dev/null; then + echo "Error: jq is required but not installed. Install it with: brew install jq" + exit 1 +fi + +# Setup function for a single package +setup_package() { + local pkg="$1" + local pkg_dir="$PACKAGES_DIR/$pkg" + local composer_json="$pkg_dir/composer.json" + local target="$pkg_dir/composer.local.json" + + if [[ ! -d "$pkg_dir" ]]; then + warn "Package directory not found: $pkg_dir" + return 1 + fi + + if [[ ! -f "$composer_json" ]]; then + warn "No composer.json found in $pkg (skipping)" + return 0 + fi + + if [[ -f "$target" ]]; then + warn "composer.local.json already exists in $pkg (skipping)" + return 0 + fi + + step "Setting up $pkg..." + + # Merge repositories into composer.json and write to composer.local.json + jq --argjson repos "$REPOSITORIES" '. + {repositories: $repos}' "$composer_json" > "$target" + + # Add to .gitignore if not already there + local gitignore="$pkg_dir/.gitignore" + if [[ -f "$gitignore" ]]; then + if ! grep -q "composer.local.json" "$gitignore"; then + echo "composer.local.json" >> "$gitignore" + fi + else + echo "composer.local.json" > "$gitignore" + fi + + info "Created $target" +} + +# Main +if [[ -n "$1" ]]; then + # Setup specific package + setup_package "$1" +else + # Setup all packages that depend on host-uk/core + info "Setting up local package linking for all packages..." + + for dir in "$PACKAGES_DIR"/*/; do + pkg="$(basename "$dir")" + + # Skip packages that don't need it (core-php has no deps, core-template is a project) + if [[ "$pkg" == "core-php" || "$pkg" == "core-template" ]]; then + continue + fi + + # Check if composer.json exists and has host-uk dependencies + if [[ -f "$dir/composer.json" ]]; then + if grep -q '"host-uk/' "$dir/composer.json"; then + setup_package "$pkg" + fi + fi + done +fi + +echo "" +info "Done! To install dependencies with local packages:" +echo "" +echo " cd packages/" +echo " COMPOSER=composer.local.json composer install" +echo "" +echo "Or use the dev:packages script if available:" +echo "" +echo " composer dev:packages" +echo ""