development-hub/bin/new-project

456 lines
13 KiB
Bash
Executable File

#!/bin/bash
#
# new-project - Create a new project in Rob's development ecosystem
#
# This script automates:
# - Creating local project directory
# - Creating Gitea repository via API
# - Setting up documentation symlinks
# - Generating project files from templates
# - Updating build-public-docs.sh
#
# Usage:
# new-project myproject --title "My Project" --tagline "Short description"
# new-project myproject # Interactive mode
set -e
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HUB_ROOT="$(dirname "$SCRIPT_DIR")"
PROJECTS_ROOT="$HOME/PycharmProjects"
PROJECT_DOCS_ROOT="$PROJECTS_ROOT/project-docs"
TEMPLATES_DIR="$HUB_ROOT/templates"
CONFIG_DIR="$HOME/.config/development-hub"
TOKEN_FILE="$CONFIG_DIR/gitea-token"
GITEA_URL="https://gitea.brrd.tech"
GITEA_SSH="ssh://git@gitea.brrd.tech:222"
GITEA_OWNER="rob"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${CYAN}[STEP]${NC} $1"; }
# Parse arguments
PROJECT_NAME=""
PROJECT_TITLE=""
PROJECT_TAGLINE=""
DRY_RUN=false
SKIP_GITEA=false
show_help() {
cat << EOF
Usage: $(basename "$0") <project-name> [options]
Create a new project in the development ecosystem.
Arguments:
project-name Name for the project (lowercase, alphanumeric, hyphens)
Options:
--title "Title" Display title (default: derived from name)
--tagline "..." Short description (prompted if not provided)
--dry-run Show what would happen without doing it
--skip-gitea Skip Gitea repo creation (for offline use)
--help, -h Show this help message
Examples:
$(basename "$0") my-tool --title "My Tool" --tagline "A useful tool"
$(basename "$0") my-tool # Interactive mode
After creation, your project will be at:
~/PycharmProjects/<project-name>/
With documentation at:
~/PycharmProjects/project-docs/docs/projects/<project-name>/
EOF
}
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
--title)
PROJECT_TITLE="$2"
shift 2
;;
--tagline)
PROJECT_TAGLINE="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
--skip-gitea)
SKIP_GITEA=true
shift
;;
--help|-h)
show_help
exit 0
;;
-*)
log_error "Unknown option: $1"
show_help
exit 1
;;
*)
if [[ -z "$PROJECT_NAME" ]]; then
PROJECT_NAME="$1"
else
log_error "Unexpected argument: $1"
exit 1
fi
shift
;;
esac
done
}
validate_name() {
local name="$1"
if [[ -z "$name" ]]; then
log_error "Project name is required"
show_help
exit 1
fi
# Check format: lowercase, alphanumeric, hyphens
if [[ ! "$name" =~ ^[a-z][a-z0-9-]*$ ]]; then
log_error "Project name must be lowercase, start with a letter, and contain only letters, numbers, and hyphens"
exit 1
fi
# Check if project already exists
if [[ -d "$PROJECTS_ROOT/$name" ]]; then
log_error "Project directory already exists: $PROJECTS_ROOT/$name"
exit 1
fi
# Check if docs already exist
if [[ -d "$PROJECT_DOCS_ROOT/docs/projects/$name" ]]; then
log_error "Documentation directory already exists: $PROJECT_DOCS_ROOT/docs/projects/$name"
exit 1
fi
}
# Derive title from name if not provided
derive_title() {
local name="$1"
# Convert hyphens to spaces and capitalize each word
echo "$name" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1'
}
prompt_for_missing() {
if [[ -z "$PROJECT_TITLE" ]]; then
local default_title
default_title=$(derive_title "$PROJECT_NAME")
read -rp "Project title [$default_title]: " PROJECT_TITLE
PROJECT_TITLE="${PROJECT_TITLE:-$default_title}"
fi
if [[ -z "$PROJECT_TAGLINE" ]]; then
read -rp "Project tagline (short description): " PROJECT_TAGLINE
if [[ -z "$PROJECT_TAGLINE" ]]; then
log_error "Tagline is required"
exit 1
fi
fi
}
load_gitea_token() {
# Check environment variable first
if [[ -n "$GITEA_TOKEN" ]]; then
log_info "Using GITEA_TOKEN from environment"
return 0
fi
# Check token file
if [[ -f "$TOKEN_FILE" ]]; then
GITEA_TOKEN=$(cat "$TOKEN_FILE")
log_info "Loaded Gitea token from $TOKEN_FILE"
return 0
fi
# Prompt user to create token
log_warn "No Gitea API token found"
echo ""
echo "To create a token:"
echo " 1. Go to $GITEA_URL/user/settings/applications"
echo " 2. Generate a new token with 'repo' scope"
echo " 3. Copy the token"
echo ""
read -rsp "Paste your Gitea API token: " GITEA_TOKEN
echo ""
if [[ -z "$GITEA_TOKEN" ]]; then
log_error "Token is required for Gitea API access"
log_info "Use --skip-gitea to skip repository creation"
exit 1
fi
# Save token for future use
mkdir -p "$CONFIG_DIR"
echo "$GITEA_TOKEN" > "$TOKEN_FILE"
chmod 600 "$TOKEN_FILE"
log_success "Token saved to $TOKEN_FILE"
}
create_gitea_repo() {
local name="$1"
local description="$2"
log_step "Creating Gitea repository: $name"
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would create repo: $GITEA_URL/$GITEA_OWNER/$name"
return 0
fi
local response
response=$(curl -s -w "\n%{http_code}" -X POST "$GITEA_URL/api/v1/user/repos" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$name\",
\"description\": \"$description\",
\"private\": false,
\"auto_init\": false
}")
local http_code
http_code=$(echo "$response" | tail -n1)
local body
body=$(echo "$response" | sed '$d')
if [[ "$http_code" == "201" ]]; then
log_success "Created repository: $GITEA_URL/$GITEA_OWNER/$name"
elif [[ "$http_code" == "409" ]]; then
log_warn "Repository already exists on Gitea"
else
log_error "Failed to create repository (HTTP $http_code)"
echo "$body" | head -5
exit 1
fi
}
create_local_project() {
local name="$1"
local project_dir="$PROJECTS_ROOT/$name"
log_step "Creating local project: $project_dir"
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would create: $project_dir"
return 0
fi
mkdir -p "$project_dir"
cd "$project_dir"
git init --quiet
git remote add origin "$GITEA_SSH/$GITEA_OWNER/$name.git"
log_success "Created local project with git"
}
apply_template() {
local template="$1"
local output="$2"
local name="$3"
local title="$4"
local tagline="$5"
local year
year=$(date +%Y)
local date
date=$(date +%Y-%m-%d)
sed -e "s|{{PROJECT_NAME}}|$name|g" \
-e "s|{{PROJECT_TITLE}}|$title|g" \
-e "s|{{PROJECT_TAGLINE}}|$tagline|g" \
-e "s|{{YEAR}}|$year|g" \
-e "s|{{DATE}}|$date|g" \
-e "s|{{GITEA_URL}}|$GITEA_URL|g" \
-e "s|{{GITEA_OWNER}}|$GITEA_OWNER|g" \
"$template" > "$output"
}
generate_project_files() {
local name="$1"
local title="$2"
local tagline="$3"
local project_dir="$PROJECTS_ROOT/$name"
log_step "Generating project files from templates"
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would generate files in $project_dir"
return 0
fi
# Generate each file from template
apply_template "$TEMPLATES_DIR/gitignore.template" "$project_dir/.gitignore" "$name" "$title" "$tagline"
apply_template "$TEMPLATES_DIR/CLAUDE.md.template" "$project_dir/CLAUDE.md" "$name" "$title" "$tagline"
apply_template "$TEMPLATES_DIR/README.md.template" "$project_dir/README.md" "$name" "$title" "$tagline"
apply_template "$TEMPLATES_DIR/pyproject.toml.template" "$project_dir/pyproject.toml" "$name" "$title" "$tagline"
log_success "Generated project files"
}
setup_documentation() {
local name="$1"
local title="$2"
local tagline="$3"
local docs_dir="$PROJECT_DOCS_ROOT/docs/projects/$name"
local project_dir="$PROJECTS_ROOT/$name"
log_step "Setting up documentation"
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would create: $docs_dir"
log_info "[DRY RUN] Would create symlink: $project_dir/docs"
return 0
fi
# Create docs directory
mkdir -p "$docs_dir"
# Generate documentation files
apply_template "$TEMPLATES_DIR/overview.md.template" "$docs_dir/overview.md" "$name" "$title" "$tagline"
apply_template "$TEMPLATES_DIR/updating-documentation.md.template" "$docs_dir/updating-documentation.md" "$name" "$title" "$tagline"
# Create symlink
ln -s "../project-docs/docs/projects/$name" "$project_dir/docs"
log_success "Created documentation with symlink"
}
update_build_script() {
local name="$1"
local title="$2"
local tagline="$3"
local build_script="$PROJECT_DOCS_ROOT/scripts/build-public-docs.sh"
log_step "Updating build-public-docs.sh"
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would add to PROJECT_CONFIG:"
log_info " PROJECT_CONFIG[\"$name\"]=\"$title|$tagline|$GITEA_OWNER|$name|$name\""
return 0
fi
# Find the last PROJECT_CONFIG line and add after it
local config_line="PROJECT_CONFIG[\"$name\"]=\"$title|$tagline|$GITEA_OWNER|$name|$name\""
# Check if already exists
if grep -q "PROJECT_CONFIG\[\"$name\"\]" "$build_script"; then
log_warn "Project already in build script"
return 0
fi
# Add after the last PROJECT_CONFIG line
sed -i "/^PROJECT_CONFIG\[\"ramble\"\]/a $config_line" "$build_script"
log_success "Added project to build script"
}
initial_commit() {
local name="$1"
local project_dir="$PROJECTS_ROOT/$name"
log_step "Creating initial commit"
if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY RUN] Would commit and push"
return 0
fi
cd "$project_dir"
git add .
git commit -m "Initial project setup
Created by development-hub/new-project script"
if [[ "$SKIP_GITEA" != true ]]; then
git push -u origin main 2>/dev/null || git push -u origin master 2>/dev/null || {
log_warn "Could not push to remote. You may need to push manually."
}
fi
log_success "Created initial commit"
}
print_summary() {
local name="$1"
local title="$2"
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} Project '$title' created successfully!${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo "Locations:"
echo " Local: $PROJECTS_ROOT/$name/"
echo " Gitea: $GITEA_URL/$GITEA_OWNER/$name"
echo " Docs: $PROJECT_DOCS_ROOT/docs/projects/$name/"
echo ""
echo "Next steps:"
echo " 1. cd $PROJECTS_ROOT/$name"
echo " 2. Start developing!"
echo ""
echo "To publish documentation:"
echo " $PROJECT_DOCS_ROOT/scripts/build-public-docs.sh $name --deploy"
echo ""
echo "Public docs will be available at:"
echo " https://pages.brrd.tech/$GITEA_OWNER/$name/"
echo ""
}
main() {
parse_args "$@"
validate_name "$PROJECT_NAME"
prompt_for_missing
echo ""
log_info "Creating project: $PROJECT_NAME"
log_info "Title: $PROJECT_TITLE"
log_info "Tagline: $PROJECT_TAGLINE"
echo ""
if [[ "$DRY_RUN" == true ]]; then
log_warn "DRY RUN - No changes will be made"
echo ""
fi
# Load Gitea token (unless skipping)
if [[ "$SKIP_GITEA" != true ]]; then
load_gitea_token
create_gitea_repo "$PROJECT_NAME" "$PROJECT_TAGLINE"
fi
create_local_project "$PROJECT_NAME"
generate_project_files "$PROJECT_NAME" "$PROJECT_TITLE" "$PROJECT_TAGLINE"
setup_documentation "$PROJECT_NAME" "$PROJECT_TITLE" "$PROJECT_TAGLINE"
update_build_script "$PROJECT_NAME" "$PROJECT_TITLE" "$PROJECT_TAGLINE"
initial_commit "$PROJECT_NAME"
if [[ "$DRY_RUN" != true ]]; then
print_summary "$PROJECT_NAME" "$PROJECT_TITLE"
fi
}
main "$@"