Compare commits

..

3 Commits

Author SHA1 Message Date
Jullian Pepito 60372ff797 fix(git-clone): Update README.md (#448)
Changes `coder_git_auth` to `coder_external_auth` in README

## Description

<!-- Briefly describe what this PR does and why -->

## Type of Change

- [ ] New module
- [ ] Bug fix
- [ ] Feature/enhancement
- [X] Documentation
- [ ] Other

## Module Information

<!-- Delete this section if not applicable -->

**Path:** `registry/coder/modules/git-clone` 
**New version:** `v1.1.2`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

- [ ] Tests pass (`bun test`)
- [ ] Code formatted (`bun run fmt`)
- [ ] Changes tested locally

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: Jullian Pepito <jullian@MacBook-Pro.local>
Co-authored-by: DevCats <christofer@coder.com>
2025-10-07 15:35:02 -05:00
Rishi Mondal f28bcdb713 Auto-Start Development Servers Module (#316)
# Auto-Start Development Servers Module

## Summary

/claim #204

Implements automatic detection and startup of development servers based
on project detection as requested in #204.

-  **Multi-language support**: Node.js, Rails, Django, Flask, Spring
Boot, Go, PHP, Rust, .NET
-  **Background execution**: Servers start automatically without user
intervention
-  **Devcontainer.json integration**: Uses custom start commands when
available
-  **Smart fallback**: Creates sample project when no existing projects
found
-  **Comprehensive logging**: Full activity logs for troubleshooting



https://github.com/user-attachments/assets/2eddf67c-3ac1-4e55-a5ba-79292d61e918



## Addresses GitHub Issue

Closes #204 - "Auto-start development servers based on project
detection"

---------

Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: DevCats <chris@dualriver.com>
2025-10-07 14:44:00 -05:00
romracer cb553209a5 fix: update CLI icon for copilot module to same icon as web app (#469)
## Description

Sets `cli_app_icon` in agentapi to the same icon used for
`web_app_icon`. Its currently using the default of Claude.

## Type of Change

- [ ] New module
- [x] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

<!-- Delete this section if not applicable -->

**Path:** `registry/coder-labs/modules/copilot`  
**New version:** `v0.2.1`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

- [ ] Tests pass (`bun test`)
- [ ] Code formatted (`bun run fmt`)
- [x] Changes tested locally

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-10-07 13:54:14 -05:00
8 changed files with 940 additions and 15 deletions
@@ -13,7 +13,7 @@ Run [GitHub Copilot CLI](https://docs.github.com/copilot/concepts/agents/about-c
```tf
module "copilot" {
source = "registry.coder.com/coder-labs/copilot/coder"
version = "0.2.0"
version = "0.2.1"
agent_id = coder_agent.example.id
workdir = "/home/coder/projects"
}
@@ -51,7 +51,7 @@ data "coder_parameter" "ai_prompt" {
module "copilot" {
source = "registry.coder.com/coder-labs/copilot/coder"
version = "0.2.0"
version = "0.2.1"
agent_id = coder_agent.example.id
workdir = "/home/coder/projects"
@@ -71,7 +71,7 @@ Customize tool permissions, MCP servers, and Copilot settings:
```tf
module "copilot" {
source = "registry.coder.com/coder-labs/copilot/coder"
version = "0.2.0"
version = "0.2.1"
agent_id = coder_agent.example.id
workdir = "/home/coder/projects"
@@ -142,7 +142,7 @@ variable "github_token" {
module "copilot" {
source = "registry.coder.com/coder-labs/copilot/coder"
version = "0.2.0"
version = "0.2.1"
agent_id = coder_agent.example.id
workdir = "/home/coder/projects"
github_token = var.github_token
@@ -156,7 +156,7 @@ Run Copilot as a command-line tool without task reporting or web interface. This
```tf
module "copilot" {
source = "registry.coder.com/coder-labs/copilot/coder"
version = "0.2.0"
version = "0.2.1"
agent_id = coder_agent.example.id
workdir = "/home/coder"
report_tasks = false
@@ -253,6 +253,7 @@ module "agentapi" {
web_app_display_name = var.web_app_display_name
cli_app = var.cli_app
cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null
cli_app_icon = var.cli_app ? var.icon : null
cli_app_display_name = var.cli_app ? var.cli_app_display_name : null
agentapi_subdomain = var.subdomain
module_dir_name = local.module_dir_name
+10 -10
View File
@@ -14,7 +14,7 @@ This module allows you to automatically clone a repository by URL and skip if it
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
@@ -28,7 +28,7 @@ module "git-clone" {
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
base_dir = "~/projects/coder"
@@ -43,12 +43,12 @@ To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-prov
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
data "coder_git_auth" "github" {
data "coder_external_auth" "github" {
id = "github"
}
```
@@ -69,7 +69,7 @@ data "coder_parameter" "git_repo" {
module "git_clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
url = data.coder_parameter.git_repo.value
}
@@ -103,7 +103,7 @@ Configuring `git-clone` for a self-hosted GitHub Enterprise Server running at `g
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
url = "https://github.example.com/coder/coder/tree/feat/example"
git_providers = {
@@ -122,7 +122,7 @@ To GitLab clone with a specific branch like `feat/example`
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
url = "https://gitlab.com/coder/coder/-/tree/feat/example"
}
@@ -134,7 +134,7 @@ Configuring `git-clone` for a self-hosted GitLab running at `gitlab.example.com`
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
url = "https://gitlab.example.com/coder/coder/-/tree/feat/example"
git_providers = {
@@ -155,7 +155,7 @@ For example, to clone the `feat/example` branch:
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
branch_name = "feat/example"
@@ -173,7 +173,7 @@ For example, this will clone into the `~/projects/coder/coder-dev` folder:
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.1.1"
version = "1.1.2"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
folder_name = "coder-dev"
+1
View File
@@ -19,3 +19,4 @@ participating in LFX CNCF programs, and helping the developer community grow.
## Modules
- **aws-ami-snapshot**: Create and manage AMI snapshots for Coder workspaces with restore capabilities
- [auto-start-dev-server](modules/auto-start-dev-server/README.md) - Automatically detect and start development servers for various project types
@@ -0,0 +1,151 @@
---
display_name: Auto-Start Development Servers
description: Automatically detect and start development servers for various project types
icon: ../../../../.icons/server.svg
verified: false
tags: [development, automation, servers]
---
# Auto-Start Development Servers
Automatically detect and start development servers for various project types when a workspace starts. This module scans your workspace for common project structures and starts the appropriate development servers in the background without manual intervention.
```tf
module "auto_start_dev_servers" {
source = "registry.coder.com/mavrickrishi/auto-start-dev-server/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
}
```
## Features
- **Multi-language support**: Detects and starts servers for Node.js, Python (Django/Flask), Ruby (Rails), Java (Spring Boot), Go, PHP, Rust, and .NET projects
- **Smart script prioritization**: Prioritizes `dev` scripts over `start` scripts for better development experience
- **Intelligent frontend detection**: Automatically identifies frontend projects (React, Vue, Angular, Next.js, Nuxt, Svelte, Vite) and prioritizes them for preview apps
- **Devcontainer integration**: Respects custom start commands defined in `.devcontainer/devcontainer.json`
- **Configurable scanning**: Adjustable directory scan depth and project type toggles
- **Non-blocking startup**: Servers start in the background with configurable startup delay
- **Comprehensive logging**: All server output and detection results logged to a central file
- **Smart detection**: Uses project-specific files and configurations to identify project types
- **Integrated live preview**: Automatically creates a preview app for the primary frontend project
## Supported Project Types
| Framework/Language | Detection Files | Start Commands (in priority order) |
| ------------------ | -------------------------------------------- | ----------------------------------------------------- |
| **Node.js/npm** | `package.json` | `npm run dev`, `npm run serve`, `npm start` (or yarn) |
| **Ruby on Rails** | `Gemfile` with rails gem | `bundle exec rails server` |
| **Django** | `manage.py` | `python manage.py runserver` |
| **Flask** | `requirements.txt` with Flask | `python app.py/main.py/run.py` |
| **Spring Boot** | `pom.xml` or `build.gradle` with spring-boot | `mvn spring-boot:run`, `gradle bootRun` |
| **Go** | `go.mod` | `go run main.go` |
| **PHP** | `composer.json` | `php -S 0.0.0.0:8080` |
| **Rust** | `Cargo.toml` | `cargo run` |
| **.NET** | `*.csproj` | `dotnet run` |
## Examples
### Basic Usage
```hcl
module "auto_start" {
source = "./modules/auto-start-dev-server"
version = "1.0.0"
agent_id = coder_agent.main.id
}
```
### Advanced Usage
```hcl
module "auto_start_dev_servers" {
source = "./modules/auto-start-dev-server"
version = "1.0.0"
agent_id = coder_agent.main.id
# Optional: Configure which project types to detect
enable_npm = true
enable_rails = true
enable_django = true
enable_flask = true
enable_spring_boot = true
enable_go = true
enable_php = true
enable_rust = true
enable_dotnet = true
# Optional: Enable devcontainer.json integration
enable_devcontainer = true
# Optional: Workspace directory to scan (supports environment variables)
workspace_directory = "$HOME"
# Optional: Directory scan depth (1-5)
scan_depth = 2
# Optional: Startup delay in seconds
startup_delay = 10
# Optional: Log file path
log_path = "/tmp/dev-servers.log"
# Optional: Enable automatic preview app (default: true)
enable_preview_app = true
}
```
### Disable Preview App
```hcl
module "auto_start" {
source = "./modules/auto-start-dev-server"
version = "1.0.0"
agent_id = coder_agent.main.id
# Disable automatic preview app creation
enable_preview_app = false
}
```
### Selective Project Types
```hcl
module "auto_start" {
source = "./modules/auto-start-dev-server"
version = "1.0.0"
agent_id = coder_agent.main.id
# Only enable web development projects
enable_npm = true
enable_rails = true
enable_django = true
enable_flask = true
# Disable other project types
enable_spring_boot = false
enable_go = false
enable_php = false
enable_rust = false
enable_dotnet = false
}
```
### Deep Workspace Scanning
```hcl
module "auto_start" {
source = "./modules/auto-start-dev-server"
version = "1.0.0"
agent_id = coder_agent.main.id
workspace_directory = "/workspaces"
scan_depth = 3
startup_delay = 5
log_path = "/var/log/dev-servers.log"
}
```
## License
This module is provided under the same license as the Coder Registry.
@@ -0,0 +1,109 @@
import { describe, expect, it } from "bun:test";
import {
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "~test";
describe("auto-start-dev-server", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "test-agent-123",
});
it("validates scan_depth range", () => {
const t1 = async () => {
await runTerraformApply(import.meta.dir, {
agent_id: "test-agent-123",
scan_depth: "0",
});
};
expect(t1).toThrow("Scan depth must be between 1 and 5");
const t2 = async () => {
await runTerraformApply(import.meta.dir, {
agent_id: "test-agent-123",
scan_depth: "6",
});
};
expect(t2).toThrow("Scan depth must be between 1 and 5");
});
it("applies successfully with default values", async () => {
await runTerraformApply(import.meta.dir, {
agent_id: "test-agent-123",
});
});
it("applies successfully with all project types enabled", async () => {
await runTerraformApply(import.meta.dir, {
agent_id: "test-agent-123",
enable_npm: "true",
enable_rails: "true",
enable_django: "true",
enable_flask: "true",
enable_spring_boot: "true",
enable_go: "true",
enable_php: "true",
enable_rust: "true",
enable_dotnet: "true",
enable_devcontainer: "true",
});
});
it("applies successfully with all project types disabled", async () => {
await runTerraformApply(import.meta.dir, {
agent_id: "test-agent-123",
enable_npm: "false",
enable_rails: "false",
enable_django: "false",
enable_flask: "false",
enable_spring_boot: "false",
enable_go: "false",
enable_php: "false",
enable_rust: "false",
enable_dotnet: "false",
enable_devcontainer: "false",
});
});
it("applies successfully with custom configuration", async () => {
await runTerraformApply(import.meta.dir, {
agent_id: "test-agent-123",
workspace_directory: "/custom/workspace",
scan_depth: "3",
startup_delay: "5",
log_path: "/var/log/custom-dev-servers.log",
display_name: "Custom Dev Server Startup",
});
});
it("validates scan_depth boundary values", async () => {
// Test valid boundary values
await runTerraformApply(import.meta.dir, {
agent_id: "test-agent-123",
scan_depth: "1",
});
await runTerraformApply(import.meta.dir, {
agent_id: "test-agent-123",
scan_depth: "5",
});
});
it("applies with selective project type configuration", async () => {
await runTerraformApply(import.meta.dir, {
agent_id: "test-agent-123",
enable_npm: "true",
enable_django: "true",
enable_go: "true",
enable_rails: "false",
enable_flask: "false",
enable_spring_boot: "false",
enable_php: "false",
enable_rust: "false",
enable_dotnet: "false",
});
});
});
@@ -0,0 +1,195 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.5"
}
}
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "workspace_directory" {
type = string
description = "The directory to scan for development projects."
default = "$HOME"
}
variable "project_detection" {
type = bool
description = "Enable automatic project detection for all supported types. When true, all project types are detected unless individually disabled. When false, only explicitly enabled project types are detected."
default = true
}
variable "enable_npm" {
type = bool
description = "Enable auto-detection and startup of npm projects."
default = null
}
variable "enable_rails" {
type = bool
description = "Enable auto-detection and startup of Rails projects."
default = null
}
variable "enable_django" {
type = bool
description = "Enable auto-detection and startup of Django projects."
default = null
}
variable "enable_flask" {
type = bool
description = "Enable auto-detection and startup of Flask projects."
default = null
}
variable "enable_spring_boot" {
type = bool
description = "Enable auto-detection and startup of Spring Boot projects."
default = null
}
variable "enable_go" {
type = bool
description = "Enable auto-detection and startup of Go projects."
default = null
}
variable "enable_php" {
type = bool
description = "Enable auto-detection and startup of PHP projects."
default = null
}
variable "enable_rust" {
type = bool
description = "Enable auto-detection and startup of Rust projects."
default = null
}
variable "enable_dotnet" {
type = bool
description = "Enable auto-detection and startup of .NET projects."
default = null
}
variable "enable_devcontainer" {
type = bool
description = "Enable integration with devcontainer.json configuration."
default = null
}
variable "log_path" {
type = string
description = "The path to log development server output to."
default = "/tmp/dev-servers.log"
}
variable "scan_depth" {
type = number
description = "Maximum directory depth to scan for projects (1-5)."
default = 2
validation {
condition = var.scan_depth >= 1 && var.scan_depth <= 5
error_message = "Scan depth must be between 1 and 5."
}
}
variable "startup_delay" {
type = number
description = "Delay in seconds before starting dev servers (allows other setup to complete)."
default = 10
}
variable "display_name" {
type = string
description = "Display name for the auto-start dev server script."
default = "Auto-Start Dev Servers"
}
variable "enable_preview_app" {
type = bool
description = "Enable automatic creation of a preview app for the first detected project."
default = true
}
# Read the detected port from the file written by the script
locals {
detected_port = var.enable_preview_app ? try(tonumber(trimspace(file("/tmp/detected-port.txt"))), 3000) : 3000
# Attempt to read project information for better preview naming
detected_projects = try(jsondecode(file("/tmp/detected-projects.json")), [])
preview_project = length(local.detected_projects) > 0 ? local.detected_projects[0] : null
}
resource "coder_script" "auto_start_dev_server" {
agent_id = var.agent_id
display_name = var.display_name
icon = "/icon/server.svg"
script = templatefile("${path.module}/run.sh", {
WORKSPACE_DIR = var.workspace_directory
ENABLE_NPM = coalesce(var.enable_npm, var.project_detection)
ENABLE_RAILS = coalesce(var.enable_rails, var.project_detection)
ENABLE_DJANGO = coalesce(var.enable_django, var.project_detection)
ENABLE_FLASK = coalesce(var.enable_flask, var.project_detection)
ENABLE_SPRING_BOOT = coalesce(var.enable_spring_boot, var.project_detection)
ENABLE_GO = coalesce(var.enable_go, var.project_detection)
ENABLE_PHP = coalesce(var.enable_php, var.project_detection)
ENABLE_RUST = coalesce(var.enable_rust, var.project_detection)
ENABLE_DOTNET = coalesce(var.enable_dotnet, var.project_detection)
ENABLE_DEVCONTAINER = coalesce(var.enable_devcontainer, var.project_detection)
LOG_PATH = var.log_path
SCAN_DEPTH = var.scan_depth
STARTUP_DELAY = var.startup_delay
})
run_on_start = true
}
# Create preview app for first detected project
resource "coder_app" "preview" {
count = var.enable_preview_app ? 1 : 0
agent_id = var.agent_id
slug = "dev-preview"
display_name = "Live Preview"
url = "http://localhost:${local.detected_port}"
icon = "/icon/globe.svg"
subdomain = true
share = "owner"
}
output "log_path" {
value = var.log_path
description = "Path to the log file for dev server output"
}
# Example output values for common port mappings
output "common_ports" {
value = {
nodejs = 3000
rails = 3000
django = 8000
flask = 5000
spring = 8080
go = 8080
php = 8080
rust = 8000
dotnet = 5000
}
description = "Common default ports for different project types"
}
output "preview_url" {
value = var.enable_preview_app ? try(coder_app.preview[0].url, null) : null
description = "URL of the live preview app (if enabled)"
}
output "detected_port" {
value = local.detected_port
description = "Port of the first detected development server"
}
+468
View File
@@ -0,0 +1,468 @@
#!/usr/bin/env bash
set -euo pipefail
# Color codes for output
BOLD='\033[0;1m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
RED='\033[0;31m'
RESET='\033[0m'
echo -e "$${BOLD}🚀 Auto-Start Development Servers$${RESET}"
echo "Workspace Directory: ${WORKSPACE_DIR}"
echo "Log Path: ${LOG_PATH}"
echo "Scan Depth: ${SCAN_DEPTH}"
# Wait for startup delay to allow other setup to complete
if [ "${STARTUP_DELAY}" -gt 0 ]; then
echo -e "$${YELLOW}⏳ Waiting ${STARTUP_DELAY} seconds for system initialization...$${RESET}"
sleep "${STARTUP_DELAY}"
fi
# Initialize log file
echo "=== Auto-Start Dev Servers Log ===" > "${LOG_PATH}"
echo "Started at: $(date)" >> "${LOG_PATH}"
# Initialize detected projects JSON file
DETECTED_PROJECTS_FILE="/tmp/detected-projects.json"
echo '[]' > "$DETECTED_PROJECTS_FILE"
# Initialize detected port file for preview app
DETECTED_PORT_FILE="/tmp/detected-port.txt"
FIRST_PORT_DETECTED=false
FRONTEND_PROJECT_DETECTED=false
# Function to log messages
log_message() {
echo -e "$1"
echo "$1" >> "${LOG_PATH}"
}
# Function to determine if a project is likely a frontend project
is_frontend_project() {
local project_dir="$1"
local project_type="$2"
# Check for common frontend indicators
if [ "$project_type" = "nodejs" ]; then
# Check package.json for frontend dependencies
if [ -f "$project_dir/package.json" ] && command -v jq &> /dev/null; then
# Check for common frontend frameworks
local has_react=$(jq '.dependencies.react // .devDependencies.react // empty' "$project_dir/package.json")
local has_vue=$(jq '.dependencies.vue // .devDependencies.vue // empty' "$project_dir/package.json")
local has_angular=$(jq '.dependencies["@angular/core"] // .devDependencies["@angular/core"] // empty' "$project_dir/package.json")
local has_next=$(jq '.dependencies.next // .devDependencies.next // empty' "$project_dir/package.json")
local has_nuxt=$(jq '.dependencies.nuxt // .devDependencies.nuxt // empty' "$project_dir/package.json")
local has_svelte=$(jq '.dependencies.svelte // .devDependencies.svelte // empty' "$project_dir/package.json")
local has_vite=$(jq '.dependencies.vite // .devDependencies.vite // empty' "$project_dir/package.json")
if [ -n "$has_react" ] || [ -n "$has_vue" ] || [ -n "$has_angular" ] \
|| [ -n "$has_next" ] || [ -n "$has_nuxt" ] || [ -n "$has_svelte" ] \
|| [ -n "$has_vite" ]; then
return 0 # It's a frontend project
fi
fi
# Check for common frontend directory structures
if [ -d "$project_dir/src/components" ] || [ -d "$project_dir/components" ] \
|| [ -d "$project_dir/pages" ] || [ -d "$project_dir/views" ] \
|| [ -f "$project_dir/index.html" ] || [ -f "$project_dir/public/index.html" ]; then
return 0 # It's likely a frontend project
fi
fi
# Rails projects with webpack/webpacker are frontend-enabled
if [ "$project_type" = "rails" ]; then
if [ -f "$project_dir/config/webpacker.yml" ] || [ -f "$project_dir/webpack.config.js" ]; then
return 0
fi
fi
# Django projects with static/templates are frontend-enabled
if [ "$project_type" = "django" ]; then
if [ -d "$project_dir/static" ] || [ -d "$project_dir/templates" ]; then
return 0
fi
fi
return 1 # Not a frontend project
}
# Function to add detected project to JSON
add_detected_project() {
local project_dir="$1"
local project_type="$2"
local port="$3"
local command="$4"
# Check if this is a frontend project
local is_frontend=false
if is_frontend_project "$project_dir" "$project_type"; then
is_frontend=true
log_message "$${BLUE}🎨 Detected frontend project at $project_dir$${RESET}"
fi
# Prioritize frontend projects for the preview app
# Set port if: 1) No port set yet, OR 2) This is frontend and no frontend detected yet
if [ "$FIRST_PORT_DETECTED" = false ] || ([ "$is_frontend" = true ] && [ "$FRONTEND_PROJECT_DETECTED" = false ]); then
echo "$port" > "$DETECTED_PORT_FILE"
FIRST_PORT_DETECTED=true
if [ "$is_frontend" = true ]; then
FRONTEND_PROJECT_DETECTED=true
log_message "$${BLUE}🎯 Frontend project detected - Preview app will be available on port $port$${RESET}"
else
log_message "$${BLUE}🎯 Project detected - Preview app will be available on port $port$${RESET}"
fi
fi
# Create JSON entry for this project
local project_json=$(jq -n \
--arg dir "$project_dir" \
--arg type "$project_type" \
--arg port "$port" \
--arg cmd "$command" \
--arg frontend "$is_frontend" \
'{"directory": $dir, "type": $type, "port": $port, "command": $cmd, "is_frontend": ($frontend == "true")}')
# Append to the detected projects file
jq ". += [$project_json]" "$DETECTED_PROJECTS_FILE" > "$DETECTED_PROJECTS_FILE.tmp" \
&& mv "$DETECTED_PROJECTS_FILE.tmp" "$DETECTED_PROJECTS_FILE"
}
# Function to detect and start npm/yarn projects
detect_npm_projects() {
if [ "${ENABLE_NPM}" != "true" ]; then
return
fi
log_message "$${BLUE}🔍 Scanning for Node.js/npm projects...$${RESET}"
# Use find with maxdepth to respect scan depth
while IFS= read -r -d '' package_json; do
project_dir=$(dirname "$package_json")
log_message "$${GREEN}📦 Found Node.js project: $project_dir$${RESET}"
cd "$project_dir"
# Check package.json for start script
if [ -f "package.json" ] && command -v jq &> /dev/null; then
start_script=$(jq -r '.scripts.start // empty' package.json)
dev_script=$(jq -r '.scripts.dev // empty' package.json)
serve_script=$(jq -r '.scripts.serve // empty' package.json)
# Determine port (check for common port configurations)
local project_port=3000
if [ -n "$dev_script" ] && echo "$dev_script" | grep -q "\-\-port"; then
project_port=$(echo "$dev_script" | grep -oE "\-\-port[[:space:]]+[0-9]+" | grep -oE "[0-9]+$" || echo "3000")
fi
# Use yarn if yarn.lock exists
local pkg_manager="npm"
local cmd_prefix=""
if [ -f "yarn.lock" ] && command -v yarn &> /dev/null; then
pkg_manager="yarn"
cmd_prefix=""
else
cmd_prefix="run "
fi
# Prioritize scripts: 'dev' > 'serve' > 'start' for development environments
if [ -n "$dev_script" ]; then
if [ "$pkg_manager" = "yarn" ]; then
log_message "$${GREEN}🟢 Starting project with 'yarn dev' in $project_dir$${RESET}"
nohup yarn dev >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "nodejs" "$project_port" "yarn dev"
else
log_message "$${GREEN}🟢 Starting project with 'npm run dev' in $project_dir$${RESET}"
nohup npm run dev >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "nodejs" "$project_port" "npm run dev"
fi
elif [ -n "$serve_script" ]; then
if [ "$pkg_manager" = "yarn" ]; then
log_message "$${GREEN}🟢 Starting project with 'yarn serve' in $project_dir$${RESET}"
nohup yarn serve >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "nodejs" "$project_port" "yarn serve"
else
log_message "$${GREEN}🟢 Starting project with 'npm run serve' in $project_dir$${RESET}"
nohup npm run serve >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "nodejs" "$project_port" "npm run serve"
fi
elif [ -n "$start_script" ]; then
if [ "$pkg_manager" = "yarn" ]; then
log_message "$${GREEN}🟢 Starting project with 'yarn start' in $project_dir$${RESET}"
nohup yarn start >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "nodejs" "$project_port" "yarn start"
else
log_message "$${GREEN}🟢 Starting project with 'npm start' in $project_dir$${RESET}"
nohup npm start >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "nodejs" "$project_port" "npm start"
fi
fi
fi
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "package.json" -type f -print0)
}
# Function to detect and start Rails projects
detect_rails_projects() {
if [ "${ENABLE_RAILS}" != "true" ]; then
return
fi
log_message "$${BLUE}🔍 Scanning for Ruby on Rails projects...$${RESET}"
while IFS= read -r -d '' gemfile; do
project_dir=$(dirname "$gemfile")
log_message "$${GREEN}💎 Found Rails project: $project_dir$${RESET}"
cd "$project_dir"
# Check if it's actually a Rails project
if grep -q "gem ['\"]rails['\"]" Gemfile 2> /dev/null; then
log_message "$${GREEN}🟢 Starting Rails server in $project_dir$${RESET}"
nohup bundle exec rails server >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "rails" "3000" "bundle exec rails server"
fi
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "Gemfile" -type f -print0)
}
# Function to detect and start Django projects
detect_django_projects() {
if [ "${ENABLE_DJANGO}" != "true" ]; then
return
fi
log_message "$${BLUE}🔍 Scanning for Django projects...$${RESET}"
while IFS= read -r -d '' manage_py; do
project_dir=$(dirname "$manage_py")
log_message "$${GREEN}🐍 Found Django project: $project_dir$${RESET}"
cd "$project_dir"
log_message "$${GREEN}🟢 Starting Django development server in $project_dir$${RESET}"
nohup python manage.py runserver 0.0.0.0:8000 >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "django" "8000" "python manage.py runserver"
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "manage.py" -type f -print0)
}
# Function to detect and start Flask projects
detect_flask_projects() {
if [ "${ENABLE_FLASK}" != "true" ]; then
return
fi
log_message "$${BLUE}🔍 Scanning for Flask projects...$${RESET}"
while IFS= read -r -d '' requirements_txt; do
project_dir=$(dirname "$requirements_txt")
# Check if Flask is in requirements
if grep -q -i "flask" "$requirements_txt" 2> /dev/null; then
log_message "$${GREEN}🌶️ Found Flask project: $project_dir$${RESET}"
cd "$project_dir"
# Look for common Flask app files
for app_file in app.py main.py run.py; do
if [ -f "$app_file" ]; then
log_message "$${GREEN}🟢 Starting Flask application ($app_file) in $project_dir$${RESET}"
export FLASK_ENV=development
nohup python "$app_file" >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "flask" "5000" "python $app_file"
break
fi
done
fi
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "requirements.txt" -type f -print0)
}
# Function to detect and start Spring Boot projects
detect_spring_boot_projects() {
if [ "${ENABLE_SPRING_BOOT}" != "true" ]; then
return
fi
log_message "$${BLUE}🔍 Scanning for Spring Boot projects...$${RESET}"
# Maven projects
while IFS= read -r -d '' pom_xml; do
project_dir=$(dirname "$pom_xml")
# Check if it's a Spring Boot project
if grep -q "spring-boot" "$pom_xml" 2> /dev/null; then
log_message "$${GREEN}🍃 Found Spring Boot Maven project: $project_dir$${RESET}"
cd "$project_dir"
if command -v ./mvnw &> /dev/null; then
log_message "$${GREEN}🟢 Starting Spring Boot application with Maven wrapper in $project_dir$${RESET}"
nohup ./mvnw spring-boot:run >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "spring-boot" "8080" "./mvnw spring-boot:run"
elif command -v mvn &> /dev/null; then
log_message "$${GREEN}🟢 Starting Spring Boot application with Maven in $project_dir$${RESET}"
nohup mvn spring-boot:run >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "spring-boot" "8080" "mvn spring-boot:run"
fi
fi
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "pom.xml" -type f -print0)
# Gradle projects
while IFS= read -r -d '' build_gradle; do
project_dir=$(dirname "$build_gradle")
# Check if it's a Spring Boot project
if grep -q "spring-boot" "$build_gradle" 2> /dev/null; then
log_message "$${GREEN}🍃 Found Spring Boot Gradle project: $project_dir$${RESET}"
cd "$project_dir"
if command -v ./gradlew &> /dev/null; then
log_message "$${GREEN}🟢 Starting Spring Boot application with Gradle wrapper in $project_dir$${RESET}"
nohup ./gradlew bootRun >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "spring-boot" "8080" "./gradlew bootRun"
elif command -v gradle &> /dev/null; then
log_message "$${GREEN}🟢 Starting Spring Boot application with Gradle in $project_dir$${RESET}"
nohup gradle bootRun >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "spring-boot" "8080" "gradle bootRun"
fi
fi
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "build.gradle" -type f -print0)
}
# Function to detect and start Go projects
detect_go_projects() {
if [ "${ENABLE_GO}" != "true" ]; then
return
fi
log_message "$${BLUE}🔍 Scanning for Go projects...$${RESET}"
while IFS= read -r -d '' go_mod; do
project_dir=$(dirname "$go_mod")
log_message "$${GREEN}🐹 Found Go project: $project_dir$${RESET}"
cd "$project_dir"
# Look for main.go or check if there's a main function
if [ -f "main.go" ]; then
log_message "$${GREEN}🟢 Starting Go application in $project_dir$${RESET}"
nohup go run main.go >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "go" "8080" "go run main.go"
elif [ -f "cmd/main.go" ]; then
log_message "$${GREEN}🟢 Starting Go application (cmd/main.go) in $project_dir$${RESET}"
nohup go run cmd/main.go >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "go" "8080" "go run cmd/main.go"
fi
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "go.mod" -type f -print0)
}
# Function to detect and start PHP projects
detect_php_projects() {
if [ "${ENABLE_PHP}" != "true" ]; then
return
fi
log_message "$${BLUE}🔍 Scanning for PHP projects...$${RESET}"
while IFS= read -r -d '' composer_json; do
project_dir=$(dirname "$composer_json")
log_message "$${GREEN}🐘 Found PHP project: $project_dir$${RESET}"
cd "$project_dir"
# Look for common PHP entry points
for entry_file in index.php public/index.php; do
if [ -f "$entry_file" ]; then
log_message "$${GREEN}🟢 Starting PHP development server in $project_dir$${RESET}"
nohup php -S 0.0.0.0:8080 -t "$(dirname "$entry_file")" >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "php" "8080" "php -S 0.0.0.0:8080"
break
fi
done
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "composer.json" -type f -print0)
}
# Function to detect and start Rust projects
detect_rust_projects() {
if [ "${ENABLE_RUST}" != "true" ]; then
return
fi
log_message "$${BLUE}🔍 Scanning for Rust projects...$${RESET}"
while IFS= read -r -d '' cargo_toml; do
project_dir=$(dirname "$cargo_toml")
log_message "$${GREEN}🦀 Found Rust project: $project_dir$${RESET}"
cd "$project_dir"
# Check if it's a binary project (has [[bin]] or default main.rs)
if grep -q "\[\[bin\]\]" Cargo.toml 2> /dev/null || [ -f "src/main.rs" ]; then
log_message "$${GREEN}🟢 Starting Rust application in $project_dir$${RESET}"
nohup cargo run >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "rust" "8000" "cargo run"
fi
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "Cargo.toml" -type f -print0)
}
# Function to detect and start .NET projects
detect_dotnet_projects() {
if [ "${ENABLE_DOTNET}" != "true" ]; then
return
fi
log_message "$${BLUE}🔍 Scanning for .NET projects...$${RESET}"
while IFS= read -r -d '' csproj; do
project_dir=$(dirname "$csproj")
log_message "$${GREEN}🔷 Found .NET project: $project_dir$${RESET}"
cd "$project_dir"
log_message "$${GREEN}🟢 Starting .NET application in $project_dir$${RESET}"
nohup dotnet run >> "${LOG_PATH}" 2>&1 &
add_detected_project "$project_dir" "dotnet" "5000" "dotnet run"
done < <(find "${WORKSPACE_DIR}" -maxdepth "${SCAN_DEPTH}" -name "*.csproj" -type f -print0)
}
log_message "Starting auto-detection of development projects..."
# Expand workspace directory if it contains variables
WORKSPACE_DIR=$(eval echo "${WORKSPACE_DIR}")
# Check if workspace directory exists
if [ ! -d "$WORKSPACE_DIR" ]; then
log_message "$${RED}❌ Workspace directory does not exist: $WORKSPACE_DIR$${RESET}"
exit 1
fi
cd "$WORKSPACE_DIR"
# Run all detection functions
detect_npm_projects
detect_rails_projects
detect_django_projects
detect_flask_projects
detect_spring_boot_projects
detect_go_projects
detect_php_projects
detect_rust_projects
detect_dotnet_projects
log_message "$${GREEN}✅ Auto-start scan completed!$${RESET}"
log_message "$${YELLOW}💡 Check running processes with 'ps aux | grep -E \"(npm|rails|python|java|go|php|cargo|dotnet)\"'$${RESET}"
log_message "$${YELLOW}💡 View logs: tail -f ${LOG_PATH}$${RESET}"
# Set default port if no projects were detected
if [ "$FIRST_PORT_DETECTED" = false ]; then
echo "3000" > "$DETECTED_PORT_FILE"
log_message "$${YELLOW}⚠️ No projects detected - Preview app will default to port 3000$${RESET}"
fi