#!/usr/bin/env bash set -euo pipefail # monorepo-init: Auto-detect subprojects and generate .monorepo.json # # Usage: # bin/monorepo-init # Output JSON to stdout # bin/monorepo-init --dry-run # Same as above # bin/monorepo-init --write # Write to .monorepo.json show_usage() { cat << EOF Usage: monorepo-init [OPTIONS] Auto-detect subprojects and generate .monorepo.json OPTIONS: --dry-run Output JSON to stdout (default) --write Write to .monorepo.json -h, --help Show this help DETECTION: Scans for package manager artifacts: - package.json (Node) - Gemfile (Ruby) - go.mod (Go) - pyproject.toml, setup.py, requirements.txt (Python) - Cargo.toml (Rust) - build.gradle, pom.xml (Java) EXAMPLES: bin/monorepo-init --dry-run bin/monorepo-init | jq . bin/monorepo-init --write EOF } # Parse arguments MODE="dry-run" while [[ $# -gt 0 ]]; do case $1 in --write) MODE="write" shift ;; --dry-run) MODE="dry-run" shift ;; -h | --help) show_usage exit 0 ;; *) echo "Unknown option: $1" >&2 show_usage >&2 exit 1 ;; esac done # Find repo root find_repo_root() { git rev-parse --show-toplevel 2> /dev/null || { echo "Error: Not in a git repository" >&2 exit 1 } } # Detect subproject type from artifacts detect_type() { local dir="$1" if [[ -f "$dir/package.json" ]]; then echo "node" elif [[ -f "$dir/Gemfile" ]]; then echo "ruby" elif [[ -f "$dir/go.mod" ]]; then echo "go" elif [[ -f "$dir/pyproject.toml" ]] || [[ -f "$dir/setup.py" ]] || [[ -f "$dir/requirements.txt" ]]; then echo "python" elif [[ -f "$dir/Cargo.toml" ]]; then echo "rust" elif [[ -f "$dir/build.gradle" ]] || [[ -f "$dir/pom.xml" ]]; then echo "java" else echo "unknown" fi } # Find all artifact files find_artifacts() { local root="$1" # Check if fd is available (faster) if command -v fd &> /dev/null; then fd -t f '(package\.json|Gemfile|go\.mod|pyproject\.toml|setup\.py|requirements\.txt|Cargo\.toml|build\.gradle|pom\.xml)$' "$root" else # Fallback to find find "$root" -type f \( \ -name 'package.json' -o \ -name 'Gemfile' -o \ -name 'go.mod' -o \ -name 'pyproject.toml' -o \ -name 'setup.py' -o \ -name 'requirements.txt' -o \ -name 'Cargo.toml' -o \ -name 'build.gradle' -o \ -name 'pom.xml' \ \) fi } # Generate JSON structure generate_json() { local root="$1" local subprojects="$2" # newline-separated list of "id:path:type" # Start JSON cat << EOF { "root": "$root", "subprojects": { EOF # Add each subproject local first=true while IFS=: read -r id path type; do if [[ "$first" == true ]]; then first=false else echo "," fi cat << EOF "$id": { "path": "$path", "type": "$type" } EOF done <<< "$subprojects" # Close JSON cat << EOF } } EOF } # Main logic REPO_ROOT=$(find_repo_root) cd "$REPO_ROOT" # Find all artifacts and group by directory declare -A seen_dirs SUBPROJECTS="" while read -r artifact_path; do dir=$(dirname "$artifact_path") # Skip if we've seen this directory [[ -n "${seen_dirs[$dir]:-}" ]] && continue seen_dirs[$dir]=1 # Generate subproject ID (relative path with / → -) rel_path="${dir#$REPO_ROOT/}" if [[ "$rel_path" == "$REPO_ROOT" ]] || [[ "$rel_path" == "." ]]; then id="root" rel_path="." else id="${rel_path//\//-}" fi # Detect type type=$(detect_type "$dir") # Add to list SUBPROJECTS+="$id:$rel_path:$type"$'\n' done < <(find_artifacts "$REPO_ROOT") # Generate JSON JSON=$(generate_json "$REPO_ROOT" "$SUBPROJECTS") # Output based on mode case "$MODE" in dry-run) echo "$JSON" ;; write) if [[ -f ".monorepo.json" ]]; then echo "Warning: .monorepo.json already exists" >&2 echo "Overwrite? (y/N) " >&2 read -r response if [[ ! "$response" =~ ^[Yy]$ ]]; then echo "Aborted" >&2 exit 1 fi fi echo "$JSON" > .monorepo.json echo "Written to .monorepo.json" >&2 ;; esac