Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.25'

- name: Build binaries
run: |
cd apps/cli
mkdir -p release
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o release/radas-linux-amd64 .
GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o release/radas-darwin-amd64 .
GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o release/radas-darwin-arm64 .
GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o release/radas-windows-amd64.exe .

- name: Compress
run: |
cd apps/cli/release
tar czf radas-linux-amd64.tar.gz radas-linux-amd64
tar czf radas-darwin-amd64.tar.gz radas-darwin-amd64
tar czf radas-darwin-arm64.tar.gz radas-darwin-arm64
zip radas-windows-amd64.zip radas-windows-amd64.exe
rm -f radas-linux-amd64 radas-darwin-amd64 radas-darwin-arm64 radas-windows-amd64.exe

- name: GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: apps/cli/release/*

- name: Discord Notification
uses: Ilshidur/action-discord@master
env:
DISCORD_WEBHOOK: https://discord.com/api/webhooks/1417629457224569014/AcwTbsQRb5-psBaLJ8fUuXt2gaJRmNMWg9N3xwxLvVot6IOYmMgmWOPjRb3eJK89pgza
with:
args: |
πŸš€ **New Release: ${{ github.ref_name }}**

Published by ${{ github.actor }}

${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}
3 changes: 2 additions & 1 deletion apps/cli/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
.env.production.local

# Build artifacts
bin/
bin/
release/
33 changes: 33 additions & 0 deletions apps/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,39 @@ If you want to contribute to the project, please read the [contributing guide](h

</div>

## Security scanning

Detect committed secrets in the repo or any subdirectory using gitleaks.

```bash
# Emit SARIF 2.1.0 (default; pipe into GitHub Code Scanning or any SARIF viewer)
radas scan secrets > radas.sarif

# Human-readable table
radas scan secrets --format=table

# Limit to staged files (pre-commit hook)
radas scan secrets --staged

# Use a custom .gitleaks.toml
radas scan secrets --config=./.gitleaks.toml
```

Exit code 0 means no secrets found; exit code 1 means findings; exit code 2 means a scan error.

### GitHub Actions

```yaml
- name: Scan for secrets
run: radas scan secrets --format=sarif > radas.sarif || true
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: radas.sarif
```

Requires the `gitleaks/v8` Go library (handled by `go build` automatically).

## ⚠️ License

[`The Radas CLI`][repo_url] is free and open-source software licensed under the [Apache 2.0 License][repo_license_url], created and supported by [TreonStudio][author_url] with 🩡 for people and robots. Use it confidently in both personal and commercial projects. Official logo distributed under the [Creative Commons License][repo_cc_license_url] (CC BY-SA 4.0 International).
Expand Down
2 changes: 2 additions & 0 deletions apps/cli/cmd/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ func init() {
Cmd.AddCommand(InstallCmd)
Cmd.AddCommand(CleanCmd)
Cmd.AddCommand(FreshCmd)
Cmd.AddCommand(BeIgnoreCmd)
Cmd.AddCommand(DevCmd)
}
218 changes: 218 additions & 0 deletions apps/cli/cmd/backend/dev.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package backend

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/spf13/cobra"
"github.com/raizora/radas/v4/internal/config"
"github.com/raizora/radas/v4/internal/utils"
)

// DevCmd is the command to run the backend dev server
var DevCmd = &cobra.Command{
Use: "dev [--watch]",
Short: "Run backend dev server",
Long: `Start the backend development server. Auto-detects stack (Go, Elixir, PHP, Laravel)
and picks the right run command. Uses radas.yml run config if available.

Flags:
--watch enable hot-reload (auto-detects air, gow, reflex)
--tool <name> force a specific watch tool (air, gow, reflex, nodemon)
--port <n> override server port
`,
Run: func(cmd *cobra.Command, args []string) {
stack, dir := detectBackendStack()
if stack == "" {
fmt.Println("Could not detect backend stack. Supported: Golang, Elixir, PHP, Laravel.")
os.Exit(1)
}
fmt.Printf("Detected backend stack: %s (at %s)\n", stack, dir)

watch := cmd.Flags().Changed("watch")
watchTool, _ := cmd.Flags().GetString("tool")
port, _ := cmd.Flags().GetInt("port")

// Try to load radas.yml config for run settings
runCmd := ""
if cfgPath, err := config.FindConfig(); err == nil {
if cfg, err := config.ParseConfig(cfgPath); err == nil {
if cfg.Run.Command != "" {
runCmd = cfg.Run.Command
}
if !watch && cfg.Run.Watch {
watch = true
}
if watchTool == "" && cfg.Run.WatchTool != "" {
watchTool = cfg.Run.WatchTool
}
if port == 0 && cfg.Server.Port != 0 {
port = cfg.Server.Port
}
}
}

runDevServer(stack, dir, runCmd, watch, watchTool, port)
},
}

func init() {
DevCmd.Flags().BoolP("watch", "w", false, "enable hot-reload")
DevCmd.Flags().String("tool", "", "watch tool (air, gow, reflex, nodemon)")
DevCmd.Flags().Int("port", 0, "override server port")
}

func runDevServer(stack, dir, runCmd string, watch bool, watchTool string, port int) {
switch stack {
case "golang":
runGoDev(dir, runCmd, watch, watchTool, port)
case "elixir":
runElixirDev(dir, runCmd, port)
case "laravel", "php":
runPhpDev(dir, runCmd, stack, port)
}
}

func findMainPackage(dir string) string {
// Look for common main package locations
candidates := []string{
filepath.Join(dir, "cmd", "server"),
filepath.Join(dir, "cmd", "api"),
filepath.Join(dir, "cmd", "app"),
filepath.Join(dir, "cmd"),
dir,
}
for _, c := range candidates {
mainFile := filepath.Join(c, "main.go")
if _, err := os.Stat(mainFile); err == nil {
rel, _ := filepath.Rel(dir, c)
return rel
}
}
return "."
}

func findWatchTool() string {
for _, tool := range []string{"air", "gow", "reflex", "nodemon", "entr"} {
if utils.CheckIfCommandExists(tool) {
return tool
}
}
return ""
}

func runGoDev(dir, runCmd string, watch bool, watchTool string, port int) {
if runCmd == "" {
mainPkg := findMainPackage(dir)
runCmd = fmt.Sprintf("go run ./%s", mainPkg)
}

if watch && watchTool == "" {
watchTool = findWatchTool()
}

parts := strings.Fields(runCmd)

if watch && watchTool != "" {
// Wrap with watch tool
var cmd *exec.Cmd
switch watchTool {
case "air":
// air reads .air.toml; just run it
cmd = exec.Command("air")
case "gow":
cmd = exec.Command("gow", parts...)
case "reflex":
reflexArgs := []string{"-r", `\.go$`, "--"}
reflexArgs = append(reflexArgs, parts...)
cmd = exec.Command("reflex", reflexArgs...)
case "nodemon":
cmd = exec.Command("nodemon", "--exec", parts[0], strings.Join(parts[1:], " "))
case "entr":
fmt.Println("For entr, use: ls *.go | entr -r", runCmd)
fmt.Println("Falling back to direct run...")
cmd = exec.Command(parts[0], parts[1:]...)
}
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("Running: %s (via %s)\n", runCmd, watchTool)
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Dev server failed: %v\n", err)
os.Exit(1)
}
return
}

cmd := exec.Command(parts[0], parts[1:]...)
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("Running: %s\n", runCmd)
if port > 0 {
cmd.Env = append(os.Environ(), fmt.Sprintf("PORT=%d", port))
}
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Dev server failed: %v\n", err)
os.Exit(1)
}
}

func runElixirDev(dir, runCmd string, port int) {
if runCmd == "" {
runCmd = "mix phx.server"
}
parts := strings.Fields(runCmd)
cmd := exec.Command(parts[0], parts[1:]...)
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if port > 0 {
cmd.Env = append(os.Environ(), fmt.Sprintf("PORT=%d", port))
}
fmt.Printf("Running: %s\n", runCmd)
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Dev server failed: %v\n", err)
os.Exit(1)
}
}

func runPhpDev(dir, runCmd string, stack string, port int) {
if runCmd == "" {
if stack == "laravel" {
runCmd = "php artisan serve"
} else {
runCmd = "php -S localhost:8080 -t public"
}
}
parts := strings.Fields(runCmd)
cmd := exec.Command(parts[0], parts[1:]...)
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if port > 0 {
// Override port for artisan serve or php -S
if stack == "laravel" {
cmd.Env = append(os.Environ(), fmt.Sprintf("PORT=%d", port))
} else {
// Replace port in php -S command
for i, p := range parts {
if p == "localhost:8080" {
parts[i] = fmt.Sprintf("localhost:%d", port)
}
}
cmd = exec.Command(parts[0], parts[1:]...)
cmd.Dir = dir
}
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("Running: %s\n", strings.Join(parts, " "))
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Dev server failed: %v\n", err)
os.Exit(1)
}
}
5 changes: 4 additions & 1 deletion apps/cli/cmd/backend/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
// DoctorCmd is the command to check Backend tools
var DoctorCmd = &cobra.Command{
Use: "doctor",
Short: "Check Backend tools installation",
Short: "Check Backend tools installation & project configuration",
Long: `Check if Go, Elixir, and other backend tools are installed and ready to use.`,
Run: func(cmd *cobra.Command, args []string) {
runBackendDoctor()
Expand All @@ -21,4 +21,7 @@ func runBackendDoctor() {
checker.CheckElixir()
checker.CheckRust()
checker.CheckMaven()

// Detect project database
checker.PrintDatabaseResult(".")
}
Loading