Add setup wizard and configurable git hosting
- First-run setup wizard prompts for projects dir and git hosting - Git Hosting section in Settings dialog (provider, URL, owner, token) - Supports GitHub, GitLab, and Gitea - new-project script reads from ~/.config/development-hub/settings.json - Environment variables still work as fallback (GITEA_URL, GITEA_TOKEN, etc.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
095e185364
commit
46c487cf1b
169
bin/new-project
169
bin/new-project
|
|
@ -18,15 +18,38 @@ set -e
|
||||||
# Configuration
|
# Configuration
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
HUB_ROOT="$(dirname "$SCRIPT_DIR")"
|
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"
|
CONFIG_DIR="$HOME/.config/development-hub"
|
||||||
|
SETTINGS_FILE="$CONFIG_DIR/settings.json"
|
||||||
TOKEN_FILE="$CONFIG_DIR/gitea-token"
|
TOKEN_FILE="$CONFIG_DIR/gitea-token"
|
||||||
|
TEMPLATES_DIR="$HUB_ROOT/templates"
|
||||||
|
|
||||||
GITEA_URL="https://gitea.brrd.tech"
|
# Load settings from JSON file if it exists
|
||||||
GITEA_CLONE_URL="https://gitea.brrd.tech" # Use HTTPS for cloning (SSH port 222 often unavailable)
|
load_settings() {
|
||||||
GITEA_OWNER="rob"
|
if [[ -f "$SETTINGS_FILE" ]]; then
|
||||||
|
# Use python to parse JSON (more portable than jq)
|
||||||
|
PROJECTS_ROOT=$(python3 -c "import json; d=json.load(open('$SETTINGS_FILE')); print(d.get('default_project_path', '$HOME/PycharmProjects'))" 2>/dev/null || echo "$HOME/PycharmProjects")
|
||||||
|
GIT_HOST_TYPE=$(python3 -c "import json; d=json.load(open('$SETTINGS_FILE')); print(d.get('git_host_type', ''))" 2>/dev/null || echo "")
|
||||||
|
GIT_HOST_URL=$(python3 -c "import json; d=json.load(open('$SETTINGS_FILE')); print(d.get('git_host_url', ''))" 2>/dev/null || echo "")
|
||||||
|
GIT_HOST_OWNER=$(python3 -c "import json; d=json.load(open('$SETTINGS_FILE')); print(d.get('git_host_owner', ''))" 2>/dev/null || echo "")
|
||||||
|
GIT_HOST_TOKEN=$(python3 -c "import json; d=json.load(open('$SETTINGS_FILE')); print(d.get('git_host_token', ''))" 2>/dev/null || echo "")
|
||||||
|
else
|
||||||
|
PROJECTS_ROOT="${PROJECTS_ROOT:-$HOME/PycharmProjects}"
|
||||||
|
GIT_HOST_TYPE=""
|
||||||
|
GIT_HOST_URL=""
|
||||||
|
GIT_HOST_OWNER=""
|
||||||
|
GIT_HOST_TOKEN=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Allow environment variables to override
|
||||||
|
PROJECTS_ROOT="${PROJECTS_ROOT_OVERRIDE:-$PROJECTS_ROOT}"
|
||||||
|
GIT_HOST_URL="${GITEA_URL:-$GIT_HOST_URL}"
|
||||||
|
GIT_HOST_OWNER="${GITEA_OWNER:-$GIT_HOST_OWNER}"
|
||||||
|
GIT_HOST_TOKEN="${GITEA_TOKEN:-$GIT_HOST_TOKEN}"
|
||||||
|
|
||||||
|
PROJECT_DOCS_ROOT="$PROJECTS_ROOT/project-docs"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_settings
|
||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
|
|
@ -176,75 +199,121 @@ prompt_for_missing() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
load_gitea_token() {
|
load_git_token() {
|
||||||
# Check environment variable first
|
# Already loaded from settings?
|
||||||
if [[ -n "$GITEA_TOKEN" ]]; then
|
if [[ -n "$GIT_HOST_TOKEN" ]]; then
|
||||||
log_info "Using GITEA_TOKEN from environment"
|
log_info "Using token from settings"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check token file
|
# Check legacy token file
|
||||||
if [[ -f "$TOKEN_FILE" ]]; then
|
if [[ -f "$TOKEN_FILE" ]]; then
|
||||||
GITEA_TOKEN=$(cat "$TOKEN_FILE")
|
GIT_HOST_TOKEN=$(cat "$TOKEN_FILE")
|
||||||
log_info "Loaded Gitea token from $TOKEN_FILE"
|
log_info "Loaded token from $TOKEN_FILE"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check if git hosting is configured
|
||||||
|
if [[ -z "$GIT_HOST_URL" ]] || [[ -z "$GIT_HOST_OWNER" ]]; then
|
||||||
|
log_error "Git hosting not configured"
|
||||||
|
echo ""
|
||||||
|
echo "Please configure git hosting in Development Hub:"
|
||||||
|
echo " 1. Run 'development-hub'"
|
||||||
|
echo " 2. Go to Settings"
|
||||||
|
echo " 3. Configure Git Hosting section"
|
||||||
|
echo ""
|
||||||
|
log_info "Or use --skip-gitea to skip repository creation"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Prompt user to create token
|
# Prompt user to create token
|
||||||
log_warn "No Gitea API token found"
|
log_warn "No API token found"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To create a token:"
|
echo "To create a token:"
|
||||||
echo " 1. Go to $GITEA_URL/user/settings/applications"
|
if [[ "$GIT_HOST_TYPE" == "github" ]]; then
|
||||||
echo " 2. Generate a new token with 'repo' scope"
|
echo " 1. Go to https://github.com/settings/tokens"
|
||||||
|
echo " 2. Generate a new token with 'repo' scope"
|
||||||
|
elif [[ "$GIT_HOST_TYPE" == "gitlab" ]]; then
|
||||||
|
echo " 1. Go to $GIT_HOST_URL/-/profile/personal_access_tokens"
|
||||||
|
echo " 2. Generate a new token with 'api' scope"
|
||||||
|
else
|
||||||
|
echo " 1. Go to $GIT_HOST_URL/user/settings/applications"
|
||||||
|
echo " 2. Generate a new token with 'repo' scope"
|
||||||
|
fi
|
||||||
echo " 3. Copy the token"
|
echo " 3. Copy the token"
|
||||||
echo ""
|
echo ""
|
||||||
read -rsp "Paste your Gitea API token: " GITEA_TOKEN
|
read -rsp "Paste your API token: " GIT_HOST_TOKEN
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [[ -z "$GITEA_TOKEN" ]]; then
|
if [[ -z "$GIT_HOST_TOKEN" ]]; then
|
||||||
log_error "Token is required for Gitea API access"
|
log_error "Token is required for API access"
|
||||||
log_info "Use --skip-gitea to skip repository creation"
|
log_info "Use --skip-gitea to skip repository creation"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Save token for future use
|
# Save token for future use
|
||||||
mkdir -p "$CONFIG_DIR"
|
mkdir -p "$CONFIG_DIR"
|
||||||
echo "$GITEA_TOKEN" > "$TOKEN_FILE"
|
echo "$GIT_HOST_TOKEN" > "$TOKEN_FILE"
|
||||||
chmod 600 "$TOKEN_FILE"
|
chmod 600 "$TOKEN_FILE"
|
||||||
log_success "Token saved to $TOKEN_FILE"
|
log_success "Token saved to $TOKEN_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
create_gitea_repo() {
|
create_git_repo() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
local description="$2"
|
local description="$2"
|
||||||
|
|
||||||
log_step "Creating Gitea repository: $name"
|
log_step "Creating repository: $name on $GIT_HOST_TYPE"
|
||||||
|
|
||||||
if [[ "$DRY_RUN" == true ]]; then
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
log_info "[DRY RUN] Would create repo: $GITEA_URL/$GITEA_OWNER/$name"
|
log_info "[DRY RUN] Would create repo: $GIT_HOST_URL/$GIT_HOST_OWNER/$name"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local response
|
local response http_code body
|
||||||
response=$(curl -s -w "\n%{http_code}" -X POST "$GITEA_URL/api/v1/user/repos" \
|
|
||||||
-H "Authorization: token $GITEA_TOKEN" \
|
if [[ "$GIT_HOST_TYPE" == "github" ]]; then
|
||||||
-H "Content-Type: application/json" \
|
# GitHub API
|
||||||
-d "{
|
response=$(curl -s -w "\n%{http_code}" -X POST "https://api.github.com/user/repos" \
|
||||||
\"name\": \"$name\",
|
-H "Authorization: token $GIT_HOST_TOKEN" \
|
||||||
\"description\": \"$description\",
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
\"private\": false,
|
-H "Content-Type: application/json" \
|
||||||
\"auto_init\": false
|
-d "{
|
||||||
}")
|
\"name\": \"$name\",
|
||||||
|
\"description\": \"$description\",
|
||||||
|
\"private\": false,
|
||||||
|
\"auto_init\": false
|
||||||
|
}")
|
||||||
|
elif [[ "$GIT_HOST_TYPE" == "gitlab" ]]; then
|
||||||
|
# GitLab API
|
||||||
|
response=$(curl -s -w "\n%{http_code}" -X POST "$GIT_HOST_URL/api/v4/projects" \
|
||||||
|
-H "PRIVATE-TOKEN: $GIT_HOST_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"name\": \"$name\",
|
||||||
|
\"description\": \"$description\",
|
||||||
|
\"visibility\": \"public\",
|
||||||
|
\"initialize_with_readme\": false
|
||||||
|
}")
|
||||||
|
else
|
||||||
|
# Gitea API (default)
|
||||||
|
response=$(curl -s -w "\n%{http_code}" -X POST "$GIT_HOST_URL/api/v1/user/repos" \
|
||||||
|
-H "Authorization: token $GIT_HOST_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"name\": \"$name\",
|
||||||
|
\"description\": \"$description\",
|
||||||
|
\"private\": false,
|
||||||
|
\"auto_init\": false
|
||||||
|
}")
|
||||||
|
fi
|
||||||
|
|
||||||
local http_code
|
|
||||||
http_code=$(echo "$response" | tail -n1)
|
http_code=$(echo "$response" | tail -n1)
|
||||||
local body
|
|
||||||
body=$(echo "$response" | sed '$d')
|
body=$(echo "$response" | sed '$d')
|
||||||
|
|
||||||
if [[ "$http_code" == "201" ]]; then
|
if [[ "$http_code" == "201" ]]; then
|
||||||
log_success "Created repository: $GITEA_URL/$GITEA_OWNER/$name"
|
log_success "Created repository: $GIT_HOST_URL/$GIT_HOST_OWNER/$name"
|
||||||
elif [[ "$http_code" == "409" ]]; then
|
elif [[ "$http_code" == "409" ]] || [[ "$http_code" == "422" ]]; then
|
||||||
log_warn "Repository already exists on Gitea"
|
log_warn "Repository already exists"
|
||||||
else
|
else
|
||||||
log_error "Failed to create repository (HTTP $http_code)"
|
log_error "Failed to create repository (HTTP $http_code)"
|
||||||
echo "$body" | head -5
|
echo "$body" | head -5
|
||||||
|
|
@ -267,7 +336,7 @@ create_local_project() {
|
||||||
cd "$project_dir"
|
cd "$project_dir"
|
||||||
|
|
||||||
git init --quiet
|
git init --quiet
|
||||||
git remote add origin "$GITEA_CLONE_URL/$GITEA_OWNER/$name.git"
|
git remote add origin "$GIT_HOST_URL/$GIT_HOST_OWNER/$name.git"
|
||||||
|
|
||||||
log_success "Created local project with git"
|
log_success "Created local project with git"
|
||||||
}
|
}
|
||||||
|
|
@ -289,8 +358,8 @@ apply_template() {
|
||||||
-e "s|{{PROJECT_TAGLINE}}|$tagline|g" \
|
-e "s|{{PROJECT_TAGLINE}}|$tagline|g" \
|
||||||
-e "s|{{YEAR}}|$year|g" \
|
-e "s|{{YEAR}}|$year|g" \
|
||||||
-e "s|{{DATE}}|$date|g" \
|
-e "s|{{DATE}}|$date|g" \
|
||||||
-e "s|{{GITEA_URL}}|$GITEA_URL|g" \
|
-e "s|{{GITEA_URL}}|$GIT_HOST_URL|g" \
|
||||||
-e "s|{{GITEA_OWNER}}|$GITEA_OWNER|g" \
|
-e "s|{{GITEA_OWNER}}|$GIT_HOST_OWNER|g" \
|
||||||
"$template" > "$output"
|
"$template" > "$output"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -390,12 +459,12 @@ update_build_script() {
|
||||||
|
|
||||||
if [[ "$DRY_RUN" == true ]]; then
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
log_info "[DRY RUN] Would add to PROJECT_CONFIG:"
|
log_info "[DRY RUN] Would add to PROJECT_CONFIG:"
|
||||||
log_info " PROJECT_CONFIG[\"$name\"]=\"$title|$tagline|$GITEA_OWNER|$name|$name\""
|
log_info " PROJECT_CONFIG[\"$name\"]=\"$title|$tagline|$GIT_HOST_OWNER|$name|$name\""
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Find the last PROJECT_CONFIG line and add after it
|
# Find the last PROJECT_CONFIG line and add after it
|
||||||
local config_line="PROJECT_CONFIG[\"$name\"]=\"$title|$tagline|$GITEA_OWNER|$name|$name\""
|
local config_line="PROJECT_CONFIG[\"$name\"]=\"$title|$tagline|$GIT_HOST_OWNER|$name|$name\""
|
||||||
|
|
||||||
# Check if already exists
|
# Check if already exists
|
||||||
if grep -q "PROJECT_CONFIG\[\"$name\"\]" "$build_script"; then
|
if grep -q "PROJECT_CONFIG\[\"$name\"\]" "$build_script"; then
|
||||||
|
|
@ -468,7 +537,7 @@ print_summary() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "Locations:"
|
echo "Locations:"
|
||||||
echo " Local: $PROJECTS_ROOT/$name/"
|
echo " Local: $PROJECTS_ROOT/$name/"
|
||||||
echo " Gitea: $GITEA_URL/$GITEA_OWNER/$name"
|
echo " Remote: $GIT_HOST_URL/$GIT_HOST_OWNER/$name"
|
||||||
echo " Docs: $PROJECT_DOCS_ROOT/docs/projects/$name/"
|
echo " Docs: $PROJECT_DOCS_ROOT/docs/projects/$name/"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Next steps:"
|
echo "Next steps:"
|
||||||
|
|
@ -476,14 +545,10 @@ print_summary() {
|
||||||
echo " 2. Start developing!"
|
echo " 2. Start developing!"
|
||||||
echo ""
|
echo ""
|
||||||
if [[ "$deployed" == true ]]; then
|
if [[ "$deployed" == true ]]; then
|
||||||
echo "Public docs are live at:"
|
echo "Public docs deployed."
|
||||||
echo " https://pages.brrd.tech/$GITEA_OWNER/$name/"
|
|
||||||
else
|
else
|
||||||
echo "To publish documentation:"
|
echo "To publish documentation:"
|
||||||
echo " $PROJECT_DOCS_ROOT/scripts/build-public-docs.sh $name --deploy"
|
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/"
|
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
@ -504,10 +569,10 @@ main() {
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Load Gitea token (unless skipping)
|
# Load token and create repo (unless skipping)
|
||||||
if [[ "$SKIP_GITEA" != true ]]; then
|
if [[ "$SKIP_GITEA" != true ]]; then
|
||||||
load_gitea_token
|
load_git_token
|
||||||
create_gitea_repo "$PROJECT_NAME" "$PROJECT_TAGLINE"
|
create_git_repo "$PROJECT_NAME" "$PROJECT_TAGLINE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
create_local_project "$PROJECT_NAME"
|
create_local_project "$PROJECT_NAME"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ from PySide6.QtCore import QTimer
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
|
|
||||||
from development_hub.main_window import MainWindow
|
from development_hub.main_window import MainWindow
|
||||||
|
from development_hub.settings import Settings
|
||||||
|
from development_hub.dialogs import SetupWizardDialog
|
||||||
from development_hub.styles import DARK_THEME
|
from development_hub.styles import DARK_THEME
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -28,6 +30,13 @@ def main() -> int:
|
||||||
Exit code (0 for success).
|
Exit code (0 for success).
|
||||||
"""
|
"""
|
||||||
app = DevelopmentHubApp(sys.argv)
|
app = DevelopmentHubApp(sys.argv)
|
||||||
|
|
||||||
|
# Check for first-run setup
|
||||||
|
settings = Settings()
|
||||||
|
if not settings.get("setup_completed", False):
|
||||||
|
wizard = SetupWizardDialog()
|
||||||
|
wizard.exec()
|
||||||
|
|
||||||
window = MainWindow()
|
window = MainWindow()
|
||||||
|
|
||||||
# Handle Ctrl+C gracefully
|
# Handle Ctrl+C gracefully
|
||||||
|
|
|
||||||
|
|
@ -576,6 +576,45 @@ class SettingsDialog(QDialog):
|
||||||
|
|
||||||
layout.addWidget(docs_group)
|
layout.addWidget(docs_group)
|
||||||
|
|
||||||
|
# Git hosting settings
|
||||||
|
git_group = QGroupBox("Git Hosting")
|
||||||
|
git_layout = QFormLayout(git_group)
|
||||||
|
|
||||||
|
self.git_type_combo = QComboBox()
|
||||||
|
self.git_type_combo.addItem("Not configured", "")
|
||||||
|
current_type = self.settings.git_host_type
|
||||||
|
current_type_index = 0
|
||||||
|
for i, (value, label) in enumerate(Settings.GIT_HOST_CHOICES):
|
||||||
|
self.git_type_combo.addItem(label, value)
|
||||||
|
if value == current_type:
|
||||||
|
current_type_index = i + 1
|
||||||
|
self.git_type_combo.setCurrentIndex(current_type_index)
|
||||||
|
self.git_type_combo.currentIndexChanged.connect(self._on_git_type_changed)
|
||||||
|
git_layout.addRow("Provider:", self.git_type_combo)
|
||||||
|
|
||||||
|
self.git_url_edit = QLineEdit()
|
||||||
|
self.git_url_edit.setText(self.settings.git_host_url)
|
||||||
|
self.git_url_edit.setPlaceholderText("https://github.com or https://gitea.example.com")
|
||||||
|
git_layout.addRow("URL:", self.git_url_edit)
|
||||||
|
|
||||||
|
self.git_owner_edit = QLineEdit()
|
||||||
|
self.git_owner_edit.setText(self.settings.git_host_owner)
|
||||||
|
self.git_owner_edit.setPlaceholderText("username or organization")
|
||||||
|
git_layout.addRow("Owner:", self.git_owner_edit)
|
||||||
|
|
||||||
|
self.git_token_edit = QLineEdit()
|
||||||
|
self.git_token_edit.setText(self.settings.git_host_token)
|
||||||
|
self.git_token_edit.setEchoMode(QLineEdit.EchoMode.Password)
|
||||||
|
self.git_token_edit.setPlaceholderText("API token with repo scope")
|
||||||
|
git_layout.addRow("Token:", self.git_token_edit)
|
||||||
|
|
||||||
|
token_help = QLabel('<a href="#">How to create a token</a>')
|
||||||
|
token_help.setOpenExternalLinks(False)
|
||||||
|
token_help.linkActivated.connect(self._show_token_help)
|
||||||
|
git_layout.addRow("", token_help)
|
||||||
|
|
||||||
|
layout.addWidget(git_group)
|
||||||
|
|
||||||
layout.addStretch()
|
layout.addStretch()
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
|
|
@ -614,6 +653,39 @@ class SettingsDialog(QDialog):
|
||||||
row = self.paths_list.row(current)
|
row = self.paths_list.row(current)
|
||||||
self.paths_list.takeItem(row)
|
self.paths_list.takeItem(row)
|
||||||
|
|
||||||
|
def _on_git_type_changed(self, index: int):
|
||||||
|
"""Update URL placeholder based on git type."""
|
||||||
|
git_type = self.git_type_combo.currentData()
|
||||||
|
if git_type == "github":
|
||||||
|
self.git_url_edit.setPlaceholderText("https://github.com")
|
||||||
|
if not self.git_url_edit.text():
|
||||||
|
self.git_url_edit.setText("https://github.com")
|
||||||
|
elif git_type == "gitlab":
|
||||||
|
self.git_url_edit.setPlaceholderText("https://gitlab.com or self-hosted URL")
|
||||||
|
if not self.git_url_edit.text():
|
||||||
|
self.git_url_edit.setText("https://gitlab.com")
|
||||||
|
elif git_type == "gitea":
|
||||||
|
self.git_url_edit.setPlaceholderText("https://gitea.example.com")
|
||||||
|
|
||||||
|
def _show_token_help(self):
|
||||||
|
"""Show help dialog for creating tokens."""
|
||||||
|
git_type = self.git_type_combo.currentData()
|
||||||
|
if git_type == "github":
|
||||||
|
url = "https://github.com/settings/tokens"
|
||||||
|
msg = "Go to GitHub Settings → Developer settings → Personal access tokens\n\nCreate a token with 'repo' scope."
|
||||||
|
elif git_type == "gitlab":
|
||||||
|
url = "https://gitlab.com/-/profile/personal_access_tokens"
|
||||||
|
msg = "Go to GitLab Settings → Access Tokens\n\nCreate a token with 'api' scope."
|
||||||
|
elif git_type == "gitea":
|
||||||
|
base_url = self.git_url_edit.text() or "https://your-gitea-instance"
|
||||||
|
url = f"{base_url}/user/settings/applications"
|
||||||
|
msg = f"Go to {url}\n\nGenerate a new token with 'repo' scope."
|
||||||
|
else:
|
||||||
|
msg = "Select a git provider first."
|
||||||
|
url = None
|
||||||
|
|
||||||
|
QMessageBox.information(self, "Creating an API Token", msg)
|
||||||
|
|
||||||
def _save(self):
|
def _save(self):
|
||||||
"""Save settings and close."""
|
"""Save settings and close."""
|
||||||
# Save search paths
|
# Save search paths
|
||||||
|
|
@ -626,6 +698,151 @@ class SettingsDialog(QDialog):
|
||||||
self.settings.deploy_docs_after_creation = self.deploy_checkbox.isChecked()
|
self.settings.deploy_docs_after_creation = self.deploy_checkbox.isChecked()
|
||||||
self.settings.preferred_editor = self.editor_combo.currentData()
|
self.settings.preferred_editor = self.editor_combo.currentData()
|
||||||
self.settings.auto_start_docs_server = self.auto_docs_server_checkbox.isChecked()
|
self.settings.auto_start_docs_server = self.auto_docs_server_checkbox.isChecked()
|
||||||
|
|
||||||
|
# Save git hosting settings
|
||||||
|
self.settings.git_host_type = self.git_type_combo.currentData()
|
||||||
|
self.settings.git_host_url = self.git_url_edit.text().rstrip("/")
|
||||||
|
self.settings.git_host_owner = self.git_owner_edit.text()
|
||||||
|
self.settings.git_host_token = self.git_token_edit.text()
|
||||||
|
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
|
||||||
|
class SetupWizardDialog(QDialog):
|
||||||
|
"""First-run setup wizard for configuring Development Hub."""
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setWindowTitle("Welcome to Development Hub")
|
||||||
|
self.setMinimumWidth(500)
|
||||||
|
self.settings = Settings()
|
||||||
|
self._setup_ui()
|
||||||
|
|
||||||
|
def _setup_ui(self):
|
||||||
|
"""Set up the wizard UI."""
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
|
||||||
|
# Welcome message
|
||||||
|
welcome = QLabel(
|
||||||
|
"<h2>Welcome to Development Hub!</h2>"
|
||||||
|
"<p>Let's set up a few things to get you started.</p>"
|
||||||
|
)
|
||||||
|
welcome.setWordWrap(True)
|
||||||
|
layout.addWidget(welcome)
|
||||||
|
|
||||||
|
# Projects directory
|
||||||
|
projects_group = QGroupBox("Projects Directory")
|
||||||
|
projects_layout = QHBoxLayout(projects_group)
|
||||||
|
|
||||||
|
self.projects_dir_edit = QLineEdit()
|
||||||
|
self.projects_dir_edit.setText(str(self.settings.default_project_path))
|
||||||
|
projects_layout.addWidget(self.projects_dir_edit)
|
||||||
|
|
||||||
|
browse_btn = QPushButton("Browse...")
|
||||||
|
browse_btn.clicked.connect(self._browse_projects_dir)
|
||||||
|
projects_layout.addWidget(browse_btn)
|
||||||
|
|
||||||
|
layout.addWidget(projects_group)
|
||||||
|
|
||||||
|
# Git hosting settings
|
||||||
|
git_group = QGroupBox("Git Hosting (for creating new projects)")
|
||||||
|
git_layout = QFormLayout(git_group)
|
||||||
|
|
||||||
|
self.git_type_combo = QComboBox()
|
||||||
|
for value, label in Settings.GIT_HOST_CHOICES:
|
||||||
|
self.git_type_combo.addItem(label, value)
|
||||||
|
self.git_type_combo.currentIndexChanged.connect(self._on_git_type_changed)
|
||||||
|
git_layout.addRow("Provider:", self.git_type_combo)
|
||||||
|
|
||||||
|
self.git_url_edit = QLineEdit()
|
||||||
|
self.git_url_edit.setPlaceholderText("https://github.com")
|
||||||
|
git_layout.addRow("URL:", self.git_url_edit)
|
||||||
|
|
||||||
|
self.git_owner_edit = QLineEdit()
|
||||||
|
self.git_owner_edit.setPlaceholderText("your username or organization")
|
||||||
|
git_layout.addRow("Owner:", self.git_owner_edit)
|
||||||
|
|
||||||
|
self.git_token_edit = QLineEdit()
|
||||||
|
self.git_token_edit.setEchoMode(QLineEdit.EchoMode.Password)
|
||||||
|
self.git_token_edit.setPlaceholderText("API token (optional - needed for creating repos)")
|
||||||
|
git_layout.addRow("Token:", self.git_token_edit)
|
||||||
|
|
||||||
|
skip_note = QLabel(
|
||||||
|
"<small>You can skip git setup and configure it later in Settings.</small>"
|
||||||
|
)
|
||||||
|
skip_note.setStyleSheet("color: #888;")
|
||||||
|
git_layout.addRow("", skip_note)
|
||||||
|
|
||||||
|
layout.addWidget(git_group)
|
||||||
|
|
||||||
|
layout.addStretch()
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
|
||||||
|
skip_btn = QPushButton("Skip Setup")
|
||||||
|
skip_btn.clicked.connect(self._skip)
|
||||||
|
button_layout.addWidget(skip_btn)
|
||||||
|
|
||||||
|
button_layout.addStretch()
|
||||||
|
|
||||||
|
finish_btn = QPushButton("Finish Setup")
|
||||||
|
finish_btn.setDefault(True)
|
||||||
|
finish_btn.clicked.connect(self._finish)
|
||||||
|
button_layout.addWidget(finish_btn)
|
||||||
|
|
||||||
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
# Set initial URL based on default selection
|
||||||
|
self._on_git_type_changed(0)
|
||||||
|
|
||||||
|
def _browse_projects_dir(self):
|
||||||
|
"""Browse for projects directory."""
|
||||||
|
path = QFileDialog.getExistingDirectory(
|
||||||
|
self,
|
||||||
|
"Select Projects Directory",
|
||||||
|
self.projects_dir_edit.text() or str(Path.home()),
|
||||||
|
)
|
||||||
|
if path:
|
||||||
|
self.projects_dir_edit.setText(path)
|
||||||
|
|
||||||
|
def _on_git_type_changed(self, index: int):
|
||||||
|
"""Update URL based on git type."""
|
||||||
|
git_type = self.git_type_combo.currentData()
|
||||||
|
if git_type == "github":
|
||||||
|
self.git_url_edit.setText("https://github.com")
|
||||||
|
elif git_type == "gitlab":
|
||||||
|
self.git_url_edit.setText("https://gitlab.com")
|
||||||
|
elif git_type == "gitea":
|
||||||
|
self.git_url_edit.setText("")
|
||||||
|
self.git_url_edit.setPlaceholderText("https://gitea.example.com")
|
||||||
|
|
||||||
|
def _skip(self):
|
||||||
|
"""Skip setup and just set first_run flag."""
|
||||||
|
self.settings.set("setup_completed", True)
|
||||||
|
self.reject()
|
||||||
|
|
||||||
|
def _finish(self):
|
||||||
|
"""Save settings and finish setup."""
|
||||||
|
# Save projects directory
|
||||||
|
projects_dir = self.projects_dir_edit.text()
|
||||||
|
if projects_dir:
|
||||||
|
self.settings.default_project_path = Path(projects_dir)
|
||||||
|
self.settings.project_search_paths = [projects_dir]
|
||||||
|
|
||||||
|
# Save git settings if provided
|
||||||
|
git_type = self.git_type_combo.currentData()
|
||||||
|
git_url = self.git_url_edit.text().rstrip("/")
|
||||||
|
git_owner = self.git_owner_edit.text()
|
||||||
|
git_token = self.git_token_edit.text()
|
||||||
|
|
||||||
|
if git_type and git_url and git_owner:
|
||||||
|
self.settings.git_host_type = git_type
|
||||||
|
self.settings.git_host_url = git_url
|
||||||
|
self.settings.git_host_owner = git_owner
|
||||||
|
self.settings.git_host_token = git_token
|
||||||
|
|
||||||
|
self.settings.set("setup_completed", True)
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,11 @@ class Settings:
|
||||||
"project_search_paths": [str(Path.home() / "PycharmProjects")],
|
"project_search_paths": [str(Path.home() / "PycharmProjects")],
|
||||||
"project_ignore_folders": ["trash", "project-docs", ".cache", "__pycache__", "node_modules"],
|
"project_ignore_folders": ["trash", "project-docs", ".cache", "__pycache__", "node_modules"],
|
||||||
"auto_start_docs_server": True,
|
"auto_start_docs_server": True,
|
||||||
|
# Git hosting settings
|
||||||
|
"git_host_type": "", # "gitea", "github", "gitlab", ""
|
||||||
|
"git_host_url": "", # e.g., "https://gitea.example.com" or "https://github.com"
|
||||||
|
"git_host_owner": "", # username or organization
|
||||||
|
"git_host_token": "", # API token (stored in settings, not ideal but simple)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Available editor choices with display names
|
# Available editor choices with display names
|
||||||
|
|
@ -31,6 +36,13 @@ class Settings:
|
||||||
("subl", "Sublime Text"),
|
("subl", "Sublime Text"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Git hosting provider choices
|
||||||
|
GIT_HOST_CHOICES = [
|
||||||
|
("gitea", "Gitea"),
|
||||||
|
("github", "GitHub"),
|
||||||
|
("gitlab", "GitLab"),
|
||||||
|
]
|
||||||
|
|
||||||
def __new__(cls):
|
def __new__(cls):
|
||||||
"""Singleton pattern."""
|
"""Singleton pattern."""
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
|
|
@ -120,6 +132,47 @@ class Settings:
|
||||||
def auto_start_docs_server(self, value: bool):
|
def auto_start_docs_server(self, value: bool):
|
||||||
self.set("auto_start_docs_server", value)
|
self.set("auto_start_docs_server", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def git_host_type(self) -> str:
|
||||||
|
"""Git hosting type (gitea, github, gitlab)."""
|
||||||
|
return self.get("git_host_type", "")
|
||||||
|
|
||||||
|
@git_host_type.setter
|
||||||
|
def git_host_type(self, value: str):
|
||||||
|
self.set("git_host_type", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def git_host_url(self) -> str:
|
||||||
|
"""Git host URL."""
|
||||||
|
return self.get("git_host_url", "")
|
||||||
|
|
||||||
|
@git_host_url.setter
|
||||||
|
def git_host_url(self, value: str):
|
||||||
|
self.set("git_host_url", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def git_host_owner(self) -> str:
|
||||||
|
"""Git host username or organization."""
|
||||||
|
return self.get("git_host_owner", "")
|
||||||
|
|
||||||
|
@git_host_owner.setter
|
||||||
|
def git_host_owner(self, value: str):
|
||||||
|
self.set("git_host_owner", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def git_host_token(self) -> str:
|
||||||
|
"""Git host API token."""
|
||||||
|
return self.get("git_host_token", "")
|
||||||
|
|
||||||
|
@git_host_token.setter
|
||||||
|
def git_host_token(self, value: str):
|
||||||
|
self.set("git_host_token", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_git_configured(self) -> bool:
|
||||||
|
"""Check if git hosting is configured."""
|
||||||
|
return bool(self.git_host_type and self.git_host_url and self.git_host_owner)
|
||||||
|
|
||||||
def save_session(self, state: dict):
|
def save_session(self, state: dict):
|
||||||
"""Save session state to file."""
|
"""Save session state to file."""
|
||||||
self._session_file.parent.mkdir(parents=True, exist_ok=True)
|
self._session_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue