Skip to content
Closed
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
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,22 @@ The template points PHP tooling at `.ddev/drupal-code-quality/tooling/bin` and J
- `dcq-reports/` is created at the project root when running `checks`
or the `*-fix` commands (logs + patch previews).
- Add `dcq-reports/` to `.gitignore` if you do not want to track it.
- Host-path parity alias:
- The add-on installs `.ddev/web-entrypoint.d/90-dcq-host-path-alias.sh`.
- On container start, it creates a host-style project-path symlink to
`/var/www/html` so absolute host paths can resolve inside the container.
- On macOS paths under `/private/...`, it also creates a `/...` companion
alias (for example `/tmp/...`) to cover common host-path forms.
- To disable, add `DCQ_HOST_PATH_ALIAS=0` under `web_environment` in
`.ddev/config.yaml`, then run `ddev restart`.
- ESLint toolchain selection:
- `ESLINT_TOOLCHAIN=auto` (default) prefers root toolchain when root configs exist.
- `ESLINT_TOOLCHAIN=core` forces Drupal core JS toolchain.
- `ESLINT_TOOLCHAIN=root` forces project root toolchain.
- ESLint config mode:
- `ESLINT_CONFIG_MODE=nearest` (default) groups by nearest config file.
- `ESLINT_CONFIG_MODE=fixed` forces `.eslintrc.passing.json`.
- `ESLINT_CONFIG_MODE=fixed` prefers `.eslintrc.passing.json`, then
`.eslintrc.json` in the project root.
- ESLint warning visibility (GitLab CI parity):
- `DCQ_ESLINT_QUIET=1` (default) adds `--quiet` to `ddev eslint` and
`ddev eslint-fix`, so warnings are suppressed.
Expand All @@ -177,8 +186,8 @@ The template points PHP tooling at `.ddev/drupal-code-quality/tooling/bin` and J
- CSpell parity:
- Run `ddev exec php /mnt/ddev_config/drupal-code-quality/tooling/scripts/prepare-cspell.php -s .prepared` once and
replace `.cspell.json` after reviewing the diff.
- `ddev cspell` runs from the repo root (`.`) by default; scope is controlled
by `.cspell.json` `ignorePaths`. Narrow the scan by passing explicit paths.
- `ddev cspell` defaults to custom code plus `sites` under the configured
docroot, excluding `sites/*/files/**`, when no paths are passed.
- `.cspell-project-words.txt` is created by the installer (empty) and updated
by `ddev cspell-suggest` when you accept suggested words.
- PHPCS / PHPCBF default scope:
Expand All @@ -189,14 +198,14 @@ The template points PHP tooling at `.ddev/drupal-code-quality/tooling/bin` and J
- You can still pass explicit paths to narrow runs.
- PHPStan baseline:
- Generate a baseline with `ddev phpstan --generate-baseline`.
- This writes `phpstan-baseline.neon` at the project root; the wrapper will
include it automatically when present.
- This writes `phpstan-baseline.neon` at the project root and updates
`phpstan.neon` to include it.
- Use a baseline to suppress known issues in legacy code or core defaults
(for example, the shipped `settings.php` files), then work it down over
time. Avoid using it to hide new regressions.
- PHPStan config fallback:
- If no project `phpstan.neon*` exists, the wrapper uses the GitLab template
config shipped with the add-on.
- PHPStan config requirement:
- `ddev phpstan` requires project config (`phpstan.neon*`) unless you pass
`--configuration <path>`.
- PHPStan level:
- GitLab CI template defaults use level 0. The installer can set a local default level (0-10).

Expand All @@ -223,6 +232,9 @@ The template points PHP tooling at `.ddev/drupal-code-quality/tooling/bin` and J
- `DCQ_INSTALL_IDE_SETTINGS`: `merge` to add missing VS Code settings and
extension recommendations, `overwrite` to back up and replace, `skip` to
handle manually, or unset to prompt.
- `DCQ_HOST_PATH_ALIAS`: `1`/unset (default) keeps host-path alias symlinks
enabled at web-container startup; set `0`/`false`/`off` to disable and remove
add-on-managed aliases on restart.

## Uninstall

Expand Down
21 changes: 1 addition & 20 deletions commands/web/checks
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@ if [ -z "$DOCROOT" ]; then
DOCROOT="web"
fi

# Collect custom code paths once so tools can reuse them.
CUSTOM_PATHS=()
for candidate in "${DOCROOT}/modules/custom" "${DOCROOT}/themes/custom" "${DOCROOT}/profiles/custom"; do
if [ -d "$candidate" ]; then
CUSTOM_PATHS+=("$candidate")
fi
done

