|
#!/usr/bin/env bash |
|
set -euo pipefail |
|
|
|
# Defaults |
|
NAME="" |
|
GROUP_ID="com.example" |
|
ARTIFACT_ID="" |
|
PACKAGE="" |
|
VERSION="0.0.1-SNAPSHOT" |
|
DESCRIPTION="" |
|
SPRING_BOOT_VERSION="" # Empty = use Initializr default |
|
JAVA_VERSION="17" |
|
PACKAGING="jar" |
|
LANGUAGE="java" |
|
DEPENDENCIES="web" |
|
OUTPUT_DIR="$(pwd)" |
|
DRY_RUN=false |
|
FORCE=false |
|
INITIALIZR_URL="https://start.spring.io" |
|
|
|
usage() { |
|
cat <<'EOF' |
|
Usage: new-spring.sh [-n NAME] [-g GROUP_ID] [-a ARTIFACT_ID] [-p PACKAGE] |
|
[-v VERSION] [-d DESCRIPTION] [-B SPRING_BOOT_VERSION] |
|
[-J JAVA_VERSION] [-P PACKAGING] [-L LANGUAGE] |
|
[-D DEPENDENCIES] [-o OUTPUT_DIR] [--dry-run] [-f] [-h] |
|
|
|
Options: |
|
-n NAME Human-friendly project/app name (used to derive artifactId if -a not set) |
|
-g GROUP_ID Maven groupId (default: com.example) |
|
-a ARTIFACT_ID Maven artifactId (default: derived from NAME) |
|
-p PACKAGE Base Java package (default: GROUP_ID + '.' + artifactId with '-' -> '_') |
|
-v VERSION Project version (default: 0.0.1-SNAPSHOT) |
|
-d DESCRIPTION Project description (default: Demo project for Spring Boot) |
|
-B SPRING_BOOT_VERSION Spring Boot version (default: latest stable from Initializr) |
|
-J JAVA_VERSION Java version: 8, 11, 17, 21, 23 (default: 17) |
|
-P PACKAGING Packaging type: jar, war (default: jar) |
|
-L LANGUAGE Language: java, kotlin, groovy (default: java) |
|
-D DEPENDENCIES Comma-separated dependency IDs (default: web) |
|
Examples: web,data-jpa,postgresql,security,actuator |
|
Use --list-deps to see all available dependencies |
|
-o OUTPUT_DIR Where to generate the project (default: current dir) |
|
--list-deps List all available dependencies and exit |
|
--dry-run Print the curl command without executing |
|
-f Force: skip existence checks on target directory |
|
-h Show this help |
|
|
|
Common Dependencies: |
|
web - Spring Web (REST APIs) |
|
data-jpa - Spring Data JPA (database ORM) |
|
postgresql - PostgreSQL Driver |
|
mysql - MySQL Driver |
|
h2 - H2 Database (in-memory) |
|
security - Spring Security |
|
actuator - Spring Boot Actuator (monitoring) |
|
devtools - Spring Boot DevTools (hot reload) |
|
lombok - Lombok (reduce boilerplate) |
|
validation - Validation (Bean Validation) |
|
thymeleaf - Thymeleaf (template engine) |
|
kafka - Spring for Apache Kafka |
|
amqp - Spring AMQP (RabbitMQ) |
|
data-mongodb - Spring Data MongoDB |
|
data-redis - Spring Data Redis |
|
cloud-config - Config Client (Spring Cloud) |
|
cloud-eureka - Eureka Discovery Client |
|
|
|
Examples: |
|
# Basic web application |
|
./new-spring.sh -n "My API" |
|
|
|
# REST API with PostgreSQL |
|
./new-spring.sh -n "User Service" -D web,data-jpa,postgresql,validation |
|
|
|
# Full-stack with security |
|
./new-spring.sh -n "Admin Portal" -D web,data-jpa,postgresql,security,thymeleaf |
|
|
|
# Microservice setup |
|
./new-spring.sh -n "Order Service" -D web,data-jpa,postgresql,actuator,cloud-eureka |
|
EOF |
|
} |
|
|
|
slugify_artifact() { |
|
local s="${1,,}" |
|
s="${s// /-}" |
|
s="${s//_/-}" |
|
s="$(echo "$s" | sed -E 's/[^a-z0-9.-]+/-/g; s/-+/-/g; s/^-+//; s/-+$//')" |
|
if [[ -z "$s" ]]; then s="app"; fi |
|
echo "$s" |
|
} |
|
|
|
pkg_from_group_and_artifact() { |
|
local gid="$1" |
|
local aid="$2" |
|
local seg="${aid//-/_}" |
|
seg="$(echo "$seg" | sed -E 's/[^A-Za-z0-9_]+/_/g')" |
|
echo "${gid}.${seg}" |
|
} |
|
|
|
require_cmd() { |
|
command -v "$1" >/dev/null 2>&1 || { echo "Error: '$1' not found in PATH" >&2; exit 127; } |
|
} |
|
|
|
list_dependencies() { |
|
echo "Fetching available dependencies from Spring Initializr..." |
|
echo |
|
require_cmd curl |
|
require_cmd jq |
|
|
|
local response |
|
response=$(curl -s "${INITIALIZR_URL}/metadata/dependencies" 2>/dev/null || echo "") |
|
|
|
if [[ -z "$response" ]]; then |
|
echo "Error: Could not fetch dependencies from ${INITIALIZR_URL}" >&2 |
|
exit 1 |
|
fi |
|
|
|
echo "$response" | jq -r '.dependencies | to_entries[] | .value.values[] | "\(.id) - \(.name): \(.description)"' | column -t -s '-' |
|
exit 0 |
|
} |
|
|
|
# Parse args |
|
while (( "$#" )); do |
|
case "$1" in |
|
-n) NAME="$2"; shift 2;; |
|
-g) GROUP_ID="$2"; shift 2;; |
|
-a) ARTIFACT_ID="$2"; shift 2;; |
|
-p) PACKAGE="$2"; shift 2;; |
|
-v) VERSION="$2"; shift 2;; |
|
-d) DESCRIPTION="$2"; shift 2;; |
|
-B) SPRING_BOOT_VERSION="$2"; shift 2;; |
|
-J) JAVA_VERSION="$2"; shift 2;; |
|
-P) PACKAGING="$2"; shift 2;; |
|
-L) LANGUAGE="$2"; shift 2;; |
|
-D) DEPENDENCIES="$2"; shift 2;; |
|
-o) OUTPUT_DIR="$2"; shift 2;; |
|
--list-deps) list_dependencies;; |
|
--dry-run) DRY_RUN=true; shift;; |
|
-f) FORCE=true; shift;; |
|
-h|--help) usage; exit 0;; |
|
*) echo "Unknown option: $1" >&2; usage; exit 2;; |
|
esac |
|
done |
|
|
|
# Derive artifactId from NAME if needed |
|
if [[ -z "${ARTIFACT_ID}" ]]; then |
|
if [[ -z "${NAME}" ]]; then |
|
echo "Error: provide at least -n NAME or -a ARTIFACT_ID" >&2 |
|
usage; exit 2 |
|
fi |
|
ARTIFACT_ID="$(slugify_artifact "$NAME")" |
|
fi |
|
|
|
# Derive package if missing |
|
if [[ -z "${PACKAGE}" ]]; then |
|
PACKAGE="$(pkg_from_group_and_artifact "$GROUP_ID" "$ARTIFACT_ID")" |
|
fi |
|
|
|
# Derive description if missing |
|
if [[ -z "${DESCRIPTION}" ]]; then |
|
if [[ -n "${NAME}" ]]; then |
|
DESCRIPTION="${NAME}" |
|
else |
|
DESCRIPTION="Demo project for Spring Boot" |
|
fi |
|
fi |
|
|
|
# Prepare target directory check |
|
TARGET_DIR="${OUTPUT_DIR%/}/${ARTIFACT_ID}" |
|
if ! $FORCE; then |
|
if [[ -e "$TARGET_DIR" ]]; then |
|
echo "Error: target directory already exists: $TARGET_DIR (use -f to override)" >&2 |
|
exit 3 |
|
fi |
|
fi |
|
|
|
require_cmd curl |
|
require_cmd unzip |
|
|
|
# Build Spring Initializr URL parameters |
|
params=( |
|
"type=maven-project" |
|
"language=${LANGUAGE}" |
|
"packaging=${PACKAGING}" |
|
"javaVersion=${JAVA_VERSION}" |
|
"groupId=${GROUP_ID}" |
|
"artifactId=${ARTIFACT_ID}" |
|
"name=${ARTIFACT_ID}" |
|
"packageName=${PACKAGE}" |
|
"version=${VERSION}" |
|
"description=${DESCRIPTION}" |
|
"dependencies=${DEPENDENCIES}" |
|
) |
|
|
|
# Add Spring Boot version if specified |
|
[[ -n "$SPRING_BOOT_VERSION" ]] && params+=("bootVersion=${SPRING_BOOT_VERSION}") |
|
|
|
# Build full URL |
|
url="${INITIALIZR_URL}/starter.zip" |
|
query_string="" |
|
for param in "${params[@]}"; do |
|
if [[ -z "$query_string" ]]; then |
|
query_string="?${param}" |
|
else |
|
query_string="${query_string}&${param}" |
|
fi |
|
done |
|
full_url="${url}${query_string}" |
|
|
|
echo ">>> Configuration:" |
|
echo " Project: ${ARTIFACT_ID}" |
|
echo " Group: ${GROUP_ID}" |
|
echo " Package: ${PACKAGE}" |
|
echo " Version: ${VERSION}" |
|
echo " Java: ${JAVA_VERSION}" |
|
echo " Packaging: ${PACKAGING}" |
|
echo " Language: ${LANGUAGE}" |
|
echo " Dependencies: ${DEPENDENCIES}" |
|
[[ -n "$SPRING_BOOT_VERSION" ]] && echo " Spring Boot: ${SPRING_BOOT_VERSION}" |
|
echo " Output: ${TARGET_DIR}" |
|
echo |
|
|
|
echo ">>> Downloading from Spring Initializr..." |
|
echo "URL: ${full_url}" |
|
echo |
|
|
|
if $DRY_RUN; then |
|
echo "(dry run)"; exit 0 |
|
fi |
|
|
|
# Create output directory if needed |
|
mkdir -p "${OUTPUT_DIR}" |
|
|
|
# Download and extract |
|
TEMP_ZIP="${OUTPUT_DIR}/${ARTIFACT_ID}.zip" |
|
if curl -f -o "${TEMP_ZIP}" "${full_url}"; then |
|
echo ">>> Extracting to ${TARGET_DIR}..." |
|
unzip -q "${TEMP_ZIP}" -d "${OUTPUT_DIR}" |
|
rm "${TEMP_ZIP}" |
|
|
|
echo |
|
echo "✓ Project generated at: ${TARGET_DIR}" |
|
echo " Next steps:" |
|
echo " cd ${TARGET_DIR}" |
|
echo " ./mvnw spring-boot:run" |
|
echo " # or run tests:" |
|
echo " ./mvnw test" |
|
else |
|
echo "Error: Failed to download project from Spring Initializr" >&2 |
|
[[ -f "${TEMP_ZIP}" ]] && rm "${TEMP_ZIP}" |
|
exit 1 |
|
fi |