#!/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_CLONE_URL="https://gitea.brrd.tech" # Use HTTPS for cloning (SSH port 222 often unavailable) 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") [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// With documentation at: ~/PycharmProjects/project-docs/docs/projects// 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_CLONE_URL/$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_sidebar() { local name="$1" local title="$2" local sidebar_file="$PROJECT_DOCS_ROOT/sidebars.ts" log_step "Updating sidebars.ts" if [[ "$DRY_RUN" == true ]]; then log_info "[DRY RUN] Would add '$title' to sidebar" return 0 fi # Check if already exists if grep -q "projects/$name/overview" "$sidebar_file"; then log_warn "Project already in sidebar" return 0 fi # Create the new sidebar entry local sidebar_entry=" { type: 'category', label: '$title', collapsed: true, items: [ 'projects/$name/overview', 'projects/$name/updating-documentation', ], }," # Insert before "Goals & Roadmap" category # Use perl for multi-line insertion (more reliable than sed) perl -i -p0e "s|(\\s+\\{\\s+type: 'category',\\s+label: 'Goals & Roadmap',)|$sidebar_entry\n\$1|s" "$sidebar_file" log_success "Added project to sidebar" } 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_sidebar "$PROJECT_NAME" "$PROJECT_TITLE" 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 "$@"