diff --git a/CLAUDE.md b/CLAUDE.md index 84e10f2..b194e64 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -354,6 +354,7 @@ Additional logs in workspace: - `drupal-core/template.tf` - Drupal core development template (most actively developed) - `drupal-contrib/template.tf` - Drupal contributed module development template - `freeform/template.tf` - Multi-project freeform workspace template (keeps ddev-router) +- `image/scripts/.ddev/commands/host/launch` - Shared `ddev launch` override for all templates. Honors `DDEV_DEBUG`/`DDEV_VERBOSE` (prints `FULLURL ` and exits, matching stock ddev's contract used by tooling). For interactive use, branches on `ddev describe -j`'s `.raw.router_disabled`: freeform (router enabled) reads the per-project `coder-routes` Traefik config; drupal-core/drupal-contrib (router disabled, direct port bind) construct the named-app URL directly from the `ddev-web`/`mailpit` `coder_app` slugs — no Traefik file needed. - `image/Dockerfile` - Base image build instructions (shared by all templates) - `image/scripts/.ddev/global_config.yaml` - DDEV defaults copied into workspaces - `scripts/coder-delete-workspace-dir.sh` - Sudo wrapper for workspace host dir cleanup (must be installed on server) diff --git a/image/scripts/.ddev/commands/host/launch b/image/scripts/.ddev/commands/host/launch index d045336..4c0232a 100644 --- a/image/scripts/.ddev/commands/host/launch +++ b/image/scripts/.ddev/commands/host/launch @@ -6,9 +6,64 @@ ## Example: "ddev launch" or "ddev launch /admin" or "ddev launch -m" ## Flags: [{"Name":"mailpit","Shorthand":"m","Usage":"ddev launch -m shows the Mailpit URL"}] +# Parse flags/path before anything else, so both the DDEV_DEBUG branch below +# and the interactive branches further down agree on the same target. +MAILPIT=false +PATH_SUFFIX="" + +while :; do + case ${1:-} in + -m | --mailpit | --mailhog) + MAILPIT=true + ;; + --) shift; break ;; + -?*) printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 ;; + *) break ;; + esac + shift +done + +if [ -n "${1:-}" ]; then + PATH_SUFFIX="/${1#/}" +fi + +# Tooling (bats tests, `ddev launch | awk '/FULLURL/ {print $2}'`, DDEV's own +# TestLaunchCommand) sets DDEV_DEBUG/DDEV_VERBOSE to get a single parseable +# line instead of the interactive URL listing below. Match the stock ddev +# launch script's contract: FULLURL is DDEV_PRIMARY_URL with the same +# path/mailpit adjustment the interactive branches apply, since tooling runs +# inside the workspace where *.ddev.site resolves directly (unlike a human's +# browser, which needs the external Coder proxy URL built further down). +if [ "${DDEV_DEBUG:-}" = "true" ] || [ "${DDEV_VERBOSE:-}" = "true" ]; then + FULLURL="${DDEV_PRIMARY_URL}" + if [ "${MAILPIT}" = "true" ]; then + if [ "${FULLURL%://*}" = "https" ]; then + FULLURL="${FULLURL%:[0-9]*}:${DDEV_MAILPIT_HTTPS_PORT}" + else + FULLURL="${FULLURL%:[0-9]*}:${DDEV_MAILPIT_PORT}" + fi + else + FULLURL="${FULLURL%/}${PATH_SUFFIX}" + fi + echo "FULLURL ${FULLURL}" + exit 0 +fi + +# Emit an OSC 8 hyperlink when stdout is a terminal that supports it (VS Code's +# integrated terminal does), so the URL is clickable even though we can't +# actually launch a browser from inside the workspace container. Terminals +# that don't understand OSC 8 just show the plain URL text. +hyperlink() { + if [ -t 1 ]; then + printf '\033]8;;%s\033\\%s\033]8;;\033\\' "$1" "$1" + else + printf '%s' "$1" + fi +} + # Outside a Coder workspace fall back to a basic URL print (no browser available in DinD) if [ -z "${CODER_WORKSPACE_NAME:-}" ] || ([ -z "${VSCODE_PROXY_URI:-}" ] && [ -z "${CODER_AGENT_URL:-}" ]); then - echo "Primary URL: ${DDEV_PRIMARY_URL:-unknown}" + echo "Primary URL: $(hyperlink "${DDEV_PRIMARY_URL:-unknown}")" echo "(Not running in a Coder workspace; cannot open a browser.)" exit 0 fi @@ -25,23 +80,23 @@ fi PROJECT="${DDEV_SITENAME}" AGENT="${CODER_AGENT_NAME:-main}" -MAILPIT=false -PATH_SUFFIX="" - -while :; do - case ${1:-} in - -m | --mailpit | --mailhog) - MAILPIT=true - ;; - --) shift; break ;; - -?*) printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 ;; - *) break ;; - esac - shift -done - -if [ -n "${1:-}" ]; then - PATH_SUFFIX="/${1#/}" +# drupal-core/drupal-contrib omit ddev-router and bind the web container directly +# to a fixed host port, so there's no Traefik merged config for coder-routes to +# read. They register fixed `coder_app` slugs (ddev-web, mailpit) instead, whose +# subdomain URLs follow Coder's named-app pattern (no agent segment, since these +# templates define a single agent) — see docs/reference/coder-url-patterns.md. +ROUTER_DISABLED=$(ddev describe -j 2>/dev/null | jq -r '.raw.router_disabled // false') + +if [ "$ROUTER_DISABLED" = "true" ]; then + if [ "${MAILPIT}" = "true" ]; then + echo "$(hyperlink "https://mailpit--${WORKSPACE}--${OWNER}.${DOMAIN}")" + exit 0 + fi + echo "" + echo "Coder URL for project '${PROJECT}':" + echo " Web: $(hyperlink "https://ddev-web--${WORKSPACE}--${OWNER}.${DOMAIN}${PATH_SUFFIX}")" + echo "" + exit 0 fi # Per-project routes file (written by ddev coder-routes after each ddev start). @@ -60,12 +115,12 @@ if [ "${MAILPIT}" = "true" ]; then mailpit_rule=$(yq e ".http.routers.\"${PROJECT}-coder-mailpit-${PROJECT_SLUG}\".rule // \"\"" "$CODER_ROUTES" 2>/dev/null) if [ -n "$mailpit_rule" ] && [ "$mailpit_rule" != "null" ]; then host=$(echo "$mailpit_rule" | sed -E 's/Host\(`(.+)`\)/\1/') - echo "https://${host}" + echo "$(hyperlink "https://${host}")" exit 0 fi fi # fallback - echo "https://mailpit-${PROJECT_SLUG}--${WORKSPACE}--${OWNER}.${DOMAIN}" + echo "$(hyperlink "https://mailpit-${PROJECT_SLUG}--${WORKSPACE}--${OWNER}.${DOMAIN}")" exit 0 fi @@ -94,15 +149,15 @@ while IFS= read -r router; do # Extract hostname from Host(`...`) — these use Coder subdomain proxy host=$(echo "$rule" | sed -E 's/Host\(`(.+)`\)/\1/') if [ "$router" = "$WEB_ROUTER" ]; then - WEB_LINE=" Web: https://${host}${PATH_SUFFIX}" + WEB_LINE=" Web: $(hyperlink "https://${host}${PATH_SUFFIX}")" elif [ "$slug" = "mailpit" ]; then - MAILPIT_LINE=" Mailpit: https://${host}" + MAILPIT_LINE=" Mailpit: $(hyperlink "https://${host}")" else - OTHER_LINES="${OTHER_LINES} ${slug}: https://${host}\n" + OTHER_LINES="${OTHER_LINES} ${slug}: $(hyperlink "https://${host}")\n" fi else # PathPrefix rule — dynamic add-on, accessible via Coder port-forwarding URL - OTHER_LINES="${OTHER_LINES} ${slug}: https://${ext_port}--${AGENT}--${WORKSPACE}--${OWNER}.${DOMAIN}\n" + OTHER_LINES="${OTHER_LINES} ${slug}: $(hyperlink "https://${ext_port}--${AGENT}--${WORKSPACE}--${OWNER}.${DOMAIN}")\n" fi done < <(yq e '.http.routers | keys | .[]' "$CODER_ROUTES" 2>/dev/null)