Files
gh-technicalpickles-pickled…/skills/working-in-monorepos/scripts/monorepo-init
2025-11-30 09:00:36 +08:00

204 lines
4.1 KiB
Bash
Executable File

#!/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