diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..8af99709 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,27 @@ +{ + "name": "Open Ecosystem Challenges", + "image": "mcr.microsoft.com/devcontainers/base:bullseye", + "features": { + "ghcr.io/devcontainers/features/python:1": { + "version": "3.12" + } + }, + "postCreateCommand": "bash .devcontainer/post-create.sh", + "forwardPorts": [8000], + "portsAttributes": { + "8000": { + "label": "MkDocs", + "onAutoForward": "openPreview" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "yzhang.markdown-all-in-one", + "davidanson.vscode-markdownlint" + ] + } + } +} + diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100644 index 00000000..aa256854 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +lib/shared/init.sh + +echo "→ Installing mkdocs-material..." +pip install --quiet mkdocs-material mkdocs-monorepo-plugin + +echo "✓ Done! Run 'mkdocs serve' to start the docs server." + diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..f0ad6c65 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +# ============================================================================== +# Open Ecosystem Challenges +# ============================================================================== + +.PHONY: help new-adventure docs + +# Default target - show help +help: + @echo "Open Ecosystem Challenges - Available Commands:" + @echo "" + @echo " make new-adventure Scaffold a new adventure from an approved idea" + @echo " make docs Start the MkDocs documentation server" + +# ------------------------------------------------------------------------------ + +new-adventure: + @scripts/new-adventure.sh + +docs: + @mkdocs serve + diff --git a/adventures/planned/.gitkeep b/adventures/planned/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docs/contributing/adventure-ideas.md b/docs/contributing/adventure-ideas.md index 357fa9db..149b23bb 100644 --- a/docs/contributing/adventure-ideas.md +++ b/docs/contributing/adventure-ideas.md @@ -59,7 +59,7 @@ Adventure 01 is a useful reference: Beginner introduces Argo CD ApplicationSets 1. **Create a new file** in `ideas/` named `your-adventure-name.md` 2. **Use the template** below to describe your idea -3. **[Open a pull request](https://github.com/dynatrace-oss/open-ecosystem-challenges/compare)** with the title `Adventure Idea: Your Adventure Name` +3. **[Open a pull request](https://github.com/dynatrace-oss/open-ecosystem-challenges/compare)** with the title `Adventure Idea: [emoji] Your Adventure Name` No issue required. Submit your idea directly as a PR. @@ -68,7 +68,7 @@ No issue required. Submit your idea directly as a PR. Use this structure for your proposal. The three-level format is recommended but not required. Adjust based on your idea. ```markdown -# Adventure Idea: [Your Adventure Name] +# Adventure Idea: [emoji] [Your Adventure Name] ## Overview diff --git a/docs/contributing/adventures.md b/docs/contributing/adventures.md old mode 100644 new mode 100755 index 4a15b47e..dc7c2062 --- a/docs/contributing/adventures.md +++ b/docs/contributing/adventures.md @@ -22,8 +22,6 @@ An adventure consists of: Use `00` as the adventure number during development. When your adventure is scheduled for release, maintainers will assign the final number and move it out of `planned/`. -If `adventures/planned/` doesn't exist yet, create it. - ``` adventures/planned/00-adventure-name/ ├── README.md # Brief intro + link to docs @@ -53,35 +51,37 @@ adventures/planned/00-adventure-name/ ## Step-by-Step -### 1. Configure the Devcontainer - -Start by setting up the environment. Check an existing adventure with a similar setup as a blueprint for what you need. For Kubernetes-based adventures, [Adventure 01](../../adventures/01-echoes-lost-in-orbit/) is a good reference. +### 1. Scaffold the Files -Create a devcontainer for each level in `.devcontainer/00-adventure-name_NN-level/` (e.g., `01-beginner`, `02-intermediate`, `03-expert`). The number prefix on levels ensures proper sorting in the GitHub UI. +Run the scaffolding script to generate the skeleton for your adventure level: -**devcontainer.json:** -```json -{ - "name": "Adventure 00 | 🟢 Beginner", - "image": "mcr.microsoft.com/devcontainers/base:bullseye", - "features": { - // Add required features (docker, kubectl, etc.) - }, - "postCreateCommand": "bash .devcontainer/00-adventure-name_01-beginner/post-create.sh", - "postStartCommand": "bash .devcontainer/00-adventure-name_01-beginner/post-start.sh", - "forwardPorts": [] -} +```bash +make new-adventure ``` +This will prompt you to select an adventure and level, then generate: + +- `adventures/planned/00-adventure-name/` — adventure base with `README.md`, `mkdocs.yaml`, and `docs/index.md` +- `adventures/planned/00-adventure-name/docs/.md` — level guide +- `adventures/planned/00-adventure-name//verify.sh` — verification script skeleton +- `.devcontainer/00-adventure-name_NN-level/` — `devcontainer.json`, `post-create.sh`, `post-start.sh` + +Search for `TODO` in the generated files to find everything that needs filling in. + +### 2. Configure the Devcontainer + +Open the generated `.devcontainer/00-adventure-name_NN-level/` files and fill in the TODOs. + +For Kubernetes-based adventures, [Adventure 01](../../adventures/01-echoes-lost-in-orbit/) is a good reference for what features and setup scripts to use. + **post-create.sh** runs once when the container is created: -- Install CLI tools +- Install CLI tools using setup scripts from `lib/` - Pull container images - Set up one-time configurations **post-start.sh** runs every time the container starts: - Start services (databases, clusters, etc.) - Apply initial state -- Set up port forwarding **Infrastructure constraints:** @@ -89,7 +89,7 @@ Codespaces run on 2 cores and 8 GB RAM by default. Design your adventure within Post-create should finish in under 15 minutes, but aim for well under that. -### 2. Build the Working Solution +### 3. Build the Working Solution Implement the fully working version first. This is what the solved challenge looks like where everything works correctly. @@ -98,7 +98,7 @@ This approach helps you: - Ensure the challenge is actually solvable - Have a reference implementation for the solution walkthrough -### 3. Introduce the Challenges +### 4. Introduce the Challenges Work backwards from your working solution to create the "broken" state participants will fix. @@ -110,48 +110,21 @@ Good challenges are: Not sure if a challenge belongs at Beginner, Intermediate, or Expert? See [Calibrating Difficulty](adventure-ideas.md#calibrating-difficulty) for concrete signals and time expectations. -### 4. Write the Documentation +### 5. Write the Documentation -**Level guide (e.g., `docs/beginner.md`):** -- Story context -- Objectives (what success looks like) +Fill in the generated `docs/.md` — it already contains the story, objectives, and learning outcomes from the idea file. Add: +- Architecture overview (how the level is set up) +- UI access instructions with port numbers +- Where to start investigating - Helpful links to external docs -- No spoilers -See [Adventure 01's beginner level](../../adventures/01-echoes-lost-in-orbit/docs/beginner.md) for a good example. +No spoilers — save those for a `solutions/.md` file. -**MkDocs configuration (`mkdocs.yaml`):** -```yaml -site_name: '00: Adventure Name' - -nav: - - Introduction: index.md - - 'Beginner': beginner.md - - 'Intermediate': intermediate.md - - 'Expert': expert.md - - 'Solutions': - - 'Beginner': solutions/beginner.md - - 'Intermediate': solutions/intermediate.md - - 'Expert': solutions/expert.md -``` - -### 5. Create the Verification Script - -Each level needs a `verify.sh` that validates the solution and generates a completion certificate. - -```bash -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/../../../lib/scripts/loader.sh" +See [Adventure 01's beginner level](../../adventures/01-echoes-lost-in-orbit/docs/beginner.md) for a good example. -# Your validation logic here using shared helpers -# Browse lib/scripts/ to see what's available +### 6. Create the Verification Script -# On success, generate certificate -check_submission_readiness "adventure-name" "level" -``` +Fill in the generated `/verify.sh`. It already has the boilerplate wired up — add your checks between the `print_sub_header` and the summary block. A good verification script: - Passes when the challenge is solved correctly @@ -160,7 +133,9 @@ A good verification script: **Check outcomes, not implementation.** Verify the state the participant should have reached — a service is healthy, traces are present in Jaeger, a metric is being collected — not how they got there. File content checks (`check_file_contains`) are a last resort: they break for valid alternative solutions and reward copy-pasting over understanding. If your objective says "see traces in Jaeger", your verification should check that traces exist, not that a specific import was added. -### 6. Final Test Run +Browse `lib/scripts/` to see the available helper functions. + +### 7. Final Test Run Before submitting: @@ -174,4 +149,4 @@ Before submitting: - **[Open a draft PR early.](https://github.com/dynatrace-oss/open-ecosystem-challenges/compare)** Get feedback on structure before completing everything. - **Ship one level at a time.** Each level gets its own PR — start with one, get it working, then build the next. Use `Part of #` on all but the last PR, and `Closes #` on the final one so the tracking issue closes automatically. - **Test on slow connections.** Codespace startup time matters. -- **Write clear error messages.** Help participants understand what went wrong without giving away the solution. \ No newline at end of file +- **Write clear error messages.** Help participants understand what went wrong without giving away the solution. diff --git a/ideas/.implemented/echoes-lost-in-orbit.md b/ideas/.implemented/echoes-lost-in-orbit.md index ee84723e..05c8af58 100644 --- a/ideas/.implemented/echoes-lost-in-orbit.md +++ b/ideas/.implemented/echoes-lost-in-orbit.md @@ -1,10 +1,11 @@ -# Adventure Idea: Echoes Lost in Orbit +# Adventure Idea: 🛰️ Echoes Lost in Orbit ## Overview **Theme:** The Intergalactic Union's communication network is in disarray. The Echo Server has gone silent, a language pack rollout for a new species is failing, and the hyperspace transport system needs robust observability. As an SRE, you'll restore order by fixing GitOps configurations, stabilizing progressive delivery, and building an observability pipeline. **Skills:** + - Deploy reliably across multiple environments - Release safely using progressive delivery - Observe distributed systems with tracing and metrics @@ -12,6 +13,7 @@ **Technologies:** Argo CD, Argo Rollouts, OpenTelemetry, Prometheus, Jaeger, Kubernetes **Levels:** + - 🟢 Beginner: Fix a broken multi-environment GitOps setup - 🟡 Intermediate: Stabilize a failing canary rollout with metrics-based analysis - 🔴 Expert: Build an observability pipeline to validate traffic before promoting releases diff --git a/scripts/new-adventure.sh b/scripts/new-adventure.sh new file mode 100755 index 00000000..89be0f8c --- /dev/null +++ b/scripts/new-adventure.sh @@ -0,0 +1,408 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +IDEAS_DIR="$REPO_ROOT/ideas" + +# ─── Select adventure ──────────────────────────────────────────────────────── + +selected_slug=$(find "$IDEAS_DIR" -maxdepth 1 -name "*.md" -exec basename {} .md \; | sort \ + | gum choose --header "Which adventure do you want to scaffold?") +selected_file="$IDEAS_DIR/$selected_slug.md" +adventure_header=$(grep -m1 '^# Adventure Idea:' "$selected_file" | sed 's/^# Adventure Idea: *//') +adventure_emoji=$(echo "$adventure_header" | awk '{print $1}') +adventure_name=$(echo "$adventure_header" | cut -d' ' -f2-) + +# ─── Select level ──────────────────────────────────────────────────────────── + +level_lines=$(grep '^### ' "$selected_file" | sed 's/^### //') +selected_level=$(echo "$level_lines" | gum choose --header "Which level do you want to scaffold?") + +# ─── Parse level metadata ───────────────────────────────────────────────────── + +level_emoji=$(echo "$selected_level" | awk '{print $1}') +level_difficulty=$(echo "$selected_level" | awk '{print $2}' | tr -d ':') +level_name=$(echo "$selected_level" | sed 's/[^ ]* [^:]*: //') +level_slug=$(echo "$level_difficulty" | tr '[:upper:]' '[:lower:]') + +# position of selected level among ### headings (1st = 01, 2nd = 02, ...) +level_number=$(echo "$level_lines" \ + | awk -v target="$selected_level" '{n++; if ($0 == target) {printf "%02d", n; exit}}') + +echo "" +echo "Adventure : $adventure_emoji $adventure_name ($selected_slug)" +echo "Level : $level_emoji $level_difficulty: $level_name" +echo "Slug : $level_slug (level $level_number)" +echo "" + +# ─── Scaffold adventure base ────────────────────────────────────────────────── + +ADVENTURE_DIR="$REPO_ROOT/adventures/planned/00-$selected_slug" +adventure_technologies=$(grep -m1 '^\*\*Technologies:\*\*' "$selected_file" | sed 's/^\*\*Technologies:\*\* *//') +adventure_theme=$(awk ' + /^\*\*Theme:\*\*/ { sub(/^\*\*Theme:\*\* */, ""); p=1; print; next } + p && /^\*\*/ { exit } + p { print } +' "$selected_file") + +if [[ ! -d "$ADVENTURE_DIR" ]]; then + echo "Creating adventure base at adventures/planned/00-$selected_slug/ ..." + mkdir -p "$ADVENTURE_DIR/docs" + + cat > "$ADVENTURE_DIR/README.md" << EOF +# $adventure_emoji Adventure 00: $adventure_name + +$adventure_theme + +**Technologies:** $adventure_technologies + +The entire **infrastructure is pre-provisioned in your Codespace** +**You don't need to set up anything locally. Just focus on solving the problem.** + +## 🚀 Ready to Start? + +[Choose your level](https://dynatrace-oss.github.io/open-ecosystem-challenges/00-$selected_slug/) and begin +learning! +EOF + + cat > "$ADVENTURE_DIR/mkdocs.yaml" << EOF +site_name: '$adventure_emoji 00: $adventure_name' + +nav: + - Introduction: index.md +EOF + + cat > "$ADVENTURE_DIR/docs/index.md" << EOF +# $adventure_emoji Adventure 00: $adventure_name + + + +## 🪐 The Backstory + +$adventure_theme + + + +## 🎮 Choose Your Level + +Each level is a standalone challenge with its own Codespace that builds on the story while being technically +independent — pick your level and start wherever you feel comfortable. +EOF + + echo "✅ Adventure base created." +else + echo "ℹ️ Adventure base already exists, skipping." +fi + +# ─── Scaffold level doc ─────────────────────────────────────────────────────── + +# Extracts a #### subsection for the selected level from the idea file +extract_level_section() { + local section="$1" + awk -v level="$selected_level" -v section="$section" ' + /^### / { in_level = ($0 == "### " level); next } + in_level && $0 == "#### " section { in_section=1; next } + in_level && in_section && (/^#### / || /^---/) { in_section=0 } + in_level && in_section { print } + ' "$selected_file" +} + +level_summary=$(grep "^- $level_emoji $level_difficulty:" "$selected_file" | sed "s/^- $level_emoji [^:]*: //") +level_story=$(extract_level_section "Story") +level_objective=$(extract_level_section "Objective") +level_learnings=$(extract_level_section "What You'll Learn") +level_tools=$(extract_level_section "Tools & Infrastructure") + +LEVEL_DOC="$ADVENTURE_DIR/docs/$level_slug.md" + +if [[ ! -f "$LEVEL_DOC" ]]; then + echo "Creating level doc at docs/$level_slug.md ..." + TICK='```' + + cat > "$LEVEL_DOC" << EOF +# $level_emoji $level_difficulty: $level_name +$level_story + + + +🏗️ Architecture + + + +## 🎯 Objective +$level_objective + +## 🧠 What You'll Learn +$level_learnings + +## 🧰 Toolbox + +Your Codespace comes pre-configured with the following tools: +$level_tools + + + +## ⏰ Deadline + + + +> ℹ️ You can still complete the challenge after this date, but points will only be awarded for submissions before the +> deadline. + +## 💬 Join the discussion + + +Share your solutions and questions in +the [challenge thread](TODO) +in the Open Ecosystem Community. + +## ✅ How to Play + + + +### 1. Start Your Challenge + +> 📖 **First time?** Check out the [Getting Started Guide](../../start-a-challenge) for detailed instructions on +> forking, starting a Codespace, and waiting for infrastructure setup. + +Quick start: + +- Fork the [repo](https://github.com/dynatrace-oss/open-ecosystem-challenges/) +- Create a Codespace +- Select "$adventure_emoji Adventure 00 | $level_emoji $level_difficulty ($level_name)" +- Wait a couple of minutes for the environment to initialize (\`Cmd/Ctrl + Shift + P\` → \`View Creation Log\` to view progress) + + + +### 2. Access the UIs + +- Open the **Ports** tab in the bottom panel to access the following UIs + +#### Some UI you might use + + + + +- Find the tool row (port NN) and click the forwarded address + +### 3. Implement the Objective + + + +Review the [🎯 Objective](#objective) section to understand what a successful solution looks like. + +#### Where to Look + + + +#### How to Run + + + +#### Helpful Documentation + + + +### 4. Verify Your Solution + +Once you think you've solved the challenge, run the verification script: + +${TICK}bash +./verify.sh +${TICK} + +**If the verification fails:** + +The script will tell you which checks failed. Fix the issues and run it again. + +**If the verification passes:** + +1. The script will check if your changes are committed and pushed. +2. Follow the on-screen instructions to commit your changes if needed. +3. Once everything is ready, the script will generate a **Certificate of Completion**. + +4. **Copy this certificate** and paste it into + the [challenge thread](TODO) + to claim your victory! 🏆 +EOF + + echo " - '$level_emoji $level_difficulty': $level_slug.md" >> "$ADVENTURE_DIR/mkdocs.yaml" + + cat >> "$ADVENTURE_DIR/docs/index.md" << EOF + +### $level_emoji $level_difficulty: $level_name + +- **Status:** 🚧 Coming Soon +- **Topics:** $adventure_technologies + +$level_summary + +[**Start the $level_difficulty Challenge**](./$level_slug.md){ .md-button .md-button--primary } +EOF + + echo "✅ Level doc created, added to mkdocs.yaml, and level card added to index.md." +else + echo "ℹ️ Level doc already exists, skipping." +fi + +# ─── Scaffold verify.sh ─────────────────────────────────────────────────────── + +VERIFY_SCRIPT="$ADVENTURE_DIR/$level_slug/verify.sh" + +if [[ ! -f "$VERIFY_SCRIPT" ]]; then + echo "Creating verification script at $level_slug/verify.sh ..." + mkdir -p "$ADVENTURE_DIR/$level_slug" + + cat > "$VERIFY_SCRIPT" << EOF +#!/usr/bin/env bash +set -euo pipefail + +# Load shared libraries +SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)" +# shellcheck disable=SC1091 +source "\$SCRIPT_DIR/../../../../lib/scripts/loader.sh" + +DOCS_URL="https://dynatrace-oss.github.io/open-ecosystem-challenges/00-$selected_slug/$level_slug" + +print_header \\ + 'Challenge 00: $adventure_name' \\ + 'Level $level_number: $level_name' \\ + 'Verification' + +# Init test counters +TESTS_PASSED=0 +TESTS_FAILED=0 +FAILED_CHECKS=() + +check_prerequisites # TODO: list required tools (e.g. kubectl curl jq) + +print_sub_header "Running verification checks..." + +# TODO: add verification checks +# Examples: +# check_jaeger_traces "service-name" "Traces present" "No traces found." +# is_app_reachable "http://localhost:30100" "App is reachable" "App not reachable" +# check_file_contains "file.py" "expected_string" "Check label" "Hint if it fails" + +# ============================================================================= +# Summary +# ============================================================================= + +failed_checks_json="[]" +if [[ -n "\${FAILED_CHECKS[*]:-}" ]]; then + failed_checks_json=\$(printf '%s\n' "\${FAILED_CHECKS[@]}" | jq -R . | jq -s .) +fi + +if [[ \$TESTS_FAILED -gt 0 ]]; then + track_verification_completed "failed" "\$failed_checks_json" + print_verification_summary "$selected_slug" "\$DOCS_URL" + exit 1 +fi + +track_verification_completed "success" "\$failed_checks_json" + +print_header "Test Results Summary" +print_success "✅ PASSED: All \$TESTS_PASSED verification checks passed!" +print_new_line + +check_submission_readiness "00-$selected_slug" "$level_slug" +EOF + + chmod +x "$VERIFY_SCRIPT" + echo "✅ Verification script created." +else + echo "ℹ️ Verification script already exists, skipping." +fi + +# ─── Scaffold devcontainer ──────────────────────────────────────────────────── + +DEVCONTAINER_NAME="00-${selected_slug}_${level_number}-${level_slug}" +DEVCONTAINER_DIR="$REPO_ROOT/.devcontainer/$DEVCONTAINER_NAME" + +if [[ ! -d "$DEVCONTAINER_DIR" ]]; then + echo "Creating devcontainer at .devcontainer/$DEVCONTAINER_NAME/ ..." + mkdir -p "$DEVCONTAINER_DIR" + + cat > "$DEVCONTAINER_DIR/devcontainer.json" << EOF +{ + "name": "$adventure_emoji Adventure 00 | $level_emoji $level_difficulty ($level_name)", + "image": "mcr.microsoft.com/devcontainers/base:bullseye", + "workspaceFolder": "/workspaces/\${localWorkspaceFolderBasename}/adventures/planned/00-$selected_slug/$level_slug", + "features": { + // TODO: add required features (e.g. "ghcr.io/devcontainers/features/docker-in-docker:2": {}) + }, + "postCreateCommand": "bash /workspaces/\${localWorkspaceFolderBasename}/.devcontainer/$DEVCONTAINER_NAME/post-create.sh", + "postStartCommand": "bash /workspaces/\${localWorkspaceFolderBasename}/.devcontainer/$DEVCONTAINER_NAME/post-start.sh", + "customizations": { + "codespaces": { + "openFiles": [ + "adventures/planned/00-$selected_slug/README.md" + ], + "permissions": { + "codespaces": "write" + } + } + }, + "forwardPorts": [], + "portsAttributes": { + // TODO: add port labels (e.g. "30100": { "label": "ArgoCD", "onAutoForward": "notify" }) + }, + "otherPortsAttributes": { + "onAutoForward": "ignore" + } +} +EOF + + cat > "$DEVCONTAINER_DIR/post-create.sh" << EOF +#!/usr/bin/env bash +set -e + +REPO_ROOT="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")/../.." && pwd)" + +# shellcheck disable=SC1091 +source "\$REPO_ROOT/lib/scripts/tracker.sh" +set_tracking_context "$selected_slug" "$level_slug" +track_codespace_created + +"\$REPO_ROOT/lib/shared/init.sh" + +# TODO: install & configure the tools you need by using the setup scripts located in `/lib` (e.g. "\$REPO_ROOT/lib/kubernetes/init.sh") +EOF + + cat > "$DEVCONTAINER_DIR/post-start.sh" << EOF +#!/usr/bin/env bash +set -e + +REPO_ROOT="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")/../.." && pwd)" +CHALLENGE_DIR="\$REPO_ROOT/adventures/planned/00-$selected_slug/$level_slug" + +echo "✨ Starting $adventure_name - $level_difficulty Level" + +# TODO: start services & apply initial state + +# shellcheck disable=SC1091 +source "\$REPO_ROOT/lib/scripts/tracker.sh" +set_tracking_context "$selected_slug" "$level_slug" +track_codespace_initialized +EOF + + chmod +x "$DEVCONTAINER_DIR/post-create.sh" "$DEVCONTAINER_DIR/post-start.sh" + echo "✅ Devcontainer created." +else + echo "ℹ️ Devcontainer already exists, skipping." +fi + +# ─── Done ───────────────────────────────────────────────────────────────────── + +gum style \ + --border rounded --border-foreground 212 \ + --padding "1 2" --margin "1 0" \ + "$(gum style --foreground 212 --bold "🎉 $adventure_emoji $adventure_name | $level_emoji $level_difficulty is ready!")" \ + "" \ + "Search for TODO in the generated files and fill them in:" \ + " adventures/planned/00-$selected_slug/" \ + " .devcontainer/$DEVCONTAINER_NAME/" \ + "" \ + "$(gum style --foreground 245 "Need help? Check the contributing guide:")" \ + "$(gum style --foreground 245 "https://dynatrace-oss.github.io/open-ecosystem-challenges/contributing/adventures/")" +