diff --git a/README.md b/README.md index c2bd23d..b94bad9 100644 --- a/README.md +++ b/README.md @@ -71,14 +71,15 @@ During installation, the add-on copies Drupal.org GitLab CI template default config files into the project root. If conflicts are detected, you can choose to back up and replace, skip, or abort. Skipping a config may diverge from the Drupal.org GitLab CI template defaults. The installer will prompt for: -- Accept recommended settings (default: no). Choosing yes applies the +- A pre-prompt summary of recommended defaults. +- Accept recommended settings (default: yes). Press Enter to apply the recommended defaults without further prompts. - Conflict handling (default: skip unless you choose replace/abort). - PHP tooling dependencies (install `drupal/core-dev` or run `ddev composer install`). - PHPStan default level (keep GitLab CI template level 0 or choose a local level 0-10; recommend 3). - Node toolchain install in the project root and package manager selection. - Missing Drupal JS dependencies when a root `package.json` exists. -- Optional `.gitignore` update for `dcq-reports/`. +- Optional `.gitignore` update for `dcq-reports/` (default: yes). - IDE settings (merge/overwrite/skip when templates are available). The installer runs in bash so it does not require host PHP. @@ -217,7 +218,8 @@ The template points PHP tooling at `.ddev/drupal-code-quality/tooling/bin` and J to skip, or unset to prompt (default: install in the project root). The installer selects npm/yarn based on existing lockfiles. - `DCQ_INSTALL_GITIGNORE`: `add`/`true` to add `dcq-reports/` to `.gitignore` - without prompting, `skip`/`false` to skip, or unset to prompt when interactive. + without prompting, `skip`/`false` to skip, or unset to prompt when interactive + (default: yes). - `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. diff --git a/dcq-install.sh b/dcq-install.sh index e730e46..4e83b9b 100644 --- a/dcq-install.sh +++ b/dcq-install.sh @@ -509,7 +509,7 @@ prompt_node_install_action() { emit 'ESLint, Prettier, and Stylelint require several packages to function properly.\n' emit '\n' - emit '[i]nstall these in the project root, [s]kip node module installation (default: install): ' + emit '[i]nstall in the project root, [s]kip (default: install): ' if ! IFS= read -r -u "$PROMPT_IN_FD" choice; then choice="" fi @@ -602,26 +602,32 @@ prompt_yes_no() { } prompt_recommended_settings() { - # Prompt for accepting recommended settings (default: no). + # Prompt for accepting recommended settings (default: yes). if [ "${PROMPT_AVAILABLE:-0}" -ne 1 ]; then return 1 fi - printf 'Accept recommended settings? (y/N) ' >&"$PROMPT_OUT_FD" - local answer="" - if ! IFS= read -r -u "$PROMPT_IN_FD" answer; then - answer="" - fi - answer="$(string_lower "$answer")" - if [ -z "$answer" ]; then - return 1 + if prompt_yes_no "Accept recommended settings for this install?" 0; then + return 0 fi - case "$answer" in - y|yes) return 0 ;; - esac return 1 } +print_recommended_settings_summary() { + # Show what the one-step recommended install will do before prompting. + if [ "${PROMPT_AVAILABLE:-0}" -ne 1 ]; then + return + fi + + emit '\nRecommended defaults for this install:\n' + emit ' - If a copied config file already exists, the installer will create a backup and then replace the file.\n' + emit ' - Install missing PHP tooling via drupal/core-dev, including PHPStan, PHPCS, PHPCBF, and Drupal coding standards.\n' + emit ' - Install Node tooling in the project root, including ESLint, Stylelint, Prettier, and CSpell dependencies.\n' + emit ' - Set phpstan.neon level to 3 as a practical local default.\n' + emit ' - Merge DCQ VS Code/Codium settings into .vscode/settings.json and extension recommendations into .vscode/extensions.json.\n' + emit " - Add 'dcq-reports/' to .gitignore so generated check logs and patch previews are not committed.\n" +} + set_default_env() { local name="$1" local value="$2" @@ -809,7 +815,7 @@ maybe_add_gitignore_reports() { emit 'Add %s to .gitignore to avoid committing report logs.\n' "$entry" printf '\n' - if prompt_yes_no "Add '${entry}' to .gitignore?" 1; then + if prompt_yes_no "Add '${entry}' to .gitignore?" 0; then if [ -f "$gitignore" ]; then printf '\n%s\n' "$entry" >>"$gitignore" else @@ -929,6 +935,11 @@ expand_cspell_config() { local ddev_approot="${DDEV_APPROOT:-$app_root}" local prepare_script="${ddev_approot}/.ddev/drupal-code-quality/tooling/scripts/prepare-cspell.php" local cspell_config="${app_root%/}/.cspell.json" + local docroot="${DCQ_DOCROOT:-web}" + local ddev_cmd="${DDEV_EXECUTABLE:-ddev}" + local container_cspell="/var/www/html/.cspell.json" + local container_core_cspell_dir="/var/www/html/${docroot}/core/misc/cspell" + local container_prepare_script="/mnt/ddev_config/drupal-code-quality/tooling/scripts/prepare-cspell.php" # Check if CSpell config exists @@ -944,28 +955,36 @@ expand_cspell_config() { fi # Run prepare-cspell.php in the container to expand .cspell.json - if command_available "${DDEV_EXECUTABLE:-ddev}"; then - emit 'Expanding .cspell.json with project-specific settings...\n' - local ddev_cmd="${DDEV_EXECUTABLE:-ddev}" - - # Run the script in container from project root - # Capture both stdout and stderr, but don't fail the installer if it errors - # Pass the docroot via _WEB_ROOT environment variable - local output - if output=$("$ddev_cmd" exec bash -c "cd /var/www/html && export _WEB_ROOT='${DCQ_DOCROOT:-web}' && php .ddev/drupal-code-quality/tooling/scripts/prepare-cspell.php" 2>&1); then - if echo "$output" | grep -q "Writing json"; then - emit 'Successfully expanded .cspell.json\n' - else - emit 'CSpell expansion completed (no changes needed)\n' - fi + if ! command_available "$ddev_cmd"; then + emit 'DDEV not available; skipping CSpell expansion.\n' + return 0 + fi + + emit 'Expanding .cspell.json with project-specific settings...\n' + + if ! wait_for_container_file "$ddev_cmd" "$container_cspell" 20 1; then + emit 'Skipping CSpell expansion (.cspell.json did not become visible in the container after waiting).\n' + return 0 + fi + + if ! "$ddev_cmd" exec test -f "${container_core_cspell_dir}/dictionary.txt" >/dev/null 2>&1 \ + || ! "$ddev_cmd" exec test -f "${container_core_cspell_dir}/drupal-dictionary.txt" >/dev/null 2>&1; then + emit 'Skipping CSpell expansion (Drupal core dictionary files are not available at %s).\n' "${docroot}/core/misc/cspell" + return 0 + fi + + local output + if output=$("$ddev_cmd" exec bash -lc "cd /var/www/html && export _WEB_ROOT='${docroot}' _CSPELL_DICTIONARY='.cspell-project-words.txt' && php '${container_prepare_script}'" 2>&1); then + if echo "$output" | grep -q "Writing json"; then + emit 'Successfully expanded .cspell.json\n' else - emit 'Skipping CSpell expansion: prepare-cspell.php failed.\n' - if [ -n "$output" ]; then - emit '%s\n' "$output" - fi + emit 'CSpell expansion completed (no changes needed)\n' fi else - emit 'DDEV not available; skipping CSpell expansion.\n' + emit 'Skipping CSpell expansion (prepare-cspell.php failed).\n' + if truthy "${DCQ_VERBOSE:-0}" && [ -n "$output" ]; then + emit 'CSpell expansion error output:\n%s\n' "$output" + fi fi # Always return success - CSpell expansion is optional @@ -1025,6 +1044,23 @@ command_available() { command -v "$1" >/dev/null 2>&1 } +wait_for_container_file() { + local ddev_cmd="$1" + local path="$2" + local max_attempts="${3:-20}" + local delay_seconds="${4:-1}" + local attempts=0 + + while [ "$attempts" -lt "$max_attempts" ]; do + if "$ddev_cmd" exec test -f "$path" >/dev/null 2>&1; then + return 0 + fi + attempts=$((attempts + 1)) + sleep "$delay_seconds" + done + return 1 +} + detect_package_manager() { local dir="$1" local has_yarn=0 @@ -1616,8 +1652,11 @@ fi # In non-interactive mode, or if user accepts, set recommended defaults for any unset vars. recommended_mode=0 if [ "$non_interactive" -eq 0 ] && [ "${PROMPT_AVAILABLE:-0}" -eq 1 ]; then + print_recommended_settings_summary if prompt_recommended_settings; then recommended_mode=1 + else + emit 'Using manual mode. You will be prompted for each setting.\n' fi fi @@ -1711,9 +1750,9 @@ if [ "${#missing_tools[@]}" -gt 0 ]; then fi if [ "$action" = "install" ]; then - question="Recommend installing PHP dev tools from composer.lock. Proceed?" + question="Install PHP dev tools from composer.lock now?" else - question="Recommend installing drupal/core-dev as a dev dependency to install PHP code quality tools and Drupal coding standards. Proceed?" + question="Install drupal/core-dev now to provide PHP code quality tools and Drupal coding standards?" fi should_install=0 diff --git a/tests/test-installer-deps-prompt.bats b/tests/test-installer-deps-prompt.bats index 8bfe603..0a0b6dc 100644 --- a/tests/test-installer-deps-prompt.bats +++ b/tests/test-installer-deps-prompt.bats @@ -93,8 +93,8 @@ env.pop("DDEV_NONINTERACTIVE", None) cmd = ["bash", os.path.join(addon_root, "dcq-install.sh")] prompts = { - b"Accept recommended settings? (y/N)": b"n\n", - b"Recommend installing drupal/core-dev as a dev dependency": b"n\n", + b"Accept recommended settings for this install? [Y/n]": b"n\n", + b"Install drupal/core-dev now to provide PHP code quality tools": b"n\n", } seen = set() @@ -141,8 +141,8 @@ code = os.waitstatus_to_exitcode(status) sys.exit(code) PY [ "$status" -eq 0 ] - [[ "$output" == *"Accept recommended settings? (y/N)"* ]] - [[ "$output" == *"Recommend installing drupal/core-dev as a dev dependency"* ]] + [[ "$output" == *"Accept recommended settings for this install? [Y/n]"* ]] + [[ "$output" == *"Install drupal/core-dev now to provide PHP code quality tools"* ]] [[ "$output" == *"Project root tooling configs updated"* ]] run_search "^composer require --dev drupal/core-dev --with-all-dependencies$" "${DDEV_STUB_LOG}" @@ -179,7 +179,7 @@ env.pop("DDEV_NONINTERACTIVE", None) cmd = ["bash", os.path.join(addon_root, "dcq-install.sh")] prompts = { - b"Accept recommended settings? (y/N)": b"y\n", + b"Accept recommended settings for this install? [Y/n]": b"y\n", } seen = set() @@ -226,7 +226,7 @@ code = os.waitstatus_to_exitcode(status) sys.exit(code) PY [ "$status" -eq 0 ] - [[ "$output" == *"Accept recommended settings? (y/N)"* ]] + [[ "$output" == *"Accept recommended settings for this install? [Y/n]"* ]] run_search "^composer config --no-plugins allow-plugins\\.tbachert/spi false$" "${DDEV_STUB_LOG}" [ "$status" -eq 0 ] @@ -288,7 +288,7 @@ env.pop("DDEV_NONINTERACTIVE", None) cmd = ["bash", os.path.join(addon_root, "dcq-install.sh")] prompts = { - b"Accept recommended settings? (y/N)": b"n\n", + b"Accept recommended settings for this install? [Y/n]": b"n\n", b"VS Code/Codium settings/extensions: choose merge, overwrite (with backup), or skip.": b"\n", } @@ -336,7 +336,7 @@ code = os.waitstatus_to_exitcode(status) sys.exit(code) PY [ "$status" -eq 0 ] - [[ "$output" == *"Accept recommended settings? (y/N)"* ]] + [[ "$output" == *"Accept recommended settings for this install? [Y/n]"* ]] [[ "$output" == *"VS Code/Codium settings/extensions: choose merge, overwrite (with backup), or skip."* ]] [[ "$output" == *"Skipping IDE settings/extensions install."* ]] [ ! -f "${APP_ROOT}/.vscode/settings.json" ] @@ -421,11 +421,11 @@ for key in ( cmd = ["bash", os.path.join(addon_root, "dcq-install.sh")] prompts = { - b"Accept recommended settings? (y/N)": b"n\n", - b"Recommend installing drupal/core-dev as a dev dependency": b"n\n", + b"Accept recommended settings for this install? [Y/n]": b"n\n", + b"Install drupal/core-dev now to provide PHP code quality tools": b"n\n", b"Conflict at ": b"s\n", b"Set phpstan.neon level (0-10) (default: 0):": b"0\n", - b"[i]nstall these in the project root, [s]kip node module installation (default: install):": b"s\n", + b"[i]nstall in the project root, [s]kip (default: install):": b"s\n", b"Add 'dcq-reports/' to .gitignore?": b"n\n", b"VS Code/Codium settings/extensions: choose merge, overwrite (with backup), or skip.": b"\n", } @@ -474,7 +474,7 @@ code = os.waitstatus_to_exitcode(status) sys.exit(code) PY [ "$status" -eq 0 ] - [[ "$output" == *"Recommend installing drupal/core-dev as a dev dependency"* ]] + [[ "$output" == *"Install drupal/core-dev now to provide PHP code quality tools"* ]] [[ "$output" == *"Conflict at"* ]] [[ "$output" == *"ESLint, Prettier, and Stylelint require several packages to function properly."* ]] [[ "$output" == *"Add 'dcq-reports/' to .gitignore?"* ]] diff --git a/tests/test.bats b/tests/test.bats index cb35982..7c03422 100644 --- a/tests/test.bats +++ b/tests/test.bats @@ -632,7 +632,7 @@ teardown() { health_checks } -@test "installer prompt accepts recommended settings" { +@test "installer prompt defaults to recommended settings" { set -u -o pipefail unset DCQ_NONINTERACTIVE unset DDEV_NONINTERACTIVE @@ -659,7 +659,7 @@ import select import sys cmd = ["ddev", "add-on", "get", "${DIR}"] -prompt = b"Accept recommended settings? (y/N)" +prompt = b"Accept recommended settings for this install? [Y/n]" sent = False buf = b"" @@ -678,7 +678,7 @@ try: sys.stdout.buffer.flush() buf += data if (not sent) and (prompt in buf): - os.write(fd, b"y\\n") + os.write(fd, b"\\n") sent = True except OSError: pass @@ -688,7 +688,8 @@ code = os.waitstatus_to_exitcode(status) sys.exit(code) PY assert_success - assert_output --partial "Accept recommended settings? (y/N)" + assert_output --partial "Recommended defaults for this install:" + assert_output --partial "Accept recommended settings for this install? [Y/n]" assert_phpstan_level "3" if command -v rg >/dev/null 2>&1; then run rg -n "^dcq-reports/$" ".gitignore" @@ -730,8 +731,8 @@ import sys cmd = ["ddev", "add-on", "get", "${DIR}"] # Answer 'n' to recommended settings, then respond to individual prompts responses = { - b"Accept recommended settings? (y/N)": b"n\n", - b"Recommend installing drupal/core-dev as a dev dependency": b"n\n", + b"Accept recommended settings for this install? [Y/n]": b"n\n", + b"Install drupal/core-dev now to provide PHP code quality tools": b"n\n", b"back up and replace, skip, or abort? [replace/skip/abort]": b"skip\n", b"Install Node toolchain": b"n\n", b"PHPStan level": b"0\n", @@ -771,11 +772,13 @@ code = os.waitstatus_to_exitcode(status) sys.exit(code) PY assert_success - assert_output --partial "Accept recommended settings? (y/N)" + assert_output --partial "Recommended defaults for this install:" + assert_output --partial "Accept recommended settings for this install? [Y/n]" # Verify individual prompts appeared (user declined recommended settings) - assert_output --partial "Recommend installing drupal/core-dev as a dev dependency" + assert_output --partial "Install drupal/core-dev now to provide PHP code quality tools" assert_output --partial "VS Code/Codium settings/extensions: choose merge, overwrite (with backup), or skip." assert_output --partial "Skipping IDE settings/extensions install." + assert_output --partial "Add 'dcq-reports/' to .gitignore? [Y/n]" assert_output --partial "Set phpstan.neon level" # PHPStan level should be 0 (not the recommended 3) since user chose it assert_phpstan_level "0" @@ -1039,8 +1042,15 @@ PHP @test "cspell config is expanded during installation" { set -u -o pipefail - run ddev add-on get "${DIR}" + + # Provide minimal Drupal core dictionary files so expansion can run. + run ddev exec bash -lc 'mkdir -p /var/www/html/web/core/misc/cspell && printf "drupal\n" > /var/www/html/web/core/misc/cspell/drupal-dictionary.txt && printf "dictionary\n" > /var/www/html/web/core/misc/cspell/dictionary.txt' + assert_success + + run bash -lc "ddev add-on get \"${DIR}\" 2>&1" assert_success + assert_output --partial "Expanding .cspell.json with project-specific settings..." + assert_output --partial "Successfully expanded .cspell.json" # Verify expanded dictionaries array includes Drupal and project-words run grep -q '"drupal"' .cspell.json @@ -1070,6 +1080,15 @@ PHP assert_file_exist ".cspell-project-words.txt" } +@test "cspell expansion explains skip when Drupal core dictionaries are missing" { + set -u -o pipefail + + run bash -lc "ddev add-on get \"${DIR}\" 2>&1" + assert_success + assert_output --partial "Expanding .cspell.json with project-specific settings..." + assert_output --partial "Skipping CSpell expansion (Drupal core dictionary files are not available at web/core/misc/cspell)." +} + @test "remove cleans ddev assets and shims" { set -u -o pipefail run ddev add-on get "${DIR}"