# Ordered list of Drupal.org GitLab CI template default tools to run.
TOOLS=(
"composer-validate"
Expand Down Expand Up @@ -66,18 +58,7 @@ for tool in "${TOOLS[@]}"; do
continue
fi

if [ "$tool" = "phpcs" ] && [ "${#CUSTOM_PATHS[@]}" -eq 0 ]; then
# Avoid failing when there is no custom code to lint.
echo "SKIP: no custom code directories found for phpcs." | tee -a "$log_file"
STATUS["$tool"]=SKIP
continue
fi

if [ "$tool" = "phpcs" ]; then
"$tool_path" "${CUSTOM_PATHS[@]}" >"$log_file" 2>&1
else
"$tool_path" >"$log_file" 2>&1
fi
"$tool_path" >"$log_file" 2>&1
exit_code=$?
if [ "$exit_code" -eq 0 ]; then
STATUS["$tool"]=PASS
Expand Down
21 changes: 1 addition & 20 deletions commands/web/checks-full
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@ if [ -z "$DOCROOT" ]; then
DOCROOT="web"
fi

# Collect custom code paths once so tools can reuse them.
CUSTOM_PATHS=()
for candidate in "${DOCROOT}/modules/custom" "${DOCROOT}/themes/custom" "${DOCROOT}/profiles/custom"; do
if [ -d "$candidate" ]; then
CUSTOM_PATHS+=("$candidate")
fi
done

# Ordered list of baseline Drupal.org GitLab CI template default tools.
TOOLS=(
"composer-validate"
Expand Down Expand Up @@ -82,18 +74,7 @@ for tool in "${ALL_TOOLS[@]}"; do
continue
fi

if [ "$tool" = "phpcs" ] && [ "${#CUSTOM_PATHS[@]}" -eq 0 ]; then
# Avoid failing when there is no custom code to lint.
echo "SKIP: no custom code directories found for phpcs." | tee -a "$log_file"
STATUS["$tool"]=SKIP
continue
fi

if [ "$tool" = "phpcs" ]; then
"$tool_path" "${CUSTOM_PATHS[@]}" >"$log_file" 2>&1
else
"$tool_path" >"$log_file" 2>&1
fi
"$tool_path" >"$log_file" 2>&1
exit_code=$?
if [ "$exit_code" -eq 0 ]; then
STATUS["$tool"]=PASS
Expand Down
37 changes: 25 additions & 12 deletions commands/web/cspell
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ CORE_CSPELL="${DOCROOT_PATH}/core/node_modules/.bin/cspell"
ROOT_CSPELL="${PROJECT_ROOT}/node_modules/.bin/cspell"
CSPELL_BIN=""

if ! command -v node >/dev/null 2>&1; then
echo "Node.js is not available in the DDEV web container." >&2
echo "Install the Drupal core JS toolchain (${DOCROOT}/core) to run CSpell." >&2
exit 127
fi

if [ -x "$ROOT_CSPELL" ]; then
CSPELL_BIN="$ROOT_CSPELL"
elif [ -x "$CORE_CSPELL" ]; then
Expand All @@ -85,12 +91,14 @@ fi

CMD=("$CSPELL_BIN")
if [ "$has_config" = false ]; then
# Prefer project config; warn when falling back to core.
if [ -f "${PROJECT_ROOT}/.cspell.json" ]; then
CMD+=(-c "${PROJECT_ROOT}/.cspell.json")
else
CMD+=(-c "${DOCROOT_PATH}/core/.cspell.json")
echo "Warning: using core CSpell config (${DOCROOT}/core/.cspell.json); project config not found." >&2
if [ ! -f "${PROJECT_ROOT}/.cspell.json" ]; then
echo "CSpell config file is missing. Create .cspell.json in the project root (for example by reinstalling the add-on)." >&2
exit 2
fi
CMD+=(-c "${PROJECT_ROOT}/.cspell.json")
project_words_file="${PROJECT_ROOT}/.cspell-project-words.txt"
if [ ! -f "$project_words_file" ]; then
: > "$project_words_file"
fi
fi

Expand Down Expand Up @@ -152,14 +160,19 @@ while [ "$index" -lt "$arg_count" ]; do
done

if [ "$explicit_paths" = false ]; then
"${CMD[@]}" "${FLAG_ARGS[@]}" "."
DEFAULT_PATHS=()
for candidate in "${DOCROOT}/modules/custom" "${DOCROOT}/themes/custom" "${DOCROOT}/profiles/custom" "${DOCROOT}/sites"; do
if [ -d "${PROJECT_ROOT}/${candidate}" ]; then
DEFAULT_PATHS+=("$candidate")
fi
done
if [ "${#DEFAULT_PATHS[@]}" -eq 0 ]; then
echo "No custom code or sites directories found under ${DOCROOT}. Nothing to check." >&2
exit 0
fi
"${CMD[@]}" "${FLAG_ARGS[@]}" --exclude "${DOCROOT}/sites/*/files/**" "${DEFAULT_PATHS[@]}"
exit $?
fi

"${CMD[@]}" "${FLAG_ARGS[@]}" "${POSITIONAL_ARGS[@]}"
exit $?
if ! command -v node >/dev/null 2>&1; then
echo "Node.js is not available in the DDEV web container." >&2
echo "Install the Drupal core JS toolchain (${DOCROOT}/core) to run CSpell." >&2
exit 127
fi
40 changes: 27 additions & 13 deletions commands/web/cspell-suggest
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ DOCROOT_PATH="${PROJECT_ROOT}/${DOCROOT}"
CORE_CSPELL="${DOCROOT_PATH}/core/node_modules/.bin/cspell"
ROOT_CSPELL="${PROJECT_ROOT}/node_modules/.bin/cspell"
CSPELL_BIN=""

if ! command -v node >/dev/null 2>&1; then
echo "Node.js is not available in the DDEV web container." >&2
echo "Install the Drupal core JS toolchain (${DOCROOT}/core) to run CSpell." >&2
exit 127
fi

REPORT_DIR="${PROJECT_ROOT}/dcq-reports"
if ! mkdir -p "$REPORT_DIR"; then
echo "Unable to create report directory: $REPORT_DIR" >&2
Expand Down Expand Up @@ -104,14 +111,16 @@ if [ "$has_version" = true ]; then
exit $?
fi

PROJECT_DICTIONARY="${PROJECT_ROOT}/.cspell-project-words.txt"
CMD=("$CSPELL_BIN")
if [ "$has_config" = false ]; then
# Prefer project config; warn when falling back to core.
if [ -f "${PROJECT_ROOT}/.cspell.json" ]; then
CMD+=(-c "${PROJECT_ROOT}/.cspell.json")
else
CMD+=(-c "${DOCROOT_PATH}/core/.cspell.json")
echo "Warning: using core CSpell config (${DOCROOT}/core/.cspell.json); project config not found." >&2
if [ ! -f "${PROJECT_ROOT}/.cspell.json" ]; then
echo "CSpell config file is missing. Create .cspell.json in the project root (for example by reinstalling the add-on)." >&2
exit 2
fi
CMD+=(-c "${PROJECT_ROOT}/.cspell.json")
if [ ! -f "$PROJECT_DICTIONARY" ]; then
: > "$PROJECT_DICTIONARY"
fi
fi

Expand Down Expand Up @@ -173,7 +182,18 @@ while [ "$index" -lt "$arg_count" ]; do
done

if [ "$explicit_paths" = false ]; then
POSITIONAL_ARGS=(".")
DEFAULT_PATHS=()
for candidate in "${DOCROOT}/modules/custom" "${DOCROOT}/themes/custom" "${DOCROOT}/profiles/custom" "${DOCROOT}/sites"; do
if [ -d "${PROJECT_ROOT}/${candidate}" ]; then
DEFAULT_PATHS+=("$candidate")
fi
done
if [ "${#DEFAULT_PATHS[@]}" -eq 0 ]; then
echo "No custom code or sites directories found under ${DOCROOT}. Nothing to check." >&2
exit 0
fi
POSITIONAL_ARGS=("${DEFAULT_PATHS[@]}")
FLAG_ARGS+=(--exclude "${DOCROOT}/sites/*/files/**")
fi

if [ "$explicit_paths" = false ]; then
Expand All @@ -189,7 +209,6 @@ else
: > "$UNRECOGNIZED_FILE"
fi

PROJECT_DICTIONARY="${PROJECT_ROOT}/.cspell-project-words.txt"
if [ -f "$PROJECT_DICTIONARY" ]; then
# Merge existing dictionary with new suggestions.
cat "$PROJECT_DICTIONARY" "$UNRECOGNIZED_FILE" | sort -u > "$UPDATED_WORDS_FILE"
Expand Down Expand Up @@ -219,8 +238,3 @@ if [ -s "$UNRECOGNIZED_FILE" ]; then
fi

exit 0
if ! command -v node >/dev/null 2>&1; then
echo "Node.js is not available in the DDEV web container." >&2
echo "Install the Drupal core JS toolchain (${DOCROOT}/core) to run CSpell." >&2
exit 127
fi
31 changes: 16 additions & 15 deletions commands/web/eslint
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,19 @@ if [ -n "$RESOLVE_PLUGINS_DIR" ]; then
# Ensure ESLint resolves plugins from the selected toolchain.
CMD+=(--resolve-plugins-relative-to "$RESOLVE_PLUGINS_DIR")
fi
FIXED_CONFIG=""
if [ -f "${PROJECT_ROOT}/.eslintrc.passing.json" ]; then
FIXED_CONFIG="${PROJECT_ROOT}/.eslintrc.passing.json"
elif [ -f "${PROJECT_ROOT}/.eslintrc.json" ]; then
FIXED_CONFIG="${PROJECT_ROOT}/.eslintrc.json"
fi
if [ "$has_config" = false ] && [ "$ESLINT_CONFIG_MODE" != "nearest" ]; then
# Fixed mode: force the passing config for Drupal.org GitLab CI template defaults when requested.
if [ -f "${PROJECT_ROOT}/.eslintrc.passing.json" ]; then
CMD+=(--config="${PROJECT_ROOT}/.eslintrc.passing.json")
else
CMD+=(--config="${DOCROOT_PATH}/core/.eslintrc.passing.json")
echo "Warning: using core ESLint config (${DOCROOT}/core/.eslintrc.passing.json); project config not found." >&2
# Fixed mode: prefer project passing config, then project base config.
if [ -z "$FIXED_CONFIG" ]; then
echo "ESLint config file is missing. Create .eslintrc.passing.json (or .eslintrc.json) in the project root, or pass --config." >&2
exit 2
fi
CMD+=(--config="$FIXED_CONFIG")
fi
CMD+=(--ext .js,.yml --ignore-pattern "**/node_modules/**")
CMD+=("${DEFAULT_ARGS[@]}")
Expand All @@ -146,7 +151,7 @@ find_nearest_config() {
while true; do
# Walk up the directory tree to find the closest ESLint config.
for candidate in .eslintrc.passing.json .eslintrc .eslintrc.json .eslintrc.yaml .eslintrc.yml .eslintrc.js .eslintrc.cjs; do
if [ "$dir" = "web" ] || [ "$dir" = "./web" ]; then
if [ "$dir" = "$DOCROOT" ] || [ "$dir" = "./$DOCROOT" ]; then
# Skip docroot configs so nearest-mode prefers theme/module configs or root passing config.
continue
fi
Expand Down Expand Up @@ -273,15 +278,15 @@ fi
FILES=()
if [ "$explicit_paths" = false ]; then
DEFAULT_FILES=()
for candidate in "${DOCROOT}/modules/custom" "${DOCROOT}/themes/custom" "${DOCROOT}/profiles/custom"; do
for candidate in "${DOCROOT}/modules/custom" "${DOCROOT}/themes/custom" "${DOCROOT}/profiles/custom" "${DOCROOT}/sites"; do
if [ -d "$candidate" ]; then
while IFS= read -r file_path; do
DEFAULT_FILES+=("$file_path")
done < <(find "$candidate" -path '*/node_modules/*' -prune -o -type f \( -name '*.js' -o -name '*.yml' \) -print)
done < <(find "$candidate" \( -path '*/node_modules/*' -o -path '*/sites/*/files/*' \) -prune -o -type f \( -name '*.js' -o -name '*.yml' \) -print)
fi
done
if [ "${#DEFAULT_FILES[@]}" -eq 0 ]; then
echo "No custom JS/YML files found under modules/custom, themes/custom, or profiles/custom. Nothing to lint." >&2
echo "No default JS/YML files found under modules/custom, themes/custom, profiles/custom, or sites (excluding sites/*/files). Nothing to lint." >&2
exit 2
fi
FILES=("${DEFAULT_FILES[@]}")
Expand All @@ -290,11 +295,7 @@ else
fi

DEFAULT_CONFIG=""
if [ -f "${PROJECT_ROOT}/.eslintrc.passing.json" ]; then
DEFAULT_CONFIG="${PROJECT_ROOT}/.eslintrc.passing.json"
elif [ -f "${DOCROOT_PATH}/core/.eslintrc.passing.json" ]; then
DEFAULT_CONFIG="${DOCROOT_PATH}/core/.eslintrc.passing.json"
fi
DEFAULT_CONFIG="$FIXED_CONFIG"

if [ "$ESLINT_CONFIG_MODE" = "nearest" ]; then
# Group files by nearest config to avoid plugin conflicts across modules/themes.
Expand Down
Loading
Loading