Skip to content

Conversation

@AlexSkrypnyk
Copy link
Member

@AlexSkrypnyk AlexSkrypnyk commented Sep 1, 2025

Closes #1882

Summary by CodeRabbit

  • New Features

    • Added an installer option to set up a Drupal CMS profile via a bundled recipe.
    • Search now respects content moderation: only published items appear; display uses teaser view.
    • Composer installs Drupal recipes into a dedicated recipes directory; that directory is ignored by default.
  • Refactor

    • Unified prompt resolution with clearer user-facing messages during installation.
  • Tests

    • New unit, functional, and end-to-end coverage for the Drupal CMS profile workflow and prompts.
  • Chores

    • CI matrix expanded with fail-fast enabled for faster feedback.
    • Static analysis excludes recipe assets.

@coderabbitai
Copy link

coderabbitai bot commented Sep 1, 2025

Walkthrough

Adds Drupal CMS installation support: new Starter options and composer mutation for Drupal CMS; introduces resolveOrPrompt flow and updated HandlerInterface/handlers (Profile, Webroot) with resolvedMessage signature change and Profile resolution API. Adds recipes installer-path, ignores recipes by default, test coverage (unit, functional, bats, Behat), PHPMD exclusion, and CI matrix updates.

Changes

Cohort / File(s) Summary
Installer prompt API and flow
.../Prompts/Handlers/HandlerInterface.php, .../Prompts/Handlers/AbstractHandler.php, .../Prompts/PromptManager.php, .../Prompts/Handlers/Webroot.php
Adds resolveOrPrompt() in PromptManager. Changes resolvedMessage signature to accept mixed $resolved and updates calls. Webroot now uses provided $resolved.
Starter and Profile (Drupal CMS integration)
.../Prompts/Handlers/Starter.php, .../Prompts/Handlers/Profile.php, .../Prompts/Handlers/Internal.php
Starter: new option constants, default, and composer mutation for Drupal CMS (drupal/cms, merge plugin, config). Profile: new resolvedValue/resolvedMessage APIs and side-effectful process(). Internal updated for renamed demo constant.
Composer/recipes and tooling
composer.json, .gitignore, phpmd.xml
Adds installer-path for recipes (recipes/{$name}), ignores recipes/ by default in git, excludes recipes/ from PHPMD.
Functional/unit tests
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php, .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php, .vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php
New functional test for Starter; unit tests updated for new constants/options and Profile path; defaults adjusted.
E2E tests and orchestration
.vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats, .vortex/tests/bats/_helper.bash, .vortex/tests/test.workflow.sh, .github/workflows/vortex-test-common.yml
New Drupal CMS profile workflow test; minor helper tweak; test runner updated to include new workflow; CI matrices expanded and fail-fast enabled.
Search configuration and tests
web/modules/custom/ys_search/config/install/search_api.index.content.yml, web/modules/custom/ys_search/config/install/views.view.search.yml, tests/behat/features/search.feature
Search index switches body mapping to status boolean; views adds status filter/field and adjusts view modes; Behat feature updated for moderation-aware search with wait and dataset changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant PM as PromptManager
  participant H_Profile as Handler: Profile
  participant H_Starter as Handler: Starter
  participant Env as .env / FS
  participant Composer as composer.json

  rect rgb(245,248,255)
    note over PM: Centralized resolve-or-prompt
    User->>PM: Run installer
    PM->>H_Profile: resolvedValue(responses)
    alt Resolved string
      H_Profile-->>PM: "<profile_path>"
      PM->>H_Profile: resolvedMessage(responses, "<profile_path>")
      H_Profile-->>PM: "Profile will be set to ..."
      PM->>User: info(message)
      PM-->>User: use "<profile_path>"
    else Unresolved
      PM-->>User: prompt for profile
    end
  end

  rect rgb(240,255,245)
    note over H_Starter: Starter selection
    PM->>H_Starter: resolvedValue/resolvedMessage (via resolveOrPrompt)
    alt INSTALL_PROFILE_DRUPALCMS selected
      PM->>Composer: add drupal/cms, merge-plugin, allow plugins, merge config
      PM->>Env: set DRUPAL_PROFILE="../recipes/drupal_cms_starter"
      PM->>Env: set VORTEX_PROVISION_TYPE=profile
    else INSTALL_PROFILE_CORE or LOAD_DATABASE_DEMO
      PM->>Env: set values accordingly
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
Add drupal/cms to composer requirements (#1882)
Configure installer-paths for drupal-recipe type (#1882)
Add and configure wikimedia/composer-merge-plugin with required merge settings (#1882)
Set env for profile install: VORTEX_PROVISION_TYPE=profile and DRUPAL_PROFILE="../recipes/drupal_cms_starter" (#1882)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Search index field mapping switched from body:processed to status boolean (web/modules/custom/ys_search/config/install/search_api.index.content.yml) Not related to Drupal CMS installation; alters search indexing behavior.
Views search config: add status filter/field and view mode changes (web/modules/custom/ys_search/config/install/views.view.search.yml) Outside the scope of enabling Drupal CMS install; modifies site search display/filters.
Behat search feature rewritten for moderation-aware results (tests/behat/features/search.feature) Testing changes for search functionality, unrelated to the installer CMS integration.
CI workflow matrix/fail-fast changes (.github/workflows/vortex-test-common.yml) Pipeline configuration adjustments not required by the CMS install feature.

Possibly related PRs

Suggested labels

PR: DO NOT MERGE, PR: Do not review

Poem

I hopped through prompts with gentle grace,
A recipe path, a profile place.
Composer hummed, its cables spun,
CMS installed—now look, it’s done!
With ears like flags I thump in glee,
“Ahoy!” I cheer—fresh sites for thee. 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a3328d6 and c90ee6a.

📒 Files selected for processing (2)
  • .github/workflows/vortex-test-common.yml (2 hunks)
  • .vortex/tests/test.workflow.sh (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.

Applied to files:

  • .vortex/tests/test.workflow.sh
🧬 Code graph analysis (1)
.vortex/tests/test.workflow.sh (3)
.vortex/tests/test.common.sh (1)
  • bats (28-37)
.vortex/tests/test.deployment.sh (1)
  • bats (29-38)
.vortex/tests/test.postbuild.sh (1)
  • bats (20-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: build (0)
  • GitHub Check: build (1)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (4)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.3)
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-docs
🔇 Additional comments (2)
.vortex/tests/test.workflow.sh (2)

71-74: Case 3 (Drupal CMS e2e only) — LGTM

Isolated run helps triage. No issues spotted.


75-81: Case 4 grouping (docker-compose → provision → storage) — LGTM

Order is sensible and mirrors the wider workflow.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/1882-drupal-cms

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot temporarily deployed to commit September 1, 2025 10:45 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.gitignore (1)

34-46: Add defensive ignore for web/recipes and confirm dev workflow expectations.

  • Text tweak to “Allow Drupal Scaffold files” looks good.
  • To avoid surprises if anyone maps recipes under web/, ignore that location too.
  • If teams ever need to commit a seed/custom recipe, document an explicit unignore pattern.
 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+web/recipes/*
.vortex/installer/src/Prompts/Handlers/Starter.php (1)

131-138: Use strict comparison and simplify condition.

Small readability/consistency win.

-    elseif ($this->response == self::DRUPAL_INSTALL_PROFILE || $this->response == self::DRUPALCMS_INSTALL_PROFILE) {
+    elseif (in_array($this->response, [self::DRUPAL_INSTALL_PROFILE, self::DRUPALCMS_INSTALL_PROFILE], true)) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 58297d3 and 52dccd8.

⛔ Files ignored due to path filters (9)
  • .vortex/installer/tests/Fixtures/install/_baseline/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/tests/behat/features/search.feature is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_demo_db/.ignorecontent is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (16)
  • .gitignore (2 hunks)
  • .gitignore.artifact (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Profile.php (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/Starter.php (6 hunks)
  • .vortex/installer/src/Prompts/Handlers/Webroot.php (1 hunks)
  • .vortex/installer/src/Prompts/PromptManager.php (2 hunks)
  • .vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1 hunks)
  • .vortex/tests/bats/_helper.bash (0 hunks)
  • .vortex/tests/bats/e2e/workflow.install.profile.bats (1 hunks)
  • composer.json (1 hunks)
  • tests/behat/features/search.feature (1 hunks)
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml (1 hunks)
  • web/modules/custom/ys_search/config/install/views.view.search.yml (1 hunks)
💤 Files with no reviewable changes (1)
  • .vortex/tests/bats/_helper.bash
🧰 Additional context used
📓 Path-based instructions (3)
composer.json

📄 CodeRabbit inference engine (CLAUDE.md)

When adding JavaScript or CSS libraries as Drupal libraries with Composer, define them as inline Composer packages in the 'repositories' section of composer.json

Files:

  • composer.json
tests/behat/**/*.feature

📄 CodeRabbit inference engine (CLAUDE.md)

tests/behat/**/*.feature: All Behat feature files must use the standard user story format: 'As a [user type]\nI want to [action]\nSo that [benefit]'
Use consistent user types in Behat feature files: 'site visitor', 'site administrator', and 'content editor'
Do not use commas or periods in user story statements within Behat feature files

Files:

  • tests/behat/features/search.feature
web/modules/custom/**

📄 CodeRabbit inference engine (CLAUDE.md)

Custom modules should be placed in 'web/modules/custom/'

Files:

  • web/modules/custom/ys_search/config/install/views.view.search.yml
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml
🧬 Code graph analysis (9)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (3)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (5)
.vortex/installer/src/Prompts/Handlers/ProvisionType.php (1)
  • ProvisionType (11-103)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/PromptManager.php (2)
  • PromptManager (58-581)
  • makeEnvName (431-433)
.vortex/installer/src/Utils/Env.php (2)
  • Env (7-245)
  • put (24-26)
.vortex/installer/tests/Functional/Handlers/AbstractInstallTestCase.php (1)
  • AbstractInstallTestCase (19-101)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/tests/bats/e2e/workflow.install.profile.bats (2)
.vortex/tests/bats/_helper.workflow.bash (6)
  • prepare_sut (7-39)
  • assert_ahoy_build (73-130)
  • assert_gitignore (132-162)
  • assert_ahoy_lint (413-425)
  • assert_ahoy_test (527-544)
  • assert_ahoy_fe (753-777)
.vortex/tests/bats/_helper.bash (1)
  • assert_webpage_contains (1022-1028)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/src/Prompts/PromptManager.php (3)
.vortex/installer/src/Prompts/Handlers/Profile.php (3)
  • Profile (10-165)
  • resolvedValue (76-88)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (2)
  • resolvedValue (98-110)
  • resolvedMessage (115-121)
.vortex/installer/src/Utils/Tui.php (1)
  • info (42-44)
.vortex/installer/src/Prompts/Handlers/Starter.php (2)
.vortex/installer/src/Utils/JsonManipulator.php (2)
  • JsonManipulator (10-76)
  • fromFile (19-36)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • AbstractHandler (10-233)
.vortex/installer/src/Prompts/Handlers/Profile.php (5)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (4)
  • resolvedValue (130-132)
  • id (52-62)
  • resolvedMessage (137-139)
  • getResponseAsString (191-197)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • resolvedValue (138-138)
  • id (19-19)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Webroot.php (4)
  • resolvedValue (98-110)
  • discover (65-79)
  • resolvedMessage (115-121)
  • process (126-146)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Utils/File.php (2)
  • File (10-102)
  • replaceContentAsync (59-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build (0)
  • GitHub Check: build (1)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.3)
  • GitHub Check: vortex-test-installer (8.2)
🔇 Additional comments (14)
tests/behat/features/search.feature (4)

11-18: Confirm step supports moderation_state and indexing of unpublished content.

  • Ensure the “Given page content:” step accepts moderation_state and status together and sets them correctly.
  • Verify the index is expected to include 3 items even if one is draft/unpublished; the view assertions rely on runtime filtering.

20-21: Good move: navigate after the indexing wait.

Prevents racing the indexer.


25-27: Assertions for published vs draft visibility look correct.

Matches moderation-aware behavior.


31-33: Follow-up query assertions look correct.

Ensures only the matching published item is returned.

web/modules/custom/ys_search/config/install/views.view.search.yml (2)

165-202: Good: enforce published-only results via status filter.

Non-exposed, fixed status=1 filter is correct and consistent with the index change. No issues.


210-210: Verify teaser availability across bundles.

Setting ':default' to teaser is fine for nodes; confirm all indexed bundles have a teaser display or acceptable fallback to avoid sparse result rendering.

.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)

23-30: Tests cover new DrupalCMS option and correct navigation.

Keys reflect the options order and default (demo DB). This guards regressions in prompt ordering.

.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)

137-139: Public API change: resolvedMessage signature updated—verify all handler implementations and invocations pass the new $resolved parameter.

.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1)

16-29: Good coverage of the three Starter flows.

Scenarios for demo DB, Drupal profile, and DrupalCMS profile are well represented. No functional issues spotted.

.vortex/tests/bats/e2e/workflow.install.profile.bats (1)

32-61: LGTM: DrupalCMS profile e2e path exercises key expectations.

Selecting drupalcms_profile, asserting DRUPAL_PROFILE, composer additions, full build/test/FE, and homepage smoke check look solid.

.vortex/installer/src/Prompts/Handlers/Webroot.php (2)

115-118: Signature alignment and message formatting look correct.

Method now matches HandlerInterface and mirrors Profile’s resolved-message style. No functional issues found.


115-121: All resolvedMessage implementations now use the updated (array $responses, mixed $resolved): ?string signature.

.vortex/installer/src/Prompts/Handlers/Profile.php (1)

90-99: LGTM – clear, user-friendly resolved message.

.vortex/installer/src/Prompts/Handlers/Starter.php (1)

94-97: Confirm necessity of pinning symfony/http-client explicitly.

If drupal/cms already constrains this, forcing '^6.4' may create avoidable solver conflicts in consumer projects.

Would you like me to scan the repo for an existing direct requirement or open a follow-up to make this addition conditional on absence?

Comment on lines 36 to 38
# Ignore all recipes by default. Custom recipes should be added explicitly.
recipes/*

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Confirm runtime expectations for recipes in deployment artifacts; optionally guard both locations.

  • Verify that provisioning/production never relies on ../recipes/* at runtime; if it does, unignore the required paths here.
  • If there’s any chance recipes are mapped under web/recipes in some environments, add a defensive ignore.
 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+web/recipes/*
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Ignore all recipes by default. Custom recipes should be added explicitly.
recipes/*
# Ignore all recipes by default. Custom recipes should be added explicitly.
recipes/*
web/recipes/*
🤖 Prompt for AI Agents
In .gitignore.artifact around lines 36 to 38, the review asks you to confirm
whether runtime/provisioning ever depends on ../recipes/* and to defensively
cover alternate locations; verify that production/provisioning does not require
files under ../recipes at runtime and if it does, remove or adjust this ignore
to allow those specific paths, otherwise add an additional ignore entry for
web/recipes (or other mapped locations) so artifacts remain consistent across
environments; update the .gitignore.artifact accordingly and document the
decision in the PR description.

Comment on lines +149 to 156
* @param mixed $resolved
* The resolved value from resolvedValue().
*
* @return string|null
* The message to display, or null if no message needed.
*/
public function resolvedMessage(array $responses): ?string;
public function resolvedMessage(array $responses, mixed $resolved): ?string;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Interface signature change is a BC break; ensure all implementers and call sites are updated; fix doc typos.

  • Verify every HandlerInterface implementation now accepts the new $resolved param.
  • Update any calls to resolvedMessage(...) to pass the resolved value.
  • Minor doc fixes: “handlerManager” → “PromptManager”, “handlering” → “prompting”.

Search for outdated signatures and call sites:

Docblock tweak (outside the changed lines):

/**
 * Get a message to display when showing the resolved value.
 *
 * This is used by PromptManager to show an appropriate message (via
 * info(), ok(), etc.) when using a resolved value instead of prompting
 * for input.
 */

🏁 Script executed:

#!/bin/bash
# Implementations missing $resolved in signature
rg -nP --type=php '(?s)class\s+\w+\s+implements\s+HandlerInterface.*?\bfunction\s+resolvedMessage\s*\(\s*array\s+\$responses\s*\)' -C2

# Calls passing only $responses
rg -nP --type=php '\bresolvedMessage\s*\(\s*\$[a-zA-Z_][\w]*\s*\)'

# Interface reference to help fix doc wording
rg -nP --type=php 'PromptManager|handlerManager|handlering' .vortex/installer/src/Prompts

Length of output: 797


Fix doc typos in HandlerInterface
Replace “handlerManager” with “PromptManager” and “handlering” with “prompting” in the docblock above resolvedMessage (HandlerInterface.php lines 143–144).

🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php lines 143–156: the
docblock above resolvedMessage contains typos — change “handlerManager” to
“PromptManager” and “handlering” to “prompting” in the comment text so the
parameter/class names and verbs read correctly; keep the rest of the docblock
intact and ensure spelling/casing matches existing project conventions.

Comment on lines 76 to 88
public function resolvedValue(array $responses): null|string|bool|array {
$discovered = $this->discover();

// If user selected 'custom', use the ProfileCustom response instead.
if ($v === self::CUSTOM && isset($this->responses['profile_custom'])) {
$v = $this->responses['profile_custom'];
if (!is_null($discovered)) {
return $discovered;
}

$t = $this->tmpDir;
$w = $this->webroot;

File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);
if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
return '../recipes/drupal_cms_starter';
}

if (in_array($v, [self::STANDARD, self::MINIMAL, self::DEMO_UMAMI])) {
File::rmdir(sprintf('%s/%s/profiles/your_site_profile', $t, $w));
File::rmdir(sprintf('%s/%s/profiles/custom/your_site_profile', $t, $w));
return NULL;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Avoid hardcoding the DrupalCMS recipe path; use a class constant.

Improves maintainability and reuse in tests and other handlers.

-    if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
-      return '../recipes/drupal_cms_starter';
+    if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
+      return self::DRUPALCMS_STARTER_RECIPE;
     }

Add this constant near other constants (outside the diff range):

const DRUPALCMS_STARTER_RECIPE = '../recipes/drupal_cms_starter';
🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/Profile.php around lines 76-88: the
DrupalCMS recipe path is hardcoded in resolvedValue; add the constant
DRUPALCMS_STARTER_RECIPE = '../recipes/drupal_cms_starter' alongside the other
class constants (outside the shown diff) and replace the string literal in
resolvedValue with self::DRUPALCMS_STARTER_RECIPE to avoid hardcoding and enable
reuse in tests/other handlers.

Comment on lines +147 to +161
// Assume that profiles provided as a path are contrib profiles.
$is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);

if (in_array($v, [self::STANDARD, self::MINIMAL, self::DEMO_UMAMI]) || $is_contrib_profile) {
File::rmdir(sprintf('%s/%s/profiles/your_site_profile', $t, $w));
File::rmdir(sprintf('%s/%s/profiles/custom/your_site_profile', $t, $w));

File::replaceContentAsync([
'/profiles/your_site_profile,' => '',
'/profiles/custom/your_site_profile,' => '',
]);
}
else {
File::replaceContentAsync('your_site_profile', $v);
File::renameInDir($t, 'your_site_profile', $v);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

⚠️ Potential issue

Fix OS-specific path detection for contrib/profile paths.

str_contains($v, DIRECTORY_SEPARATOR) fails on Windows when $v uses '/' (e.g., '../recipes/...'), leading to incorrect branch and potential bad renames. Detect both separators.

-    $is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
+    // Detect both Unix and Windows separators in provided path.
+    $is_contrib_profile = (strpbrk($v, '/\\') !== FALSE);

Consider adding a small unit test covering Unix-style and Windows-style paths.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Assume that profiles provided as a path are contrib profiles.
$is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
if (in_array($v, [self::STANDARD, self::MINIMAL, self::DEMO_UMAMI]) || $is_contrib_profile) {
File::rmdir(sprintf('%s/%s/profiles/your_site_profile', $t, $w));
File::rmdir(sprintf('%s/%s/profiles/custom/your_site_profile', $t, $w));
File::replaceContentAsync([
'/profiles/your_site_profile,' => '',
'/profiles/custom/your_site_profile,' => '',
]);
}
else {
File::replaceContentAsync('your_site_profile', $v);
File::renameInDir($t, 'your_site_profile', $v);
// Assume that profiles provided as a path are contrib profiles.
// Detect both Unix and Windows separators in provided path.
$is_contrib_profile = (strpbrk($v, '/\\') !== FALSE);
if (in_array($v, [self::STANDARD, self::MINIMAL, self::DEMO_UMAMI]) || $is_contrib_profile) {
File::rmdir(sprintf('%s/%s/profiles/your_site_profile', $t, $w));
File::rmdir(sprintf('%s/%s/profiles/custom/your_site_profile', $t, $w));
File::replaceContentAsync([
'/profiles/your_site_profile,' => '',
'/profiles/custom/your_site_profile,' => '',
]);
}
else {
File::replaceContentAsync('your_site_profile', $v);
File::renameInDir($t, 'your_site_profile', $v);
🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/Profile.php around lines 147 to 161: the
contrib-profile detection uses str_contains($v, DIRECTORY_SEPARATOR) which fails
when paths use the other OS separator (e.g. '/' on Windows or '\' on Unix
inputs); change the check to detect either separator (e.g. test for '/' OR '\')
so both Unix-style and Windows-style paths are treated as contrib profiles, then
keep the existing branch logic; add a unit test that passes examples with
'../recipes/...' and '..\\recipes\\...' to cover both separators.

Comment on lines 32 to 61
@test "Workflow: profile-driven, DrupalCMS" {
rm -f .data/db.sql
export VORTEX_INSTALLER_IS_DEMO_DB_DOWNLOAD_SKIP=1
assert_file_not_exists .data/db.sql

export VORTEX_INSTALLER_PROMPT_STARTER="drupalcms_profile"

prepare_sut "Starting fresh install WORKFLOW tests in build directory ${BUILD_DIR}"
# Assert that the database was not downloaded because VORTEX_INSTALLER_IS_DEMO_DB_DOWNLOAD_SKIP was set.
assert_file_not_exists .data/db.sql

assert_file_contains .env "DRUPAL_PROFILE=../recipes/drupal_cms_starter"
assert_file_not_contains .env "DEMO"
assert_file_contains composer.json "drupal/cms"
assert_file_contains composer.json "wikimedia/composer-merge-plugin"
assert_file_contains composer.json "vendor/drupal/cms/composer.json"

echo "VORTEX_PROVISION_TYPE=profile" >>.env

assert_ahoy_build
assert_gitignore

assert_ahoy_lint

assert_ahoy_test "web" "1"

assert_ahoy_fe

assert_webpage_contains "/" "This is the home page of your new site."
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Add assertion for symfony/http-client to fully validate Starter::process mutations.

Starter::process() also adds symfony/http-client. Assert it to tighten the e2e check.

   assert_file_contains composer.json "drupal/cms"
   assert_file_contains composer.json "wikimedia/composer-merge-plugin"
   assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+  assert_file_contains composer.json "symfony/http-client"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@test "Workflow: profile-driven, DrupalCMS" {
rm -f .data/db.sql
export VORTEX_INSTALLER_IS_DEMO_DB_DOWNLOAD_SKIP=1
assert_file_not_exists .data/db.sql
export VORTEX_INSTALLER_PROMPT_STARTER="drupalcms_profile"
prepare_sut "Starting fresh install WORKFLOW tests in build directory ${BUILD_DIR}"
# Assert that the database was not downloaded because VORTEX_INSTALLER_IS_DEMO_DB_DOWNLOAD_SKIP was set.
assert_file_not_exists .data/db.sql
assert_file_contains .env "DRUPAL_PROFILE=../recipes/drupal_cms_starter"
assert_file_not_contains .env "DEMO"
assert_file_contains composer.json "drupal/cms"
assert_file_contains composer.json "wikimedia/composer-merge-plugin"
assert_file_contains composer.json "vendor/drupal/cms/composer.json"
echo "VORTEX_PROVISION_TYPE=profile" >>.env
assert_ahoy_build
assert_gitignore
assert_ahoy_lint
assert_ahoy_test "web" "1"
assert_ahoy_fe
assert_webpage_contains "/" "This is the home page of your new site."
}
@test "Workflow: profile-driven, DrupalCMS" {
rm -f .data/db.sql
export VORTEX_INSTALLER_IS_DEMO_DB_DOWNLOAD_SKIP=1
assert_file_not_exists .data/db.sql
export VORTEX_INSTALLER_PROMPT_STARTER="drupalcms_profile"
prepare_sut "Starting fresh install WORKFLOW tests in build directory ${BUILD_DIR}"
# Assert that the database was not downloaded because VORTEX_INSTALLER_IS_DEMO_DB_DOWNLOAD_SKIP was set.
assert_file_not_exists .data/db.sql
assert_file_contains .env "DRUPAL_PROFILE=../recipes/drupal_cms_starter"
assert_file_not_contains .env "DEMO"
assert_file_contains composer.json "drupal/cms"
assert_file_contains composer.json "wikimedia/composer-merge-plugin"
assert_file_contains composer.json "vendor/drupal/cms/composer.json"
assert_file_contains composer.json "symfony/http-client"
echo "VORTEX_PROVISION_TYPE=profile" >>.env
assert_ahoy_build
assert_gitignore
assert_ahoy_lint
assert_ahoy_test "web" "1"
assert_ahoy_fe
assert_webpage_contains "/" "This is the home page of your new site."
}
🤖 Prompt for AI Agents
.vortex/tests/bats/e2e/workflow.install.profile.bats around lines 32 to 61: the
test currently asserts composer.json contains drupal/cms,
wikimedia/composer-merge-plugin and vendor/drupal/cms/composer.json but misses
the symfony/http-client package added by Starter::process(); add an assertion to
check composer.json contains "symfony/http-client" (same style as the other
assert_file_contains calls) so the e2e test verifies that Starter::process
mutations include that dependency.

Comment on lines +61 to 69
status:
label: Published
datasource_id: 'entity:node'
property_path: 'body:processed'
type: 'solr_text_custom:ngram'
property_path: status
type: boolean
dependencies:
config:
- field.storage.node.body
module:
- node
title:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Switch to boolean status field is sound; plan reindex + update path.

Replacing body:processed with boolean status aligns with the new Views filter and Solr boolean filtering. On existing installs, this config in config/install won’t apply automatically—ensure a config update/post-update imports this change and triggers a full reindex to avoid stale results.

Suggested steps:

  • Config deploy: drush cim -y (or a module post-update to import this specific index config).
  • Rebuild + reindex: drush sapi-c && drush sapi-i -y content
🤖 Prompt for AI Agents
In web/modules/custom/ys_search/config/install/search_api.index.content.yml
around lines 61 to 69, the change from body:processed to a boolean status field
will not be applied on already-installed sites from config/install alone; add a
post-update hook (or update hook) in the module to import the updated index
config and trigger a full reindex on deploy. Implement the hook to
programmatically run the config import for this specific search_api index (or
call drush cim for the module's config), then call the Search API reindex
commands (or use the Search API PHP API to clear and index content) —
alternatively document and run drush cim -y && drush sapi-c && drush sapi-i -y
as part of the release; ensure the hook logs success/failure and is idempotent
so repeated runs are safe.

@codecov
Copy link

codecov bot commented Sep 1, 2025

Codecov Report

❌ Patch coverage is 90.14085% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.67%. Comparing base (20f454c) to head (c90ee6a).
⚠️ Report is 2 commits behind head on develop.

Files with missing lines Patch % Lines
.vortex/installer/src/Prompts/PromptManager.php 66.66% 4 Missing ⚠️
.vortex/installer/src/Prompts/Handlers/Profile.php 88.00% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1955      +/-   ##
===========================================
+ Coverage    77.26%   77.67%   +0.40%     
===========================================
  Files           89       89              
  Lines         5464     5496      +32     
  Branches        35       35              
===========================================
+ Hits          4222     4269      +47     
+ Misses        1242     1227      -15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from 52dccd8 to e0c9246 Compare September 1, 2025 11:01
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 11:03 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)

49-60: Avoid conflicting inference between default() and resolvedValue().

Remove hosting-provider logic from default(); keep all auto-resolution in resolvedValue() to ensure a single source of truth.

Apply:

   public function default(array $responses): null|string|bool|array {
-    // Auto-select webroot based on hosting provider.
-    if (isset($responses[HostingProvider::id()])) {
-      return match ($responses[HostingProvider::id()]) {
-        HostingProvider::ACQUIA => self::DOCROOT,
-        HostingProvider::LAGOON => self::WEB,
-        default => self::WEB,
-      };
-    }
-
-    return NULL;
+    return NULL;
   }

98-110: Centralize provider-based resolution here.

Mirror the mapping that used to live in default(), and include LAGOON/others for consistency.

Apply:

   public function resolvedValue(array $responses): null|string|bool|array {
     $discovered = $this->discover();

     if (!is_null($discovered)) {
       return $discovered;
     }

-    if (($responses[HostingProvider::id()] ?? '') === HostingProvider::ACQUIA) {
-      return self::DOCROOT;
-    }
-
-    return NULL;
+    if (isset($responses[HostingProvider::id()])) {
+      return match ($responses[HostingProvider::id()]) {
+        HostingProvider::ACQUIA => self::DOCROOT,
+        HostingProvider::LAGOON => self::WEB,
+        default => self::WEB,
+      };
+    }
+
+    return NULL;
   }

126-146: Guard the rename to avoid collisions and missing source dir.

Current rename() can fail if webroot directory is missing or target exists.

Apply:

   public function process(): void {
     $v = $this->getResponseAsString();
     $t = $this->tmpDir;
     $webroot = self::WEB;

     if ($v === $webroot) {
       return;
     }

+    $src = $t . DIRECTORY_SEPARATOR . $webroot;
+    $dst = $t . DIRECTORY_SEPARATOR . $v;
+    if (!is_dir($src)) {
+      // Nothing to move; skip safely.
+      return;
+    }
+    if ($src === $dst || is_dir($dst)) {
+      // Either same path or target already exists; skip to avoid data loss.
+      return;
+    }
+
     File::replaceContentAsync([
       sprintf('%s/', $webroot) => $v . '/',
       sprintf('%s\/', $webroot) => $v . '\/',
       sprintf(': %s', $webroot) => ': ' . $v,
       sprintf('=%s', $webroot) => '=' . $v,
       sprintf('!%s', $webroot) => '!' . $v,
       sprintf('/\/%s\//', $webroot) => '/' . $v . '/',
       sprintf('/\'\/%s\'/', $webroot) => "'/" . $v . "'",
     ]);

-    rename($t . DIRECTORY_SEPARATOR . $webroot, $t . DIRECTORY_SEPARATOR . $v);
+    rename($src, $dst);
   }
.vortex/installer/src/Prompts/Handlers/Starter.php (2)

32-51: Standardize product name and wording (“Drupal CMS”, “recipe”).

Use the canonical “Drupal CMS” spelling and “recipe” terminology for consistency with Composer/Drupal docs and the option label below.

-    $label2 = Tui::bold('DrupalCMS, installed from profile');
+    $label2 = Tui::bold('Drupal CMS, installed from recipe');
@@
-      Creates a new site by {$label21}
-      from the DrupalCMS recipe.
+      Creates a new site by {$label21}
+      from the Drupal CMS recipe.

126-138: Use strict comparisons in postInstall branches.

-    if ($this->response == self::DRUPAL_LOAD_DATABASE_DEMO) {
+    if ($this->response === self::DRUPAL_LOAD_DATABASE_DEMO) {
@@
-    elseif ($this->response == self::DRUPAL_INSTALL_PROFILE || $this->response == self::DRUPALCMS_INSTALL_PROFILE) {
+    elseif ($this->response === self::DRUPAL_INSTALL_PROFILE || $this->response === self::DRUPALCMS_INSTALL_PROFILE) {
♻️ Duplicate comments (12)
.gitignore.artifact (1)

36-38: Also ignore web/recipes in artifacts (web is un-ignored).

Because !web includes everything under web/, recipe contents under web/recipes would slip into artifacts. Add a defensive ignore.

 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+web/recipes/*
composer.json (2)

152-156: Missing installer type “drupal-recipe” — recipes won’t install.

oomphinc/composer-installers-extender requires the type to be declared.

         "installer-types": [
           "bower-asset",
           "npm-asset",
-          "drupal-library"
+          "drupal-library",
+          "drupal-recipe"
         ],

6-30: Wire Drupal CMS and merge-plugin per PR objective.

Add drupal/cms, require and allow wikimedia/composer-merge-plugin, and include vendor/drupal/cms/composer.json under extra.merge-plugin.

Diffs within selected ranges:

   "require": {
     "php": ">=8.3",
     "composer/installers": "^2.3",
     "cweagans/composer-patches": "^1.7",
+    "drupal/cms": "^1.0",
     "drupal/admin_toolbar": "^3.6",
...
-    "webflo/drupal-finder": "^1.3"
+    "webflo/drupal-finder": "^1.3",
+    "wikimedia/composer-merge-plugin": "^2.1"
   },
   "config": {
     "allow-plugins": {
       "composer/installers": true,
       "cweagans/composer-patches": true,
       "dealerdirect/phpcodesniffer-composer-installer": true,
       "drupal/core-composer-scaffold": true,
       "ergebnis/composer-normalize": true,
       "oomphinc/composer-installers-extender": true,
       "php-http/discovery": true,
       "phpstan/extension-installer": true,
       "pyrech/composer-changelogs": true,
-      "tbachert/spi": true
+      "tbachert/spi": true,
+      "wikimedia/composer-merge-plugin": true
     },

Additional block to add inside "extra" (outside the selected hunk; place near other extra keys):

"merge-plugin": {
  "include": [
    "vendor/drupal/cms/composer.json"
  ],
  "merge-dev": false,
  "recurse": true,
  "replace": false,
  "ignore-duplicates": true
}

After changes, please run:

composer validate
composer update --dry-run

Also applies to: 73-85, 92-118

.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

141-155: Fix doc typos: PromptManager and “prompting”.

Tighten the doc to match current naming.

   /**
    * Get a message to display when showing the resolved value.
    *
-   * This is used by handlerManager to show an appropriate message (via
-   * info(), ok(), etc.) when using a resolved value instead of handlering
+   * This is used by PromptManager to show an appropriate message (via
+   * info(), ok(), etc.) when using a resolved value instead of prompting
    * for input.
    *
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1)

7-14: Fix incorrect coverage target to match the subject (Starter).

This mirrors an existing comment; also drop the now-unused ProvisionType import.

Apply:

-use DrevOps\VortexInstaller\Prompts\Handlers\ProvisionType;
 use DrevOps\VortexInstaller\Prompts\Handlers\Starter;
@@
-#[CoversClass(ProvisionType::class)]
+#[CoversClass(Starter::class)]
 class StarterInstallTest extends AbstractInstallTestCase {
.vortex/tests/bats/e2e/workflow.install.profile.bats (1)

45-47: Assert symfony/http-client addition as part of Starter::process().

Repeat of a prior suggestion; tighten the e2e check.

Apply:

   assert_file_contains composer.json "drupal/cms"
   assert_file_contains composer.json "wikimedia/composer-merge-plugin"
   assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+  assert_file_contains composer.json "symfony/http-client"
web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-69: Switch to boolean Published field is correct; ensure deploy path + full reindex.

This change won’t auto-apply on existing sites via config/install. Add a post-update (or documented deploy step) to import this updated index config and trigger a full reindex to avoid stale results.

Example post-update (outside this file):

// web/modules/custom/ys_search/ys_search.post_update.php
<?php

use Drupal\search_api\Entity\Index;

/**
 * Switch content index to boolean "status" and reindex.
 */
function ys_search_post_update_0001_status_boolean_reindex(&$sandbox = NULL) : void {
  /** @var \Drupal\search_api\IndexInterface|null $index */
  $index = Index::load('content');
  if ($index) {
    // Clear tracker to force reindex; actual indexing runs via cron/queue.
    $index->rebuildTracker();
    $index->save();
  }
  // Optionally: log a reminder to run: drush sapi-c && drush sapi-i -y content
  \Drupal::logger('ys_search')->notice('Search index "content" updated; run reindex.');
}
.vortex/installer/src/Prompts/Handlers/Profile.php (2)

76-88: Avoid hardcoding recipe path; use a class constant.

Improves reuse and keeps tests/handlers in sync.

   public function resolvedValue(array $responses): null|string|bool|array {
     $discovered = $this->discover();

     if (!is_null($discovered)) {
       return $discovered;
     }

     if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
-      return '../recipes/drupal_cms_starter';
+      return self::DRUPALCMS_STARTER_RECIPE;
     }

     return NULL;
   }

Add near other constants (outside this hunk):

const DRUPALCMS_STARTER_RECIPE = '../recipes/drupal_cms_starter';

147-151: Fix OS-agnostic path detection for contrib/custom profile paths.

str_contains($v, DIRECTORY_SEPARATOR) misses Unix-style paths on Windows and vice versa. Detect both separators.

-    $is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
+    // Treat any path that contains '/' or '\' as an external/contrib profile.
+    $is_contrib_profile = (strpbrk($v, '/\\') !== FALSE);
.vortex/installer/src/Prompts/PromptManager.php (2)

128-141: Guard info() against null resolved messages.

resolvedMessage() returns ?string. Avoid passing null.

-            info($handler->resolvedMessage($r, $resolved));
+            if ($m = $handler->resolvedMessage($r, $resolved)) {
+              info($m);
+            }

166-166: Apply the same guard for Webroot messaging.

Keep consistency and avoid null passed to info().

-              info($handler->resolvedMessage($r, $resolved));
+              if ($m = $handler->resolvedMessage($r, $resolved)) {
+                info($m);
+              }
.vortex/installer/src/Prompts/Handlers/Starter.php (1)

91-113: Null-dereference risk, unchecked write, and non-strict comparison.

JsonManipulator::fromFile() can return NULL; file_put_contents() can fail; and loose equality can misfire with non-string responses. Harden this block.

-    if ($this->response == self::DRUPALCMS_INSTALL_PROFILE) {
-      $cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
+    if ($this->response === self::DRUPALCMS_INSTALL_PROFILE) {
+      $composerPath = $this->tmpDir . '/composer.json';
+      $cj = JsonManipulator::fromFile($composerPath);
+      if ($cj === NULL) {
+        throw new \RuntimeException(sprintf('composer.json not readable at %s.', $composerPath));
+      }
@@
-      $c = $cj->getContents();
-      file_put_contents($this->tmpDir . '/composer.json', $c);
+      $c = $cj->getContents();
+      if (file_put_contents($composerPath, $c) === FALSE) {
+        throw new \RuntimeException(sprintf('Failed to write %s.', $composerPath));
+      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 52dccd8 and e0c9246.

⛔ Files ignored due to path filters (24)
  • .vortex/installer/tests/Fixtures/install/_baseline/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/tests/behat/features/search.feature is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/deploy_type_all_circleci/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/deploy_type_all_gha/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/deploy_type_artifact/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_demo_db/.ignorecontent is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_none/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (16)
  • .gitignore (2 hunks)
  • .gitignore.artifact (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Profile.php (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/Starter.php (6 hunks)
  • .vortex/installer/src/Prompts/Handlers/Webroot.php (1 hunks)
  • .vortex/installer/src/Prompts/PromptManager.php (2 hunks)
  • .vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1 hunks)
  • .vortex/tests/bats/_helper.bash (0 hunks)
  • .vortex/tests/bats/e2e/workflow.install.profile.bats (1 hunks)
  • composer.json (1 hunks)
  • tests/behat/features/search.feature (1 hunks)
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml (1 hunks)
  • web/modules/custom/ys_search/config/install/views.view.search.yml (1 hunks)
💤 Files with no reviewable changes (1)
  • .vortex/tests/bats/_helper.bash
🧰 Additional context used
📓 Path-based instructions (3)
composer.json

📄 CodeRabbit inference engine (CLAUDE.md)

When adding JavaScript or CSS libraries as Drupal libraries with Composer, define them as inline Composer packages in the 'repositories' section of composer.json

Files:

  • composer.json
web/modules/custom/**

📄 CodeRabbit inference engine (CLAUDE.md)

Custom modules should be placed in 'web/modules/custom/'

Files:

  • web/modules/custom/ys_search/config/install/search_api.index.content.yml
  • web/modules/custom/ys_search/config/install/views.view.search.yml
tests/behat/**/*.feature

📄 CodeRabbit inference engine (CLAUDE.md)

tests/behat/**/*.feature: All Behat feature files must use the standard user story format: 'As a [user type]\nI want to [action]\nSo that [benefit]'
Use consistent user types in Behat feature files: 'site visitor', 'site administrator', and 'content editor'
Do not use commas or periods in user story statements within Behat feature files

Files:

  • tests/behat/features/search.feature
🧬 Code graph analysis (9)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (3)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/tests/bats/e2e/workflow.install.profile.bats (2)
.vortex/tests/bats/_helper.workflow.bash (6)
  • prepare_sut (7-39)
  • assert_ahoy_build (73-130)
  • assert_gitignore (132-162)
  • assert_ahoy_lint (413-425)
  • assert_ahoy_test (527-544)
  • assert_ahoy_fe (753-777)
.vortex/tests/bats/_helper.bash (1)
  • assert_webpage_contains (1022-1028)
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (5)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/PromptManager.php (2)
  • PromptManager (58-581)
  • makeEnvName (431-433)
.vortex/installer/src/Utils/Env.php (2)
  • Env (7-245)
  • put (24-26)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)
  • CoversClass (11-42)
.vortex/installer/tests/Functional/Handlers/AbstractInstallTestCase.php (1)
  • AbstractInstallTestCase (19-101)
.vortex/installer/src/Prompts/Handlers/Starter.php (2)
.vortex/installer/src/Utils/JsonManipulator.php (2)
  • JsonManipulator (10-76)
  • fromFile (19-36)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • AbstractHandler (10-233)
.vortex/installer/src/Prompts/Handlers/Profile.php (4)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (4)
  • resolvedValue (130-132)
  • id (52-62)
  • resolvedMessage (137-139)
  • getResponseAsString (191-197)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • resolvedValue (138-138)
  • id (19-19)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Utils/File.php (2)
  • File (10-102)
  • replaceContentAsync (59-83)
.vortex/installer/src/Prompts/PromptManager.php (4)
.vortex/installer/src/Prompts/Handlers/Profile.php (3)
  • Profile (10-165)
  • resolvedValue (76-88)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
  • id (52-62)
  • resolvedValue (130-132)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Webroot.php (2)
  • resolvedValue (98-110)
  • resolvedMessage (115-121)
.vortex/installer/src/Utils/Tui.php (1)
  • info (42-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: build (0)
  • GitHub Check: build (1)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-installer (8.3)
  • GitHub Check: vortex-test-docs
🔇 Additional comments (14)
composer.json (1)

133-135: Decide on recipe install path: project-root vs web/.

Current path installs to recipes/{$name}, while the issue description mentions web/recipes/{$name}. Confirm the intended location and align docs/tests accordingly.

Option A (keep project-root; update docs only): no code change here.

Option B (move under web/recipes):

-            "recipes/{$name}": [
+            "web/recipes/{$name}": [
               "type:drupal-recipe"
             ],
.gitignore (1)

44-46: LGTM: ignore recipes by default.

Matches the deployment artifact policy and avoids committing vendor-provided recipes; custom recipes can be explicitly un-ignored when needed.

.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

149-155: Verify BC break in HandlerInterface.resolvedMessage signature
Ensure every class implementing HandlerInterface updates resolvedMessage(array $responses, mixed $resolved) and that all invocations pass both $responses and $resolved.

.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)

137-139: LGTM: abstract method matches interface.

Signature now aligns with HandlerInterface; no further changes needed here.

.vortex/installer/src/Prompts/Handlers/Webroot.php (1)

115-117: Signature alignment and message: OK.

Matches the new HandlerInterface; message mirrors Profile’s pattern.

.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)

23-30: Adds DrupalCMS selection path: OK.

Covers second DOWN navigation; keep option order stable to avoid flakiness.

tests/behat/features/search.feature (3)

25-27: Published filtering assertions: OK.

Validates exclusion of draft; keep.


31-33: Focused result assertion: OK.

Single-result expectation is precise.


11-18: Unable to locate the Given page content step definition—please manually confirm it handles status and moderation_state columns.

.vortex/installer/src/Prompts/Handlers/Profile.php (2)

90-99: Resolved message formatting — LGTM.

Clear and consistent with Webroot messaging.


145-145: Escape backslashes and dollar signs in $v before calling File::replaceContentInFile.

replaceContentInFile uses a regex‐based replacement internally; unescaped backslashes (\) or dollar signs ($) in the replacement string are treated as backreferences and will corrupt values like Windows paths (C:\path\to\dir) or strings containing “$”.

Suggested refactor:

// Escape “\” and “$” for safe preg_replace replacement
$safe = strtr($v, ['\\' => '\\\\', '$' => '\\$']);

File::replaceContentInFile(
    $t . '/.env',
    '/^DRUPAL_PROFILE=.*/m',
    'DRUPAL_PROFILE=' . $safe
);

Verify that replaceContentInFile indeed uses preg_replace internally and test this change against your .env formats.

.vortex/installer/src/Prompts/Handlers/Starter.php (3)

7-7: Import looks correct.

JsonManipulator is the right utility to mutate composer.json here.


14-17: New DrupalCMS option constants — LGTM.

Clear naming and consistent with existing constants.


94-97: Confirm necessity of symfony/http-client.

Not listed in the issue’s steps. If unused by the installer flow or recipes, omit to reduce dependency surface.

Comment on lines 65 to 73
public function options(array $responses): ?array {
return [
self::DRUPAL_INSTALL_PROFILE => 'Drupal, installed from profile',
self::DRUPALCMS_INSTALL_PROFILE => 'DrupalCMS, installed from profile',
self::DRUPAL_LOAD_DATABASE_DEMO => 'Drupal, loaded from the demo database',
];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Align option label with copy (“Drupal CMS”, “recipe”).

-      self::DRUPALCMS_INSTALL_PROFILE => 'DrupalCMS, installed from profile',
+      self::DRUPALCMS_INSTALL_PROFILE => 'Drupal CMS, installed from recipe',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function options(array $responses): ?array {
return [
self::DRUPAL_INSTALL_PROFILE => 'Drupal, installed from profile',
self::DRUPALCMS_INSTALL_PROFILE => 'DrupalCMS, installed from profile',
self::DRUPAL_LOAD_DATABASE_DEMO => 'Drupal, loaded from the demo database',
];
}
public function options(array $responses): ?array {
return [
self::DRUPAL_INSTALL_PROFILE => 'Drupal, installed from profile',
self::DRUPALCMS_INSTALL_PROFILE => 'Drupal CMS, installed from recipe',
self::DRUPAL_LOAD_DATABASE_DEMO => 'Drupal, loaded from the demo database',
];
}
🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/Starter.php around lines 65–71: update
the option labels to match the copy by changing occurrences of "Drupal" to
"Drupal CMS" where appropriate and replace "installed from profile" with
"installed from recipe" (so the three labels become "Drupal, installed from
recipe", "Drupal CMS, installed from recipe", and "Drupal CMS, loaded from the
demo database"). Ensure exact wording and punctuation match these strings.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from e0c9246 to f38c8d4 Compare September 1, 2025 11:15
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 11:18 Inactive
@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from f38c8d4 to 1251954 Compare September 1, 2025 11:21
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 11:23 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)

126-139: Use strict comparisons for response checks.

Avoid type juggling on enum-like constants.

Apply this diff:

-    if ($this->response == self::DRUPAL_LOAD_DATABASE_DEMO) {
+    if ($this->response === self::DRUPAL_LOAD_DATABASE_DEMO) {
@@
-    elseif ($this->response == self::DRUPAL_INSTALL_PROFILE || $this->response == self::DRUPALCMS_INSTALL_PROFILE) {
+    elseif ($this->response === self::DRUPAL_INSTALL_PROFILE || $this->response === self::DRUPALCMS_INSTALL_PROFILE) {
♻️ Duplicate comments (14)
web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-68: Published boolean field added — plan update path + reindex for existing sites.

Config in config/install won’t update already-installed environments. Add a post-update to apply this field to live config and trigger a full reindex to prevent mismatches with the view filter.

Example post-update (modules/custom/ys_search/ys_search.post_update.php):

<?php

use Drupal\search_api\Entity\Index;

/**
 * Add boolean status field to Search API "content" index and queue reindex.
 */
function ys_search_post_update_0001_add_status_field_and_reindex(&$sandbox = NULL) {
  $config = \Drupal::service('config.factory')->getEditable('search_api.index.content');
  $field_settings = $config->get('field_settings') ?: [];
  if (empty($field_settings['status'])) {
    $field_settings['status'] = [
      'label' => 'Published',
      'datasource_id' => 'entity:node',
      'property_path' => 'status',
      'type' => 'boolean',
      'dependencies' => ['module' => ['node']],
    ];
    $config->set('field_settings', $field_settings)->save();
  }

  if ($index = Index::load('content')) {
    // Clear and schedule full reindex.
    $index->clear();
    $index->rebuildTracker();
    $index->reindex();
  }

  return 'Search index updated with boolean status and reindex queued.';
}
tests/behat/features/search.feature (1)

18-20: Replace fixed sleep with deterministic wait for index readiness.

Avoid flakiness; wait until the index reports completion before navigating.

-    And I run search indexing for 3 items
-    And I wait for 5 seconds
-    And I visit "/search"
+    And I run search indexing for 3 items
+    And I wait for search indexing to complete
+    And I visit "/search"
web/modules/custom/ys_search/config/install/views.view.search.yml (2)

210-210: Teaser view mode switch — verify all bundles have teaser configured.

Ensure parity with previous output; add/adjust teaser displays where missing.


165-202: Boolean status filter is correct; drop redundant expose.remember_roles.

Filter isn’t exposed; remember_roles has no effect. Clean it up.

         status:
           id: status
           table: search_api_index_content
           field: status
           relationship: none
           group_type: group
           admin_label: ''
           plugin_id: search_api_boolean
           operator: '='
           value: '1'
           group: 1
           exposed: false
           expose:
             operator_id: ''
             label: ''
             description: ''
             use_operator: false
             operator: ''
             operator_limit_selection: false
             operator_list: { }
             identifier: ''
             required: false
             remember: false
             multiple: false
-            remember_roles:
-              authenticated: authenticated
           is_grouped: false
           group_info:
             label: ''
             description: ''
             identifier: ''
             optional: true
             widget: select
             multiple: false
             remember: false
             default_group: All
             default_group_multiple: { }
             group_items: { }
.gitignore.artifact (1)

36-38: Add defensive ignore for web/recipes in artifacts.

If any env maps recipes under web/, these would currently be included because web is un-ignored. Add a guard and confirm provisioning/runtime never requires these paths in artifacts.

 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+web/recipes/*
.gitignore (1)

34-34: Tiny wording consistency: “scaffold” lowercase.

Matches prior style guidance; optional.

-# Allow Drupal Scaffold files. @see https://github.com/drupal-composer/drupal-scaffold#limitation
+# Allow Drupal scaffold files. @see https://github.com/drupal-composer/drupal-scaffold#limitation
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

141-156: Fix doc typos and verify BC updates across implementers/call sites.

  • Doc: “handlerManager” → “PromptManager”; “handlering” → “prompting”.
  • Signature change is a BC break; ensure all implementers accept $resolved and all callers pass it.
   /**
    * Get a message to display when showing the resolved value.
    *
-   * This is used by handlerManager to show an appropriate message (via
-   * info(), ok(), etc.) when using a resolved value instead of handlering
-   * for input.
+   * This is used by PromptManager to show an appropriate message (via
+   * info(), ok(), etc.) when using a resolved value instead of prompting
+   * for input.
    *
    * @param array $responses
    *   Current form responses for context-aware message generation.
    * @param mixed $resolved
    *   The resolved value from resolvedValue().

Run to verify implementers and call sites:

#!/bin/bash
# Implementations missing $resolved in signature
rg -nP --type=php '(?s)class\s+\w+\s+implements\s+HandlerInterface.*?\bfunction\s+resolvedMessage\s*\(\s*array\s+\$responses\s*\)' -C2

# Calls passing only $responses
rg -nP --type=php '\bresolvedMessage\s*\(\s*\$[a-zA-Z_][\w]*\s*\)(?!\s*:)'
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1)

13-14: Fix incorrect coverage annotation (duplicate of prior feedback)

This test drives Starter via env; annotate coverage for Starter and drop the unused ProvisionType import.

Apply:

-use DrevOps\VortexInstaller\Prompts\Handlers\ProvisionType;
+// (no import needed)

-#[CoversClass(ProvisionType::class)]
+#[CoversClass(Starter::class)]
.vortex/tests/bats/e2e/workflow.install.profile.bats (1)

45-47: Add missing assertion for symfony/http-client added by Starter::process().

Covers an important mutation and tightens the e2e check.

Apply this diff:

   assert_file_contains composer.json "wikimedia/composer-merge-plugin"
   assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+  assert_file_contains composer.json "symfony/http-client"
.vortex/installer/src/Prompts/Handlers/Starter.php (1)

29-37: Align labels/copy: “Drupal CMS”, “recipe”.

Match installer wording consistently across description and options.

Apply this diff:

-    $label1 = Tui::bold('Drupal, installed from profile');
+    $label1 = Tui::bold('Drupal, installed from recipe');
@@
-    $label2 = Tui::bold('DrupalCMS, installed from profile');
+    $label2 = Tui::bold('Drupal CMS, installed from recipe');
@@
-    $label3 = Tui::bold('Drupal, loaded from the demo database');
+    $label3 = Tui::bold('Drupal CMS, loaded from the demo database');
@@
-      self::DRUPAL_INSTALL_PROFILE => 'Drupal, installed from profile',
-      self::DRUPALCMS_INSTALL_PROFILE => 'DrupalCMS, installed from profile',
-      self::DRUPAL_LOAD_DATABASE_DEMO => 'Drupal, loaded from the demo database',
+      self::DRUPAL_INSTALL_PROFILE => 'Drupal, installed from recipe',
+      self::DRUPALCMS_INSTALL_PROFILE => 'Drupal CMS, installed from recipe',
+      self::DRUPAL_LOAD_DATABASE_DEMO => 'Drupal CMS, loaded from the demo database',

Also applies to: 65-71

.vortex/installer/src/Prompts/Handlers/Profile.php (2)

76-88: Avoid hardcoded DrupalCMS recipe path; introduce a class constant.

Improves reuse and reduces drift.

Apply this diff:

-    if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
-      return '../recipes/drupal_cms_starter';
+    if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
+      return self::DRUPALCMS_STARTER_RECIPE;
     }

Add near other constants:

// Inside class Profile, near other constants.
const DRUPALCMS_STARTER_RECIPE = '../recipes/drupal_cms_starter';

131-163: Fix OS-agnostic path detection and remove magic key for custom profile.

Prevents Windows false negatives and string drift.

Apply this diff:

-    // If user selected 'custom', use the ProfileCustom response instead.
-    if ($v === self::CUSTOM && isset($this->responses['profile_custom'])) {
-      $v = $this->responses['profile_custom'];
+    // If user selected 'custom', use the ProfileCustom response instead.
+    if ($v === self::CUSTOM && isset($this->responses[ProfileCustom::id()])) {
+      $v = $this->responses[ProfileCustom::id()];
     }
@@
-    // Assume that profiles provided as a path are contrib profiles.
-    $is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
+    // Assume that profiles provided as a path are contrib profiles (handle '/' and '\').
+    $is_contrib_profile = (strpbrk($v, "/\\") !== FALSE);
.vortex/installer/src/Prompts/PromptManager.php (2)

133-134: Guard info() call; resolvedMessage() is nullable.

Avoid passing null under strict_types; cache the message and call info() only if non-null.

-            info($handler->resolvedMessage($r, $resolved));
+            if ($m = $handler->resolvedMessage($r, $resolved)) {
+              info($m);
+            }

166-166: Apply the same null-guard for Webroot messaging.

resolvedMessage() returns ?string; protect the info() call.

-              info($handler->resolvedMessage($r, $resolved));
+              if ($m = $handler->resolvedMessage($r, $resolved)) {
+                info($m);
+              }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f38c8d4 and 1251954.

⛔ Files ignored due to path filters (24)
  • .vortex/installer/tests/Fixtures/install/_baseline/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/tests/behat/features/search.feature is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/deploy_type_all_circleci/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/deploy_type_all_gha/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/deploy_type_artifact/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_demo_db/.ignorecontent is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_none/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (15)
  • .gitignore (2 hunks)
  • .gitignore.artifact (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Profile.php (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/Starter.php (6 hunks)
  • .vortex/installer/src/Prompts/Handlers/Webroot.php (1 hunks)
  • .vortex/installer/src/Prompts/PromptManager.php (2 hunks)
  • .vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (2 hunks)
  • .vortex/tests/bats/e2e/workflow.install.profile.bats (1 hunks)
  • composer.json (1 hunks)
  • tests/behat/features/search.feature (1 hunks)
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml (1 hunks)
  • web/modules/custom/ys_search/config/install/views.view.search.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
composer.json

📄 CodeRabbit inference engine (CLAUDE.md)

When adding JavaScript or CSS libraries as Drupal libraries with Composer, define them as inline Composer packages in the 'repositories' section of composer.json

Files:

  • composer.json
tests/behat/**/*.feature

📄 CodeRabbit inference engine (CLAUDE.md)

tests/behat/**/*.feature: All Behat feature files must use the standard user story format: 'As a [user type]\nI want to [action]\nSo that [benefit]'
Use consistent user types in Behat feature files: 'site visitor', 'site administrator', and 'content editor'
Do not use commas or periods in user story statements within Behat feature files

Files:

  • tests/behat/features/search.feature
web/modules/custom/**

📄 CodeRabbit inference engine (CLAUDE.md)

Custom modules should be placed in 'web/modules/custom/'

Files:

  • web/modules/custom/ys_search/config/install/views.view.search.yml
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml
🧬 Code graph analysis (9)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (5)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/PromptManager.php (2)
  • PromptManager (58-581)
  • makeEnvName (431-433)
.vortex/installer/src/Utils/Env.php (2)
  • Env (7-245)
  • put (24-26)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)
  • CoversClass (12-47)
.vortex/installer/tests/Functional/Handlers/AbstractInstallTestCase.php (1)
  • AbstractInstallTestCase (19-101)
.vortex/tests/bats/e2e/workflow.install.profile.bats (2)
.vortex/tests/bats/_helper.workflow.bash (6)
  • prepare_sut (7-39)
  • assert_ahoy_build (73-130)
  • assert_gitignore (132-162)
  • assert_ahoy_lint (413-425)
  • assert_ahoy_test (527-544)
  • assert_ahoy_fe (753-777)
.vortex/tests/bats/_helper.bash (1)
  • assert_webpage_contains (1022-1028)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (4)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • Profile (10-165)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Profile.php (5)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (4)
  • resolvedValue (130-132)
  • id (52-62)
  • resolvedMessage (137-139)
  • getResponseAsString (191-197)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • resolvedValue (138-138)
  • id (19-19)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Webroot.php (4)
  • resolvedValue (98-110)
  • discover (65-79)
  • resolvedMessage (115-121)
  • process (126-146)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Utils/File.php (2)
  • File (10-102)
  • replaceContentAsync (59-83)
.vortex/installer/src/Prompts/PromptManager.php (5)
.vortex/installer/src/Prompts/Handlers/Profile.php (3)
  • Profile (10-165)
  • resolvedValue (76-88)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
  • id (52-62)
  • resolvedValue (130-132)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • id (19-19)
  • resolvedValue (138-138)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Webroot.php (2)
  • resolvedValue (98-110)
  • resolvedMessage (115-121)
.vortex/installer/src/Utils/Tui.php (1)
  • info (42-44)
.vortex/installer/src/Prompts/Handlers/Starter.php (2)
.vortex/installer/src/Utils/JsonManipulator.php (2)
  • JsonManipulator (10-76)
  • fromFile (19-36)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • AbstractHandler (10-233)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-workflow (3)
🔇 Additional comments (10)
tests/behat/features/search.feature (3)

11-14: Dataset covers published vs draft — good.

Includes moderation_state and status to validate the new published-only behavior.


25-27: Assertions align with published-only filter — good.

Confirms both published show, draft hidden.


31-33: Targeted query validation — good.

Checks single expected hit and keeps draft excluded.

.gitignore (1)

44-46: LGTM: ignore root-level recipes/

Aligns with composer installer-path to recipes/{$name}.

.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)

137-139: LGTM: base signature aligned with interface.

Neutral default returning NULL remains appropriate.

.vortex/installer/src/Prompts/Handlers/Webroot.php (1)

115-121: Signature update and message formatting look correct

Matches the new interface (accepts mixed $resolved) and mirrors Profile’s messaging. No issues.

.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (3)

7-7: Correctly importing Profile for expectations

Needed for the expected resolved value assertion. LGTM.


24-27: Clarified test case label

Renamed label improves readability; behavior unchanged. LGTM.


28-35: Added DrupalCMS path coverage

Good addition: verifies Starter::DRUPALCMS_INSTALL_PROFILE and Profile’s resolved value '../recipes/drupal_cms_starter'.

.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1)

1-32: No stale CoversClass annotations referencing ProvisionType found
Ripgrep search across all PHP tests yielded zero matches for CoversClass(ProvisionType::class)—no further updates required.

Comment on lines +145 to +146
File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Anchor the .env replacement to the line for DRUPAL_PROFILE (defensive).

Reduces accidental matches if the token appears elsewhere.

Apply this diff (if replaceContentInFile supports PCRE flags):

-    File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);
+    File::replaceContentInFile($t . '/.env', '/^DRUPAL_PROFILE=.*/m', 'DRUPAL_PROFILE=' . $v);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);
File::replaceContentInFile(
$t . '/.env',
'/^DRUPAL_PROFILE=.*/m',
'DRUPAL_PROFILE=' . $v
);
🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/Profile.php around lines 145-146: the
current replacement uses an unanchored pattern that can match DRUPAL_PROFILE
anywhere in the file; change it to use a line-anchored PCRE (start-of-line)
pattern with multiline mode so only the DRUPAL_PROFILE line is replaced, and
pass the multiline flag if replaceContentInFile supports PCRE flags; replace the
entire line starting with DRUPAL_PROFILE= with the new value.

Comment on lines 91 to 115
if ($this->response == self::DRUPALCMS_INSTALL_PROFILE) {
$cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');

$cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
$cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
$cj->addLink('require', 'symfony/http-client', '^6.4', TRUE);

$cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
$cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);

$cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
$cj->addProperty('extra.merge-plugin.merge-false', TRUE);
$cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
$cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
$cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
$cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
$cj->addProperty('extra.merge-plugin.recurse', TRUE);
$cj->addProperty('extra.merge-plugin.replace', TRUE);
$cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);

$c = $cj->getContents();
file_put_contents($this->tmpDir . '/composer.json', $c);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify recipe install path mapping exists (type:drupal-recipe → web/recipes/{$name}).

If not set in base composer.json, Starter should ensure it; otherwise installs will land under vendor.

Run:


🏁 Script executed:

#!/bin/bash
# Expect an installer-paths mapping for type:drupal-recipe to web/recipes/{$name}
rg -nC2 -F '"installer-paths"' composer.json
rg -nC2 -F '"type:drupal-recipe"' composer.json

Length of output: 395


Change drupal-recipe installer-path to web/recipes/{$name}
composer.json currently maps type:drupal-recipe to "recipes/{$name}" instead of "web/recipes/{$name}".

🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/Starter.php around lines 91 to 113:
update the composer extra configuration to map the drupal-recipe installer path
to "web/recipes/{$name}" instead of the current "recipes/{$name}"; modify or add
the extra.installer-paths property via JsonManipulator->addProperty so that
'extra.installer-paths' includes 'web/recipes/{$name}' for the
'type:drupal-recipe' entry (replace the existing mapping if present), then write
the updated composer.json back as done currently.

⚠️ Potential issue

Null-check JsonManipulator::fromFile() to avoid fatal on missing composer.json.

Current code dereferences $cj without guarding a NULL return.

Apply this diff:

-    if ($this->response == self::DRUPALCMS_INSTALL_PROFILE) {
-      $cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
+    if ($this->response === self::DRUPALCMS_INSTALL_PROFILE) {
+      $composerPath = $this->tmpDir . '/composer.json';
+      $cj = JsonManipulator::fromFile($composerPath);
+      if ($cj === NULL) {
+        throw new \RuntimeException(sprintf('composer.json not found or unreadable at %s', $composerPath));
+      }
@@
-      $c = $cj->getContents();
-      file_put_contents($this->tmpDir . '/composer.json', $c);
+      $c = $cj->getContents();
+      if (file_put_contents($composerPath, $c) === FALSE) {
+        throw new \RuntimeException(sprintf('Failed to write composer.json to %s', $composerPath));
+      }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ($this->response == self::DRUPALCMS_INSTALL_PROFILE) {
$cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
$cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
$cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
$cj->addLink('require', 'symfony/http-client', '^6.4', TRUE);
$cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
$cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
$cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
$cj->addProperty('extra.merge-plugin.merge-false', TRUE);
$cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
$cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
$cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
$cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
$cj->addProperty('extra.merge-plugin.recurse', TRUE);
$cj->addProperty('extra.merge-plugin.replace', TRUE);
$cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
$c = $cj->getContents();
file_put_contents($this->tmpDir . '/composer.json', $c);
}
if ($this->response === self::DRUPALCMS_INSTALL_PROFILE) {
$composerPath = $this->tmpDir . '/composer.json';
$cj = JsonManipulator::fromFile($composerPath);
if ($cj === NULL) {
throw new \RuntimeException(sprintf(
'composer.json not found or unreadable at %s',
$composerPath
));
}
$cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
$cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
$cj->addLink('require', 'symfony/http-client', '^6.4', TRUE);
$cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
$cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
$cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
$cj->addProperty('extra.merge-plugin.merge-false', TRUE);
$cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
$cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
$cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
$cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
$cj->addProperty('extra.merge-plugin.recurse', TRUE);
$cj->addProperty('extra.merge-plugin.replace', TRUE);
$cj->addProperty(
'extra.merge-plugin.require',
['vendor/drupal/cms/composer.json']
);
$c = $cj->getContents();
if (file_put_contents($composerPath, $c) === FALSE) {
throw new \RuntimeException(sprintf(
'Failed to write composer.json to %s',
$composerPath
));
}
}
🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/Starter.php around lines 91 to 113:
JsonManipulator::fromFile($this->tmpDir . '/composer.json') can return NULL and
the code dereferences $cj immediately causing a fatal; guard the call by
checking if $cj === NULL and handle it (e.g., create a new JsonManipulator
instance or initialize a minimal composer.json file, or log an error and abort
the installer flow) before calling
addLink/addConfigSetting/addProperty/getContents; ensure subsequent
file_put_contents only runs when $cj is valid.

Comment on lines 26 to 33
'starter, DrupalCMS profile' => [
static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::DRUPALCMS_INSTALL_PROFILE)),
],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Optionally assert DrupalCMS composer changes

Consider an “after” closure to assert composer.json contains:

  • require.drupal/cms = ^1.0
  • require.wikimedia/composer-merge-plugin = ^2.1
  • require.symfony/http-client = ^6.4
  • extra.merge-plugin.require includes vendor/drupal/cms/composer.json

This will fail fast if Starter::process regresses.

🤖 Prompt for AI Agents
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php around lines
26 to 28: the test currently sets the starter env but does not assert that
Starter::process made the expected composer.json modifications; add an "after"
closure that reads and decodes the generated composer.json and asserts the
following keys/values exist: require["drupal/cms"] === "^1.0",
require["wikimedia/composer-merge-plugin"] === "^2.1",
require["symfony/http-client"] === "^6.4", and that
extra["merge-plugin"]["require"] includes the path to
vendor/drupal/cms/composer.json (use strict assertions so the test fails fast on
regression); implement using existing test helpers for file reads and PHPUnit
assertions (assertArrayHasKey/assertEquals/assertContains) and ensure the
closure runs after Starter::process completes.

Comment on lines +133 to +135
"recipes/{$name}": [
"type:drupal-recipe"
],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Mapping won’t apply unless drupal-recipe is added to installer-types.

oomphinc/composer-installers-extender requires declaring custom types. Without this, drupal-recipe packages will not install to recipes/{$name}.

   "installer-types": [
     "bower-asset",
     "npm-asset",
-    "drupal-library"
+    "drupal-library",
+    "drupal-recipe"
   ],

Also, confirm the intended target path is recipes/{$name} (root) and not web/recipes/{$name}, as tests/env vars appear to expect ../recipes/… from web/.


🏁 Script executed:

#!/bin/bash
set -e

# Locate mapping snippet in composer.json
grep -n '"recipes/{\$name}"' -C2 composer.json

# Locate installer-types block in composer.json
grep -n '"installer-types"' -C8 composer.json

# Search for recipes path references across the repo
grep -R -n 'recipes/' -C3 .

Length of output: 3975


Update composer.json installer-types

  • Add "drupal-recipe" to the "installer-types" array so that type:drupal-recipe packages install into recipes/{$name}.
  • No change needed for the target path—recipes/{$name} (root) matches the .vortex tests, which reference ../recipes/… from web/.
🤖 Prompt for AI Agents
In composer.json around lines 133 to 135, the new package type
"type:drupal-recipe" is mapped to "recipes/{$name}" but "drupal-recipe" is not
included in the "installer-types" array; update the "installer-types" array to
include "drupal-recipe" so packages with type "drupal-recipe" are installed to
recipes/{$name} (no change to the target path required).

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from 1251954 to 47d6ecf Compare September 1, 2025 11:50
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 11:52 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.vortex/installer/src/Prompts/Handlers/Webroot.php (2)

98-110: Avoid duplicating resolution logic between default() and resolvedValue().

default() and resolvedValue() both derive values from HostingProvider/discovery. Consider delegating default() to resolvedValue() (or extracting a shared helper) to keep behavior in one place.


126-146: Broaden replacements for JSON web-root and harden rename error handling.

Add a targeted replacement for composer "web-root": "web" and fail fast if the directory rename fails.

   File::replaceContentAsync([
     sprintf('%s/', $webroot) => $v . '/',
     sprintf('%s\/', $webroot) => $v . '\/',
     sprintf(': %s', $webroot) => ': ' . $v,
     sprintf('=%s', $webroot) => '=' . $v,
     sprintf('!%s', $webroot) => '!' . $v,
     sprintf('/\/%s\//', $webroot) => '/' . $v . '/',
     sprintf('/\'\/%s\'/', $webroot) => "'/" . $v . "'",
+    // composer.json (drupal-scaffold) JSON path
+    sprintf('/"web-root"\s*:\s*"%s"/', $webroot) => '"web-root": "' . $v . '"',
   ]);
 
-  rename($t . DIRECTORY_SEPARATOR . $webroot, $t . DIRECTORY_SEPARATOR . $v);
+  if (!@rename($t . DIRECTORY_SEPARATOR . $webroot, $t . DIRECTORY_SEPARATOR . $v)) {
+    throw new \RuntimeException(sprintf(
+      'Failed to rename webroot from "%s" to "%s" in %s',
+      $webroot,
+      $v,
+      $t
+    ));
+  }
.vortex/installer/src/Prompts/Handlers/Starter.php (1)

126-137: Use strict comparisons in postInstall() branches.

Minor correctness/readability improvement.

-    if ($this->response == self::DRUPAL_LOAD_DATABASE_DEMO) {
+    if ($this->response === self::DRUPAL_LOAD_DATABASE_DEMO) {
@@
-    elseif ($this->response == self::DRUPAL_INSTALL_PROFILE || $this->response == self::DRUPALCMS_INSTALL_PROFILE) {
+    elseif ($this->response === self::DRUPAL_INSTALL_PROFILE || $this->response === self::DRUPALCMS_INSTALL_PROFILE) {
♻️ Duplicate comments (18)
.gitignore.artifact (1)

36-38: Also ignore web/recipes for parity and portability.

Guard both potential installer paths to keep artifacts consistent across environments.

 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+web/recipes/*

Verification script to confirm the Composer installer path for drupal-recipe:

#!/bin/bash
set -euo pipefail
# Show installer-paths keys that contain "recipes/{$name}" or "web/recipes/{$name}"
if command -v jq >/dev/null 2>&1; then
  jq -r '.extra["installer-paths"]|keys[]' composer.json | rg -n '(^|/)recipes/\{\$name\}|(^|/)web/recipes/\{\$name\}'
else
  rg -nP '"installer-paths"\s*:\s*\{' -n composer.json -A50 | rg -n 'recipes/\{\$name\}|web/recipes/\{\$name\}'
fi
tests/behat/features/search.feature (2)

19-21: Avoid fixed sleeps; wait deterministically for indexing.

Replace static delay with a polling/wait step to reduce flakiness.

-    And I wait for 5 seconds
-    And I visit "/search"
+    And I wait for search indexing to complete
+    And I visit "/search"

15-18: Batch index enqueue to cut noise and speed up tests.

Prefer a single step if supported by your step library.

-    And I add the "page" content with the title "[TEST] Test page uniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page otheruniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page thirduniquestring" to the search index
-    And I run search indexing for 3 items
+    And I queue all created "page" content for search indexing
+    And I run search indexing for 3 items
web/modules/custom/ys_search/config/install/views.view.search.yml (1)

210-211: Switched to teaser view mode — verify per-bundle teaser displays exist.

Confirm all node bundles shown in search have a configured teaser display with expected fields; otherwise results may render inconsistently.

#!/bin/bash
# List teaser displays present in repo config.
fd -a 'core.entity_view_display.node.*.teaser*.yml' | sort
web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-69: Added boolean status field to index — align with Views filter and reindex.

The mapping looks right (datasource entity:node, property_path status, type boolean). Plan a config import + full reindex so the new filter works on existing installs.

Suggested deploy steps:

  • drush cim -y
  • drush sapi-c
  • drush sapi-i -y content
composer.json (1)

152-156: Add "drupal-recipe" to installer-types (required for custom path to take effect).

Without declaring the type, oomphinc/composer-installers-extender won’t route drupal-recipe packages to recipes/{$name}.

Apply:

         "installer-types": [
           "bower-asset",
           "npm-asset",
-          "drupal-library"
+          "drupal-library",
+          "drupal-recipe"
         ],

Verify:

#!/bin/bash
rg -n '"installer-types"\s*:\s*\[' -n composer.json -A5
.vortex/tests/bats/e2e/workflow.install.profile.bats (1)

43-49: Assert all composer mutations for DrupalCMS path (deps, config, installer-paths).

Add missing assertions to catch regressions in Starter::process and composer config.

   assert_file_contains .env "DRUPAL_PROFILE=../recipes/drupal_cms_starter"
   assert_file_not_contains .env "DEMO"
   assert_file_contains composer.json "drupal/cms"
   assert_file_contains composer.json "wikimedia/composer-merge-plugin"
   assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+  assert_file_contains composer.json "symfony/http-client"
+  # Composer config wiring
+  assert_file_contains composer.json "\"allow-plugins\""
+  assert_file_contains composer.json "\"wikimedia/composer-merge-plugin\""
+  assert_file_contains composer.json "\"merge-plugin\""
+  # Recipes support under web/
+  assert_file_contains composer.json "\"type:drupal-recipe\""
+  assert_file_contains composer.json "\"web/recipes/"
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (2)

13-14: Fix coverage annotation to reference Starter.

-#[CoversClass(ProvisionType::class)]
+#[CoversClass(Starter::class)]

16-29: Add “after” assertions to validate composer.json changes for DrupalCMS flow.

Verify drupal/cms, merge-plugin, http-client, merge-plugin.require, and recipes installer-path mapping.

   public static function dataProviderInstall(): array {
     return [
       'starter, demo db' => [
         static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::DRUPAL_LOAD_DATABASE_DEMO)),
       ],
 
       'starter, Drupal profile' => [
         static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::DRUPAL_INSTALL_PROFILE)),
       ],
 
       'starter, DrupalCMS profile' => [
-        static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::DRUPALCMS_INSTALL_PROFILE)),
+        static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::DRUPALCMS_INSTALL_PROFILE)),
+        static::cw(function (self $test): void {
+          $path = static::$sut . '/composer.json';
+          $json = json_decode(file_get_contents($path), TRUE, flags: JSON_THROW_ON_ERROR);
+          $test->assertSame('^1.0', $json['require']['drupal/cms'] ?? null);
+          $test->assertSame('^2.1', $json['require']['wikimedia/composer-merge-plugin'] ?? null);
+          $test->assertNotEmpty($json['require']['symfony/http-client'] ?? null);
+          $req = $json['extra']['merge-plugin']['require'] ?? [];
+          $test->assertContains('vendor/drupal/cms/composer.json', $req);
+          $paths = $json['extra']['installer-paths'] ?? [];
+          $test->assertArrayHasKey('web/recipes/{$name}', $paths);
+          $test->assertContains('type:drupal-recipe', $paths['web/recipes/{$name}'] ?? []);
+        }),
       ],
     ];
   }
.vortex/installer/src/Prompts/Handlers/Starter.php (3)

32-37: Align copy with product terms: “Drupal CMS” and “installed from recipe”.

Update labels to “Drupal CMS” and call out “recipe” for the Drupal CMS path to match docs and user-facing language.

-    $label2 = Tui::bold('DrupalCMS, installed from profile');
+    $label2 = Tui::bold('Drupal CMS, installed from recipe');
@@
-      Creates a new site by {$label21}
-      from the DrupalCMS recipe.
+      Creates a new site by {$label21}
+      from the Drupal CMS recipe.
@@
-      self::DRUPALCMS_INSTALL_PROFILE => 'DrupalCMS, installed from profile',
+      self::DRUPALCMS_INSTALL_PROFILE => 'Drupal CMS, installed from recipe',

Also applies to: 45-51, 65-71


94-96: Optionally widen Symfony HTTP client constraint to allow 7.x when compatible.

If the Drupal CMS recipe supports Symfony 7, prefer '^6.4 || ^7.0' to ease future resolves. Confirm upstream compatibility before changing.

-      $cj->addLink('require', 'symfony/http-client', '^6.4', TRUE);
+      $cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);

90-113: Guard composer.json access, use strict comparisons, and ensure recipes install under web/recipes.

Prevent null deref on JsonManipulator::fromFile(), fail on write error, and set installer-paths for type:drupal-recipe → web/recipes/{$name}.

-  public function process(): void {
-    if ($this->response == self::DRUPALCMS_INSTALL_PROFILE) {
-      $cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
+  public function process(): void {
+    if ($this->response === self::DRUPALCMS_INSTALL_PROFILE) {
+      $composerPath = $this->tmpDir . '/composer.json';
+      $cj = JsonManipulator::fromFile($composerPath);
+      if ($cj === NULL) {
+        throw new \RuntimeException(sprintf('composer.json not found or unreadable at %s', $composerPath));
+      }
 
       $cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
       $cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
-      $cj->addLink('require', 'symfony/http-client', '^6.4', TRUE);
+      $cj->addLink('require', 'symfony/http-client', '^6.4', TRUE);
 
       $cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
       $cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
 
       $cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-false', TRUE);
       $cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
       $cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
       $cj->addProperty('extra.merge-plugin.recurse', TRUE);
       $cj->addProperty('extra.merge-plugin.replace', TRUE);
       $cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
+
+      // Ensure recipes install under web/recipes/{$name}.
+      $paths = $cj->getProperty('extra.installer-paths') ?? [];
+      if (!is_array($paths) || ($paths['web/recipes/{$name}'] ?? null) !== ['type:drupal-recipe']) {
+        $cj->addProperty('extra.installer-paths.web/recipes/{$name}', ['type:drupal-recipe']);
+      }
 
-      $c = $cj->getContents();
-      file_put_contents($this->tmpDir . '/composer.json', $c);
+      $c = $cj->getContents();
+      if (file_put_contents($composerPath, $c) === FALSE) {
+        throw new \RuntimeException(sprintf('Failed to write composer.json to %s', $composerPath));
+      }
     }

Note: if an existing mapping points to recipes/{$name}, consider removing it to avoid ambiguous paths.

.vortex/installer/src/Prompts/PromptManager.php (2)

128-141: Guard info() to avoid passing null from resolvedMessage().

resolvedMessage() returns ?string; info() requires string.

Apply:

-            info($handler->resolvedMessage($r, $resolved));
+            if ($m = $handler->resolvedMessage($r, $resolved)) {
+              info($m);
+            }

166-166: Apply the same null-guard for Webroot messaging.

Mirror the Profile guard to prevent TypeError.

-              info($handler->resolvedMessage($r, $resolved));
+              if ($m = $handler->resolvedMessage($r, $resolved)) {
+                info($m);
+              }
.vortex/installer/src/Prompts/Handlers/Profile.php (4)

76-88: Avoid hardcoding the DrupalCMS recipe path; use a class constant.

Improves maintainability and reuse.

-    if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
-      return '../recipes/drupal_cms_starter';
+    if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
+      return self::DRUPALCMS_STARTER_RECIPE;
     }

Add (outside this hunk):

public const DRUPALCMS_STARTER_RECIPE = '../recipes/drupal_cms_starter';

138-140: Avoid magic key; use ProfileCustom::id() (or remove branch if unreachable).

Prevents string drift and aligns with PromptManager merging.

-    if ($v === self::CUSTOM && isset($this->responses['profile_custom'])) {
-      $v = $this->responses['profile_custom'];
+    if ($v === self::CUSTOM && isset($this->responses[ProfileCustom::id()])) {
+      $v = $this->responses[ProfileCustom::id()];
     }

145-146: Anchor the .env replacement to the DRUPAL_PROFILE line (defensive).

Reduces accidental matches elsewhere.

-    File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);
+    File::replaceContentInFile($t . '/.env', '/^DRUPAL_PROFILE=.*/m', 'DRUPAL_PROFILE=' . $v);

148-149: Fix OS-agnostic path detection for contrib/custom profiles.

Detect both '/' and '' to work across platforms.

-    $is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
+    // Detect Unix and Windows separators.
+    $is_contrib_profile = (strpbrk($v, '/\\') !== FALSE);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1251954 and 47d6ecf.

⛔ Files ignored due to path filters (24)
  • .vortex/installer/tests/Fixtures/install/_baseline/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/tests/behat/features/search.feature is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/deploy_type_all_circleci/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/deploy_type_all_gha/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/deploy_type_artifact/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore.artifact is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_demo_db/.ignorecontent is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_none/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (16)
  • .gitignore (2 hunks)
  • .gitignore.artifact (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Profile.php (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/Starter.php (6 hunks)
  • .vortex/installer/src/Prompts/Handlers/Webroot.php (1 hunks)
  • .vortex/installer/src/Prompts/PromptManager.php (2 hunks)
  • .vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (2 hunks)
  • .vortex/tests/bats/_helper.bash (1 hunks)
  • .vortex/tests/bats/e2e/workflow.install.profile.bats (1 hunks)
  • composer.json (1 hunks)
  • tests/behat/features/search.feature (1 hunks)
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml (1 hunks)
  • web/modules/custom/ys_search/config/install/views.view.search.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
composer.json

📄 CodeRabbit inference engine (CLAUDE.md)

When adding JavaScript or CSS libraries as Drupal libraries with Composer, define them as inline Composer packages in the 'repositories' section of composer.json

Files:

  • composer.json
web/modules/custom/**

📄 CodeRabbit inference engine (CLAUDE.md)

Custom modules should be placed in 'web/modules/custom/'

Files:

  • web/modules/custom/ys_search/config/install/views.view.search.yml
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml
tests/behat/**/*.feature

📄 CodeRabbit inference engine (CLAUDE.md)

tests/behat/**/*.feature: All Behat feature files must use the standard user story format: 'As a [user type]\nI want to [action]\nSo that [benefit]'
Use consistent user types in Behat feature files: 'site visitor', 'site administrator', and 'content editor'
Do not use commas or periods in user story statements within Behat feature files

Files:

  • tests/behat/features/search.feature
🧠 Learnings (1)
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.

Applied to files:

  • .vortex/tests/bats/_helper.bash
🧬 Code graph analysis (9)
.vortex/installer/src/Prompts/Handlers/Profile.php (5)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (4)
  • resolvedValue (130-132)
  • id (52-62)
  • resolvedMessage (137-139)
  • getResponseAsString (191-197)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • resolvedValue (138-138)
  • id (19-19)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Webroot.php (4)
  • resolvedValue (98-110)
  • discover (65-79)
  • resolvedMessage (115-121)
  • process (126-146)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Utils/File.php (2)
  • File (10-102)
  • replaceContentAsync (59-83)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/tests/bats/e2e/workflow.install.profile.bats (2)
.vortex/tests/bats/_helper.workflow.bash (6)
  • prepare_sut (7-39)
  • assert_ahoy_build (73-130)
  • assert_gitignore (132-162)
  • assert_ahoy_lint (413-425)
  • assert_ahoy_test (527-544)
  • assert_ahoy_fe (753-777)
.vortex/tests/bats/_helper.bash (1)
  • assert_webpage_contains (1022-1028)
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (6)
.vortex/installer/src/Prompts/Handlers/ProvisionType.php (1)
  • ProvisionType (11-103)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/PromptManager.php (2)
  • PromptManager (58-581)
  • makeEnvName (431-433)
.vortex/installer/src/Utils/Env.php (2)
  • Env (7-245)
  • put (24-26)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)
  • CoversClass (12-47)
.vortex/installer/tests/Functional/Handlers/AbstractInstallTestCase.php (1)
  • AbstractInstallTestCase (19-101)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (3)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • Profile (10-165)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/Starter.php (2)
.vortex/installer/src/Utils/JsonManipulator.php (2)
  • JsonManipulator (10-76)
  • fromFile (19-36)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • AbstractHandler (10-233)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/PromptManager.php (7)
.vortex/installer/src/Prompts/Handlers/Profile.php (3)
  • Profile (10-165)
  • resolvedValue (76-88)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
  • id (52-62)
  • resolvedValue (130-132)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • id (19-19)
  • resolvedValue (138-138)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Webroot.php (2)
  • resolvedValue (98-110)
  • resolvedMessage (115-121)
.vortex/installer/src/Utils/Tui.php (1)
  • info (42-44)
scripts/custom/provision-10-example.sh (1)
  • info (22-22)
scripts/vortex/update-vortex.sh (1)
  • info (46-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: build (0)
  • GitHub Check: build (1)
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.3)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-docs
🔇 Additional comments (12)
.vortex/tests/bats/_helper.bash (1)

535-535: Good fix: avoid overbroad “#;” assertion; keep explicit marker checks.

The generic match was prone to false positives; retaining “#;<” and “#;>” checks preserves intent.

.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

141-156: Fix doc typos in HandlerInterface

Update wording in the docblock to reference PromptManager and replace “handlering” with “prompting”:

-   * This is used by handlerManager to show an appropriate message (via
-   * info(), ok(), etc.) when using a resolved value instead of handlering
+   * This is used by PromptManager to show an appropriate message (via
+   * info(), ok(), etc.) when using a resolved value instead of prompting
.gitignore (2)

34-34: Wording tweak looks fine.


44-46: Drop the web/recipes/* rule — it’s unused
Composer’s installer-paths place recipes in recipes/{$name}, not under web/recipes/, so ignoring web/recipes/* is unnecessary.

Likely an incorrect or invalid review comment.

web/modules/custom/ys_search/config/install/views.view.search.yml (1)

166-175: Published-only filter wiring looks correct.

Boolean filter targets the index’s status field with operator "=" and value "1". Good.

composer.json (1)

133-135: Installer path for recipes is correctly defined.

recipes/{$name} matches expected usage from web/ as ../recipes/… in tests.

.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)

137-139: Signature updated to include $resolved — consistent with HandlerInterface.

Matches interface; no further action.

.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (3)

7-7: Importing Profile for new expectation is correct.


24-27: Renamed case label — fine.


28-35: Added DrupalCMS profile test case — aligns with resolver behavior.

Expects Starter::DRUPALCMS_INSTALL_PROFILE and Profile path ../recipes/drupal_cms_starter; matches Profile::resolvedValue.

.vortex/installer/src/Prompts/Handlers/Webroot.php (1)

115-117: LGTM: resolvedMessage() now relies on resolved value.

Signature aligns with HandlerInterface; message formatting is consistent with Profile.

.vortex/installer/src/Prompts/Handlers/Profile.php (1)

90-99: LGTM: resolvedMessage() contract and behavior are correct.

Comment on lines +176 to +202
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
operator_limit_selection: false
operator_list: { }
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Drop unused expose/group config for non-exposed boolean filter.

Since exposed: false, the expose/remember_roles and group_info blocks are redundant noise.

Apply:

           group: 1
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
+          exposed: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
operator_limit_selection: false
operator_list: { }
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
group: 1
exposed: false
🤖 Prompt for AI Agents
In web/modules/custom/ys_search/config/install/views.view.search.yml around
lines 176 to 202, the view element is set to exposed: false but still contains
the redundant expose (including remember_roles) and group_info blocks; remove
the entire expose subtree (operator_id through remember_roles) and the
group_info block for this non-exposed boolean filter so the YAML only contains
the relevant properties (exposed: false and any required basic flags), ensuring
indentation and YAML structure remain valid.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from 47d6ecf to 97021c9 Compare September 1, 2025 12:28
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 12:30 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)

126-133: Use strict comparison in postInstall.

Consistency and type safety.

Apply:

-    if ($this->response == self::DRUPAL_LOAD_DATABASE_DEMO) {
+    if ($this->response === self::DRUPAL_LOAD_DATABASE_DEMO) {
@@
-    elseif ($this->response == self::DRUPAL_INSTALL_PROFILE || $this->response == self::DRUPALCMS_INSTALL_PROFILE) {
+    elseif ($this->response === self::DRUPAL_INSTALL_PROFILE || $this->response === self::DRUPALCMS_INSTALL_PROFILE) {
♻️ Duplicate comments (16)
composer.json (1)

133-135: Add drupal-recipe to installer-types or mapping won’t apply.

oomphinc/composer-installers-extender only honors custom type mappings declared in extra.installer-types. Currently, drupal-recipe is missing, so packages of type:drupal-recipe won’t be installed into recipes/{$name}.

Add the type to installer-types:

"extra": {
  "installer-types": [
    "bower-asset",
    "npm-asset",
    "drupal-library",
    "drupal-recipe"
  ]
}

Verify in-repo:

#!/bin/bash
rg -n '"installer-types"' -n -C4 composer.json
jq -r '.extra["installer-types"][]?' composer.json 2>/dev/null | grep -q '^drupal-recipe$' && echo "OK: drupal-recipe present" || echo "MISSING: add drupal-recipe to extra.installer-types"
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

149-156: BC break: resolvedMessage now requires $resolved—ensure all implementers and call sites are updated.

Signature change to resolvedMessage(array $responses, mixed $resolved) is correct; verify all handlers implement it and all callers pass $resolved. Also fix doc typos in this block (“handlerManager” → “PromptManager”; “handlering” → “prompting”).

Run:

#!/bin/bash
# Implementations missing $resolved
rg -nP --type=php '(?s)class\s+\w+\s+implements\s+HandlerInterface.*?\bfunction\s+resolvedMessage\s*\(\s*array\s+\$responses\s*\)' -C2

# Calls not passing $resolved
rg -nP --type=php '\bresolvedMessage\s*\(\s*\$[a-zA-Z_][\w]*\s*\)' .vortex/installer | grep -v ','

Doc tweak example:

/**
 * Get a message to display when showing the resolved value.
 *
 * This is used by PromptManager to show an appropriate message (via
 * info(), ok(), etc.) when using a resolved value instead of prompting
 * for input.
 */
web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-69: Published status field: good; plan update + reindex for existing sites.

The boolean status mapping is correct and aligns with the view filter. Ensure you ship a post-update (or documented deploy step) to import this index config and trigger a full reindex to avoid stale results on already-installed sites.

web/modules/custom/ys_search/config/install/views.view.search.yml (2)

210-210: Switch to teaser view mode — verify display parity per bundle.

Confirm every indexed content type has a properly configured teaser display to avoid unintended field changes.


176-202: Remove redundant expose/group blocks for a non-exposed filter.

Since exposed: false, drop the expose and group_info subtrees to reduce config noise.

Apply:

           group: 1
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
+          exposed: false
+          is_grouped: false
tests/behat/features/search.feature (2)

19-20: Avoid fixed sleeps; wait deterministically for index readiness.

Replace the 5s sleep with a polling/wait step to reduce flakes.

-    And I wait for 5 seconds
-    And I visit "/search"
+    And I wait for search indexing to complete
+    And I visit "/search"

15-18: Batch index enqueue to cut noise/flakiness.

Prefer a single step to queue all created items and avoid hardcoding the count in the next step.

Example:

-    And I add the "page" content with the title "[TEST] Test page uniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page otheruniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page thirduniquestring" to the search index
-    And I run search indexing for 3 items
+    And I queue all created "page" content for search indexing
+    And I run search indexing for all queued items
.vortex/tests/bats/e2e/workflow.install.profile.bats (2)

32-61: Add missing assertion for symfony/http-client in composer.json.

This verifies Starter::process mutations end-to-end.

Apply:

   assert_file_contains composer.json "wikimedia/composer-merge-plugin"
   assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+  assert_file_contains composer.json "symfony/http-client"

32-61: Also assert key composer config and recipe installer-path mapping.

Guard against regressions for merge-plugin wiring and recipe placement under web/recipes.

Apply:

   assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+  assert_file_contains composer.json "\"allow-plugins\""
+  assert_file_contains composer.json "\"wikimedia/composer-merge-plugin\""
+  assert_file_contains composer.json "\"merge-plugin\""
+  assert_file_contains composer.json "\"type:drupal-recipe\""
+  assert_file_contains composer.json "\"installer-paths\""
+  assert_file_contains composer.json "\"web/recipes/{$name}\""
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1)

14-15: Fix incorrect coverage annotation.

This test targets Starter, not ProvisionType.

Apply:

-#[CoversClass(ProvisionType::class)]
+#[CoversClass(Starter::class)]
.vortex/installer/src/Prompts/Handlers/Starter.php (2)

32-36: Align copy: “Drupal CMS” and “recipe” wording.

Matches product naming and UX text.

Apply:

-    $label2 = Tui::bold('DrupalCMS, installed from profile');
+    $label2 = Tui::bold('Drupal CMS, installed from recipe');
@@
-      self::DRUPALCMS_INSTALL_PROFILE => 'DrupalCMS, installed from profile',
+      self::DRUPALCMS_INSTALL_PROFILE => 'Drupal CMS, installed from recipe',

Also applies to: 65-69


91-113: Harden composer.json mutation: strict compare, null-check, write-check.

Prevents fatals if composer.json is unreadable/missing and avoids silent write failures.

Apply:

-    if ($this->response == self::DRUPALCMS_INSTALL_PROFILE) {
-      $cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
+    if ($this->response === self::DRUPALCMS_INSTALL_PROFILE) {
+      $composerPath = $this->tmpDir . '/composer.json';
+      $cj = JsonManipulator::fromFile($composerPath);
+      if ($cj === NULL) {
+        throw new \RuntimeException(sprintf('composer.json not found or unreadable at %s', $composerPath));
+      }
@@
-      $c = $cj->getContents();
-      file_put_contents($this->tmpDir . '/composer.json', $c);
+      $c = $cj->getContents();
+      if (file_put_contents($composerPath, $c) === FALSE) {
+        throw new \RuntimeException(sprintf('Failed to write composer.json to %s', $composerPath));
+      }
.vortex/installer/src/Prompts/PromptManager.php (2)

161-174: Apply the same helper to Webroot (and avoid unconditional info()).

Use the same resolveOrPrompt helper here for consistency and safety.

-          // @todo Move into centralised resolver within this class.
-          function (array $r, $pr, $n): string {
-            $handler = $this->handlers[Webroot::id()];
-            $resolved = $handler->resolvedValue($r);
-            if (is_string($resolved)) {
-              info($handler->resolvedMessage($r, $resolved));
-              return $resolved;
-            }
-            else {
-              return text(...$this->args(Webroot::class, NULL, $r));
-            }
-          },
+          function (array $r, $pr, $n): string {
+            return $this->resolveOrPrompt(Webroot::id(), $r, fn() => text(...$this->args(Webroot::class, NULL, $r)));
+          },

128-141: DRY the resolve/log-or-prompt flow into a helper (and guard logging).

This block duplicates logic used later and always calls info(...). Centralize it and only log when a non-empty message is returned.

-        // @todo Move into centralised resolver within this class.
-        function (array $r, $pr, $n): string {
-          $handler = $this->handlers[Profile::id()];
-          $resolved = $handler->resolvedValue($r);
-          if (is_string($resolved)) {
-            info($handler->resolvedMessage($r, $resolved));
-            return $resolved;
-          }
-          else {
-            return (string) select(...$this->args(Profile::class));
-          }
-        },
+        function (array $r, $pr, $n): string {
+          return $this->resolveOrPrompt(Profile::id(), $r, fn() => select(...$this->args(Profile::class)));
+        },

Add this helper in the class:

private function resolveOrPrompt(string $id, array $r, callable $prompt): string {
  $handler = $this->handlers[$id];
  $resolved = $handler->resolvedValue($r);
  if (is_string($resolved)) {
    if ($m = $handler->resolvedMessage($r, $resolved)) { info($m); }
    return $resolved;
  }
  return (string) $prompt();
}
.vortex/installer/src/Prompts/Handlers/Profile.php (2)

76-88: Avoid hardcoding DrupalCMS recipe path; use a class constant.

Improves maintainability and reuse (tests, other handlers).

   if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
-      return '../recipes/drupal_cms_starter';
+      return self::DRUPALCMS_STARTER_RECIPE;
   }

Add near other constants:

const DRUPALCMS_STARTER_RECIPE = '../recipes/drupal_cms_starter';

131-163: Fix Windows path detection for contrib profiles; anchor .env replacement.

  • str_contains($v, DIRECTORY_SEPARATOR) mis-detects paths on Windows when $v uses '/'; this can wrongly take the “custom rename” branch and attempt to rename to a path with separators. Detect both '/' and '' to reliably classify contrib/profile paths.
  • Anchor DRUPAL_PROFILE replacement to the line to avoid accidental matches.
-    File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);
+    File::replaceContentInFile($t . '/.env', '/^DRUPAL_PROFILE=.*/m', 'DRUPAL_PROFILE=' . $v);

-    // Assume that profiles provided as a path are contrib profiles.
-    $is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
+    // Treat values containing either Unix or Windows separators as contrib paths.
+    $is_contrib_profile = (strpbrk($v, '/\\') !== FALSE);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 47d6ecf and 97021c9.

⛔ Files ignored due to path filters (23)
  • .vortex/installer/tests/Fixtures/install/_baseline/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/tests/behat/features/search.feature is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_demo_db/.ignorecontent is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/theme_absent/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_none/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (16)
  • .gitignore (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Profile.php (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/Starter.php (6 hunks)
  • .vortex/installer/src/Prompts/Handlers/Webroot.php (1 hunks)
  • .vortex/installer/src/Prompts/PromptManager.php (2 hunks)
  • .vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (2 hunks)
  • .vortex/tests/bats/_helper.bash (1 hunks)
  • .vortex/tests/bats/e2e/workflow.install.profile.bats (1 hunks)
  • composer.json (1 hunks)
  • phpmd.xml (1 hunks)
  • tests/behat/features/search.feature (1 hunks)
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml (1 hunks)
  • web/modules/custom/ys_search/config/install/views.view.search.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
composer.json

📄 CodeRabbit inference engine (CLAUDE.md)

When adding JavaScript or CSS libraries as Drupal libraries with Composer, define them as inline Composer packages in the 'repositories' section of composer.json

Files:

  • composer.json
web/modules/custom/**

📄 CodeRabbit inference engine (CLAUDE.md)

Custom modules should be placed in 'web/modules/custom/'

Files:

  • web/modules/custom/ys_search/config/install/views.view.search.yml
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml
tests/behat/**/*.feature

📄 CodeRabbit inference engine (CLAUDE.md)

tests/behat/**/*.feature: All Behat feature files must use the standard user story format: 'As a [user type]\nI want to [action]\nSo that [benefit]'
Use consistent user types in Behat feature files: 'site visitor', 'site administrator', and 'content editor'
Do not use commas or periods in user story statements within Behat feature files

Files:

  • tests/behat/features/search.feature
🧠 Learnings (1)
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.

Applied to files:

  • .vortex/tests/bats/_helper.bash
🧬 Code graph analysis (9)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (7)
.vortex/installer/src/Prompts/Handlers/ProvisionType.php (1)
  • ProvisionType (11-103)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/PromptManager.php (2)
  • PromptManager (58-581)
  • makeEnvName (431-433)
.vortex/installer/src/Utils/Env.php (2)
  • Env (7-245)
  • put (24-26)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)
  • CoversClass (12-47)
.vortex/installer/tests/Functional/Handlers/AbstractInstallTestCase.php (1)
  • AbstractInstallTestCase (19-101)
.vortex/installer/tests/Functional/FunctionalTestCase.php (1)
  • assertSutNotContains (139-154)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (4)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • Profile (10-165)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/tests/bats/e2e/workflow.install.profile.bats (2)
.vortex/tests/bats/_helper.workflow.bash (6)
  • prepare_sut (7-39)
  • assert_ahoy_build (73-130)
  • assert_gitignore (132-162)
  • assert_ahoy_lint (413-425)
  • assert_ahoy_test (527-544)
  • assert_ahoy_fe (753-777)
.vortex/tests/bats/_helper.bash (1)
  • assert_webpage_contains (1022-1028)
.vortex/installer/src/Prompts/Handlers/Starter.php (3)
.vortex/installer/src/Utils/JsonManipulator.php (2)
  • JsonManipulator (10-76)
  • fromFile (19-36)
.vortex/installer/src/Utils/Tui.php (3)
  • Tui (15-233)
  • bold (74-76)
  • underscore (78-80)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • AbstractHandler (10-233)
.vortex/installer/src/Prompts/Handlers/Profile.php (5)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (4)
  • resolvedValue (130-132)
  • id (52-62)
  • resolvedMessage (137-139)
  • getResponseAsString (191-197)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • resolvedValue (138-138)
  • id (19-19)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/Handlers/ProfileCustom.php (1)
  • ProfileCustom (9-86)
.vortex/installer/src/Utils/File.php (2)
  • File (10-102)
  • replaceContentAsync (59-83)
.vortex/installer/src/Prompts/PromptManager.php (6)
.vortex/installer/src/Prompts/Handlers/Profile.php (3)
  • Profile (10-165)
  • resolvedValue (76-88)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
  • id (52-62)
  • resolvedValue (130-132)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Webroot.php (2)
  • resolvedValue (98-110)
  • resolvedMessage (115-121)
.vortex/installer/src/Utils/Tui.php (1)
  • info (42-44)
scripts/custom/provision-10-example.sh (1)
  • info (22-22)
scripts/vortex/update-vortex.sh (1)
  • info (46-46)
🪛 GitHub Actions: Vortex - Test installer
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php

[warning] 33-33: PHPCS warning: A comma should follow the last multiline array item. Found: ) (Drupal.Arrays.Array.CommaLastItem)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: build (1)
  • GitHub Check: build (0)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-docs
🔇 Additional comments (12)
phpmd.xml (1)

17-17: Recipes exclude is correct.

Excluding /recipes/ from PHPMD aligns with the new installer path and .gitignore. No further changes needed.

.vortex/tests/bats/_helper.bash (1)

535-535: Relaxing “#;” assertion avoids false positives; keep boundary checks.

Commenting out the broad “#;” check prevents failures due to VORTEX_DEV guard markers, while still asserting “#;<” and “#;>” are stripped. Good trade-off.

.gitignore (1)

34-34: Comment tweak: LGTM.

Wording change only; behavior unchanged.

web/modules/custom/ys_search/config/install/views.view.search.yml (1)

165-175: LGTM: Published-only filter.

Filter configuration matches the boolean status field in the index.

tests/behat/features/search.feature (2)

11-14: Fixtures updated for moderation awareness — good.

Status/moderation_state columns make expectations explicit.


25-27: Assertions match published-only behavior — good.

Checks correctly include published and exclude draft.

Also applies to: 31-33

.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)

137-139: Signature aligned with interface; propagate to implementors.

resolvedMessage(array $responses, mixed $resolved) matches HandlerInterface. Ensure all handlers use the new parameter and no callers rely on the old signature.

.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (2)

24-27: Drupal profile path: test rename only — fine.

Label change keeps expectations intact.


28-35: DrupalCMS flow covered; verify key navigation assumptions.

Two DOWNs from the default selection assumes wrap-around behavior in Laravel Prompts. If that changes, the test becomes brittle; consider selecting by value or adjusting default.

.vortex/installer/src/Prompts/Handlers/Webroot.php (1)

115-117: Resolved message API change looks correct and consistent.

Signature aligns with HandlerInterface; message only when $resolved is string is appropriate.

.vortex/installer/src/Prompts/Handlers/Starter.php (1)

98-109: Double-check merge-plugin keys.

Confirm ‘merge-false’ and ‘require’ keys match wikimedia/composer-merge-plugin 2.x schema.

Would you like me to verify against the latest plugin docs?

.vortex/installer/src/Prompts/Handlers/Profile.php (1)

90-99: LGTM on resolvedMessage().

Matches the new interface and returns a clear, user-facing line.

Comment on lines +44 to +46
# Ignore all recipes by default. Custom recipes should be added explicitly.
recipes/*

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Ignoring recipes by default is sensible; consider an opt-in keep file.

If you want the directory present but empty in new repos, add an exception and commit a placeholder.

 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+!recipes/.gitkeep
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Ignore all recipes by default. Custom recipes should be added explicitly.
recipes/*
# Ignore all recipes by default. Custom recipes should be added explicitly.
recipes/*
!recipes/.gitkeep
🤖 Prompt for AI Agents
In .gitignore around lines 44-46, the current rule ignores the entire recipes
directory which prevents committing an empty directory; update .gitignore to
keep the directory by adding an exception for the directory (e.g., allow the
directory itself or a specific keep file) and then add and commit a placeholder
file inside recipes (commonly named .gitkeep or README) so the empty directory
is preserved in the repo.

Comment on lines +94 to +112
$cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
$cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
$cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);

$cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
$cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);

$cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
$cj->addProperty('extra.merge-plugin.merge-false', TRUE);
$cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
$cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
$cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
$cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
$cj->addProperty('extra.merge-plugin.recurse', TRUE);
$cj->addProperty('extra.merge-plugin.replace', TRUE);
$cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure recipes install under web/recipes and allow composer/installers.

Meets PR objective (type:drupal-recipe → web/recipes/{$name}) and avoids Composer 2 plugin blocks.

Apply:

       $cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
       $cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
       $cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);
+      $cj->addConfigSetting('allow-plugins.composer/installers', TRUE);
@@
-      $cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
+      $cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
+      $cj->addProperty('extra.installer-paths.web/recipes/{$name}', ['type:drupal-recipe']);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
$cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
$cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);
$cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
$cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
$cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
$cj->addProperty('extra.merge-plugin.merge-false', TRUE);
$cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
$cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
$cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
$cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
$cj->addProperty('extra.merge-plugin.recurse', TRUE);
$cj->addProperty('extra.merge-plugin.replace', TRUE);
$cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
$cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
$cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
$cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);
$cj->addConfigSetting('allow-plugins.composer/installers', TRUE);
$cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
$cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
$cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
$cj->addProperty('extra.merge-plugin.merge-false', TRUE);
$cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
$cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
$cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
$cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
$cj->addProperty('extra.merge-plugin.recurse', TRUE);
$cj->addProperty('extra.merge-plugin.replace', TRUE);
$cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
$cj->addProperty('extra.installer-paths.web/recipes/{$name}', ['type:drupal-recipe']);
🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/Starter.php around lines 94-110: ensure
project composer config will install recipe packages under web/recipes and won't
be blocked by Composer 2 plugin policies; add an allow-plugins entry for
composer/installers (set to TRUE) alongside the existing allow-plugins entries,
and add an extra.installer-paths entry that maps the drupal-recipe package type
to "web/recipes/{$name}" (so recipes go into web/recipes), keeping the current
merge-plugin properties intact and avoiding any Composer 2 plugin API blocks.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from 97021c9 to ca9fb1e Compare September 1, 2025 20:55
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 20:57 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)

126-138: Prefer strict comparisons in postInstall.

Minor consistency/readability improvement.

-    if ($this->response == self::DRUPAL_LOAD_DATABASE_DEMO) {
+    if ($this->response === self::DRUPAL_LOAD_DATABASE_DEMO) {
@@
-    elseif ($this->response == self::DRUPAL_INSTALL_PROFILE || $this->response == self::DRUPALCMS_INSTALL_PROFILE) {
+    elseif ($this->response === self::DRUPAL_INSTALL_PROFILE || $this->response === self::DRUPALCMS_INSTALL_PROFILE) {

Also applies to: 131-131

♻️ Duplicate comments (18)
composer.json (1)

152-156: Add "drupal-recipe" to installer-types or mapping won’t apply.

Without declaring the custom type, oomphinc/composer-installers-extender won’t route drupal-recipe packages to recipes/{$name}.

         "installer-types": [
             "bower-asset",
             "npm-asset",
-            "drupal-library"
+            "drupal-library",
+            "drupal-recipe"
         ],
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

149-156: Doc typos and BC ripple for resolvedMessage signature.

Fix comment wording; ensure all implementers and call sites pass $resolved.

-   * This is used by handlerManager to show an appropriate message (via
-   * info(), ok(), etc.) when using a resolved value instead of handlering
+   * This is used by PromptManager to show an appropriate message (via
+   * info(), ok(), etc.) when using a resolved value instead of prompting

Verification (run in repo root):

rg -nP --type=php '\bfunction\s+resolvedMessage\s*\(\s*array\s+\$responses\s*\)' -C2
rg -nP --type=php '\bresolvedMessage\s*\(\s*[^,)]*\)$' -C2
.gitignore (1)

44-46: Keep the recipes directory via a placeholder.

This allows committing an empty recipes/ folder in new repos when desired.

 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+!recipes/.gitkeep

Follow-up: add and commit recipes/.gitkeep.

tests/behat/features/search.feature (2)

15-17: Batch the indexing step to reduce noise and flakiness.

Replace the three per-item enqueue steps with a single batch step (if available).

-    And I add the "page" content with the title "[TEST] Test page uniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page otheruniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page thirduniquestring" to the search index
+    And I queue all created "page" content for search indexing

19-21: Avoid fixed sleeps; wait for index readiness.

Swap the hard sleep for a deterministic “wait for indexing complete” step.

-    And I wait for 5 seconds
+    And I wait for search indexing to complete
     And I visit "/search"
web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-68: Ensure deploy path reimports this config and reindexes.

On existing sites, add a post-update to import this index change and trigger full reindex.

#!/bin/bash
set -euo pipefail
echo "🔎 Looking for ys_search post-update hooks importing index config & reindexing..."
fd -a -t f 'ys_search*post_update*.php' web/modules/custom || true
rg -n 'post_update|hook_post_update' web/modules/custom/ys_search || true
rg -n 'sapi-?i|sapi-?c|Index::reindex|search_api' web/modules/custom/ys_search || true
web/modules/custom/ys_search/config/install/views.view.search.yml (2)

210-210: Switch to teaser view mode — verify parity across bundles.

Confirm all indexed bundles have a suitable teaser display or adjust.

#!/bin/bash
set -euo pipefail
echo "🔎 Listing teaser displays for node bundles..."
fd -a -t f 'core.entity_view_display.node.*.teaser.yml' web || true

165-202: Trim redundant expose/group config on non-exposed filter.

Since exposed: false, drop the expose and group_info blocks to reduce noise.

           group: 1
           exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
+          is_grouped: false
.vortex/tests/bats/e2e/workflow.install.profile.bats (2)

45-48: Also assert symfony/http-client is added by Starter::process()

This was requested previously; it’s still missing here.

Apply:

   assert_file_contains composer.json "wikimedia/composer-merge-plugin"
   assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+  assert_file_contains composer.json "symfony/http-client"

45-50: Guard against regressions in composer config (allow-plugins, wiring, recipes)

Add assertions to ensure merge-plugin wiring and recipes support are present.

Apply:

   assert_file_contains composer.json "wikimedia/composer-merge-plugin"
   assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+  assert_file_contains composer.json "\"allow-plugins\""
+  assert_file_contains composer.json "\"wikimedia/composer-merge-plugin\""
+  assert_file_contains composer.json "\"merge-plugin\""
+  assert_file_contains composer.json "\"type:drupal-recipe\""
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1)

14-15: Fix incorrect coverage target: should cover Starter

The test manipulates Starter via env; adjust CoversClass accordingly.

-#[CoversClass(ProvisionType::class)]
+#[CoversClass(Starter::class)]
.vortex/installer/src/Prompts/Handlers/Starter.php (2)

32-36: Align labels/copy to “Drupal CMS” and “recipe”.

Current labels use “DrupalCMS” and “installed from profile” while the copy says “recipe”. Align for consistency and UX polish.

-    $label2 = Tui::bold('DrupalCMS, installed from profile');
+    $label2 = Tui::bold('Drupal CMS, installed from recipe');
@@
-      from the DrupalCMS recipe.
+      from the Drupal CMS recipe.
@@
-      self::DRUPALCMS_INSTALL_PROFILE => 'DrupalCMS, installed from profile',
+      self::DRUPALCMS_INSTALL_PROFILE => 'Drupal CMS, installed from recipe',

Additionally (outside changed hunk), consider:

// Also change:
$label1 = Tui::bold('Drupal, installed from profile');
// to:
$label1 = Tui::bold('Drupal, installed from recipe');

// And:
$label3 = Tui::bold('Drupal, loaded from the demo database');
// to:
$label3 = Tui::bold('Drupal CMS, loaded from the demo database');

Also applies to: 45-48, 67-70


91-113: Guard composer.json access, add recipe installer-path + plugin allow, and use strict comparisons.

  • JsonManipulator::fromFile() can return NULL; dereferencing causes fatal.
  • Missing allow-plugins for composer/installers and installer-paths mapping for type:drupal-recipe → web/recipes/{$name} (required by PR objective).
  • file_put_contents() return value ignored.
  • Use strict comparisons.
-    if ($this->response == self::DRUPALCMS_INSTALL_PROFILE) {
-      $cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
+    if ($this->response === self::DRUPALCMS_INSTALL_PROFILE) {
+      $composerPath = $this->tmpDir . '/composer.json';
+      $cj = JsonManipulator::fromFile($composerPath);
+      if ($cj === NULL) {
+        throw new \RuntimeException(sprintf('composer.json not found or unreadable at %s', $composerPath));
+      }
@@
       $cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
       $cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
       $cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);
 
+      // Composer 2 plugin policy and installers for recipes.
+      $cj->addConfigSetting('allow-plugins.composer/installers', TRUE);
       $cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
       $cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
@@
       $cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
+      // Ensure recipes install under web/recipes/{name}.
+      $cj->addProperty('extra.installer-paths.web/recipes/{$name}', ['type:drupal-recipe']);
 
-      $c = $cj->getContents();
-      file_put_contents($this->tmpDir . '/composer.json', $c);
+      $c = $cj->getContents();
+      if (file_put_contents($composerPath, $c) === FALSE) {
+        throw new \RuntimeException(sprintf('Failed to write composer.json to %s', $composerPath));
+      }
     }

To verify, run:

#!/bin/bash
# Verify composer.json mutations for Drupal CMS flow
set -euo pipefail
rg -n '"drupal/cms"\s*:\s*"\^1\.0"' composer.json
rg -n '"wikimedia/composer-merge-plugin"\s*:\s*"\^2\.1"' composer.json
rg -n '"symfony/http-client"\s*:\s*"\^6\.4 \|\| \^7\.0"' composer.json
rg -n '"allow-plugins"\s*:\s*{' -n composer.json
rg -n '"composer/installers"\s*:\s*true' composer.json
rg -n '"wikimedia/composer-merge-plugin"\s*:\s*true' composer.json
rg -n '"drupal/core-project-message"\s*:\s*true' composer.json
rg -nP '"extra"\s*:\s*\{(?s).*"installer-paths"\s*:\s*\{(?s).*"web/recipes/\{\$name\}"\s*:\s*\[\s*"type:drupal-recipe"\s*\]' composer.json
rg -nP '"extra"\s*:\s*\{(?s).*"merge-plugin"\s*:\s*\{(?s).*"require"\s*:\s*\[\s*"vendor/drupal/cms/composer\.json"\s*\]' composer.json
.vortex/installer/src/Prompts/PromptManager.php (2)

161-174: Apply same guard for Webroot and consider DRY-ing via helper.

Mirror the guard here; consider extracting a resolveOrPrompt() helper to remove duplication.

-            if (is_string($resolved)) {
-              info($handler->resolvedMessage($r, $resolved));
+            if (is_string($resolved)) {
+              if ($m = $handler->resolvedMessage($r, $resolved)) {
+                info($m);
+              }
               return $resolved;
             }

Optional helper (add to class):

private function resolveOrPrompt(string $id, array $r, callable $prompt): string {
  $h = $this->handlers[$id];
  $rv = $h->resolvedValue($r);
  if (is_string($rv)) {
    if ($m = $h->resolvedMessage($r, $rv)) { info($m); }
    return $rv;
  }
  return (string) $prompt();
}

128-141: Guard info() call when resolvedMessage is null.

Avoid passing NULL to info(); keep behavior consistent with other guards.

-          if (is_string($resolved)) {
-            info($handler->resolvedMessage($r, $resolved));
+          if (is_string($resolved)) {
+            if ($m = $handler->resolvedMessage($r, $resolved)) {
+              info($m);
+            }
             return $resolved;
           }
.vortex/installer/src/Prompts/Handlers/Profile.php (3)

76-88: Avoid hardcoded recipe path; introduce a class constant.

Improves reuse and prevents string drift across handlers/tests.

-    if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
-      return '../recipes/drupal_cms_starter';
+    if (($responses[Starter::id()] ?? '') === Starter::DRUPALCMS_INSTALL_PROFILE) {
+      return self::DRUPALCMS_STARTER_RECIPE;
     }

Add near other constants (outside this hunk):

const DRUPALCMS_STARTER_RECIPE = '../recipes/drupal_cms_starter';

145-145: Anchor DRUPAL_PROFILE replacement to line start (defensive).

Prevents accidental replacements elsewhere in the file.

-    File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);
+    File::replaceContentInFile($t . '/.env', '/^DRUPAL_PROFILE=.*/m', 'DRUPAL_PROFILE=' . $v);

147-149: Cross-platform path detection for contrib/profile paths.

str_contains($v, DIRECTORY_SEPARATOR) fails when separators don’t match OS; detect both.

-    $is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
+    // Detect both Unix and Windows separators.
+    $is_contrib_profile = (strpbrk($v, '/\\') !== FALSE);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 97021c9 and ca9fb1e.

⛔ Files ignored due to path filters (23)
  • .vortex/installer/tests/Fixtures/install/_baseline/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/tests/behat/features/search.feature is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_demo_db/.ignorecontent is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/theme_absent/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_none/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (16)
  • .gitignore (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Profile.php (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/Starter.php (6 hunks)
  • .vortex/installer/src/Prompts/Handlers/Webroot.php (1 hunks)
  • .vortex/installer/src/Prompts/PromptManager.php (2 hunks)
  • .vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (2 hunks)
  • .vortex/tests/bats/_helper.bash (1 hunks)
  • .vortex/tests/bats/e2e/workflow.install.profile.bats (1 hunks)
  • composer.json (1 hunks)
  • phpmd.xml (1 hunks)
  • tests/behat/features/search.feature (1 hunks)
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml (1 hunks)
  • web/modules/custom/ys_search/config/install/views.view.search.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
composer.json

📄 CodeRabbit inference engine (CLAUDE.md)

When adding JavaScript or CSS libraries as Drupal libraries with Composer, define them as inline Composer packages in the 'repositories' section of composer.json

Files:

  • composer.json
tests/behat/**/*.feature

📄 CodeRabbit inference engine (CLAUDE.md)

tests/behat/**/*.feature: All Behat feature files must use the standard user story format: 'As a [user type]\nI want to [action]\nSo that [benefit]'
Use consistent user types in Behat feature files: 'site visitor', 'site administrator', and 'content editor'
Do not use commas or periods in user story statements within Behat feature files

Files:

  • tests/behat/features/search.feature
web/modules/custom/**

📄 CodeRabbit inference engine (CLAUDE.md)

Custom modules should be placed in 'web/modules/custom/'

Files:

  • web/modules/custom/ys_search/config/install/search_api.index.content.yml
  • web/modules/custom/ys_search/config/install/views.view.search.yml
🧠 Learnings (1)
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.

Applied to files:

  • .vortex/tests/bats/_helper.bash
🧬 Code graph analysis (9)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/tests/bats/e2e/workflow.install.profile.bats (2)
.vortex/tests/bats/_helper.workflow.bash (6)
  • prepare_sut (7-39)
  • assert_ahoy_build (73-130)
  • assert_gitignore (132-162)
  • assert_ahoy_lint (413-425)
  • assert_ahoy_test (527-544)
  • assert_ahoy_fe (753-777)
.vortex/tests/bats/_helper.bash (1)
  • assert_webpage_contains (1022-1028)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (4)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • Profile (10-165)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/installer/src/Prompts/Handlers/Profile.php (5)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
  • resolvedValue (130-132)
  • id (52-62)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • resolvedValue (138-138)
  • id (19-19)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/Handlers/ProfileCustom.php (1)
  • ProfileCustom (9-86)
.vortex/installer/src/Utils/File.php (2)
  • File (10-102)
  • replaceContentAsync (59-83)
.vortex/installer/src/Prompts/Handlers/Starter.php (2)
.vortex/installer/src/Utils/JsonManipulator.php (2)
  • JsonManipulator (10-76)
  • fromFile (19-36)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • AbstractHandler (10-233)
.vortex/installer/src/Prompts/PromptManager.php (5)
.vortex/installer/src/Prompts/Handlers/Profile.php (3)
  • Profile (10-165)
  • resolvedValue (76-88)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
  • id (52-62)
  • resolvedValue (130-132)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • id (19-19)
  • resolvedValue (138-138)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Webroot.php (2)
  • resolvedValue (98-110)
  • resolvedMessage (115-121)
.vortex/installer/src/Utils/Tui.php (1)
  • info (42-44)
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (6)
.vortex/installer/src/Prompts/Handlers/ProvisionType.php (1)
  • ProvisionType (11-103)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-148)
.vortex/installer/src/Prompts/PromptManager.php (2)
  • PromptManager (58-581)
  • makeEnvName (431-433)
.vortex/installer/src/Utils/Env.php (2)
  • Env (7-245)
  • put (24-26)
.vortex/installer/tests/Functional/Handlers/AbstractInstallTestCase.php (1)
  • AbstractInstallTestCase (19-101)
.vortex/installer/tests/Functional/FunctionalTestCase.php (1)
  • assertSutNotContains (139-154)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: build (1)
  • GitHub Check: build (0)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-installer (8.3)
  • GitHub Check: vortex-test-docs
🔇 Additional comments (13)
.vortex/tests/bats/_helper.bash (1)

535-538: Allow generic '#;' comments; keep guard markers checks — good change.

Permitting bare "#;" avoids false positives (e.g., in .gitignore), while still asserting "#;<" and "#;>" are stripped.

phpmd.xml (1)

17-18: Exclude Drupal recipes from PHPMD — sensible.

Matches the new recipes/ install path and prevents noise from non-PHP application content.

composer.json (1)

133-135: Installer path for recipes at repo root — LGTM.

Mapping type:drupal-recipe to recipes/{$name} aligns with tests and docs.

.gitignore (1)

34-34: Casing tweak for scaffold comment — looks good.

tests/behat/features/search.feature (3)

11-14: LGTM: dataset now tests published vs draft.

User story format and user type comply with guidelines; moderation_state/status columns are appropriate.


25-27: LGTM: assertions reflect published-only results.

Draft is excluded; both published items present.


31-33: LGTM: scoped search result is correct.

Unique term returns the expected single published item.

web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-68: LGTM: boolean status field mapping aligns with the view filter.

Type, property_path, and dependencies look correct.

.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)

137-139: All resolvedMessage overrides and call sites updated
No instances of the old one-arg signature or one-argument calls remain—signature change fully applied.

.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)

24-35: LGTM: adds DrupalCMS profile selection path and expectation.

Covers selection order and ensures Profile is auto-resolved to the recipe path.

.vortex/installer/src/Prompts/Handlers/Webroot.php (2)

115-120: Signature alignment and behavior — LGTM

Method now matches HandlerInterface and mirrors Profile’s behavior. Message emission is correctly gated on string resolution.


115-120: All resolvedMessage handlers updated to the new signature
Verified that HandlerInterface, AbstractHandler, Webroot, and Profile all declare resolvedMessage(array $responses, mixed $resolved): ?string.

.vortex/installer/src/Prompts/Handlers/Profile.php (1)

90-99: LGTM on resolved messaging.

Resolved message reads well and is safe; minimal and consistent.

Comment on lines 27 to 33
'starter, DrupalCMS profile' => [
static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::DRUPALCMS_INSTALL_PROFILE)),
static::cw(fn(FunctionalTestCase $test) => $test->assertSutNotContains([
'drupal/cms',
'wikimedia/composer-merge-plugin',
'vendor/drupal/cms/composer.json',
])),
],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Incorrect expectation for DrupalCMS path: packages should be present, not absent

For DRUPALCMS_INSTALL_PROFILE, Starter::process() adds drupal/cms, merge-plugin, and http-client to composer.json and wires merge-plugin. The test currently asserts they are absent, which is wrong.

Apply:

-      'starter, DrupalCMS profile' => [
-        static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::DRUPALCMS_INSTALL_PROFILE)),
-        static::cw(fn(FunctionalTestCase $test) => $test->assertSutNotContains([
-          'drupal/cms',
-          'wikimedia/composer-merge-plugin',
-          'vendor/drupal/cms/composer.json',
-        ])),
-      ],
+      'starter, DrupalCMS profile' => [
+        static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::DRUPALCMS_INSTALL_PROFILE)),
+        static::cw(function (FunctionalTestCase $test) {
+          $composer = json_decode(file_get_contents(static::$sut . '/composer.json'), true);
+          $require  = $composer['require'] ?? [];
+          $merge    = $composer['extra']['merge-plugin'] ?? [];
+
+          $test->assertArrayHasKey('drupal/cms', $require);
+          $test->assertArrayHasKey('wikimedia/composer-merge-plugin', $require);
+          $test->assertArrayHasKey('symfony/http-client', $require);
+          $test->assertContains('vendor/drupal/cms/composer.json', $merge['require'] ?? []);
+        }),
+      ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'starter, DrupalCMS profile' => [
static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::DRUPALCMS_INSTALL_PROFILE)),
static::cw(fn(FunctionalTestCase $test) => $test->assertSutNotContains([
'drupal/cms',
'wikimedia/composer-merge-plugin',
'vendor/drupal/cms/composer.json',
])),
],
'starter, DrupalCMS profile' => [
static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::DRUPALCMS_INSTALL_PROFILE)),
static::cw(function (FunctionalTestCase $test) {
$composer = json_decode(file_get_contents(static::$sut . '/composer.json'), true);
$require = $composer['require'] ?? [];
$merge = $composer['extra']['merge-plugin'] ?? [];
$test->assertArrayHasKey('drupal/cms', $require);
$test->assertArrayHasKey('wikimedia/composer-merge-plugin', $require);
$test->assertArrayHasKey('symfony/http-client', $require);
$test->assertContains('vendor/drupal/cms/composer.json', $merge['require'] ?? []);
}),
],
🤖 Prompt for AI Agents
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php around lines
27-34: the test currently asserts that drupal/cms,
wikimedia/composer-merge-plugin, and vendor/drupal/cms/composer.json are absent
for the DRUPALCMS_INSTALL_PROFILE, but Starter::process() adds drupal/cms, the
merge-plugin, and http-client and wires the vendor/drupal/cms/composer.json;
change the assertion from assertSutNotContains to assertSutContains and ensure
the list includes 'drupal/cms', 'wikimedia/composer-merge-plugin',
'guzzlehttp/guzzle' (or the configured http-client package) and
'vendor/drupal/cms/composer.json' so the test expects those entries to be
present.

Comment on lines 534 to 538
# Assert all special comments were removed.
assert_dir_not_contains_string "${dir}" "#;"
# assert_dir_not_contains_string "${dir}" "#;"
assert_dir_not_contains_string "${dir}" "#;<"
assert_dir_not_contains_string "${dir}" "#;>"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Clarify the nearby comment to reflect intent.

Update wording so it’s clear we only enforce removal of VORTEX_DEV guard markers, not all "#;"-style comments.

-  # Assert all special comments were removed.
+  # Assert VORTEX_DEV guard markers were removed.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Assert all special comments were removed.
assert_dir_not_contains_string "${dir}" "#;"
# assert_dir_not_contains_string "${dir}" "#;"
assert_dir_not_contains_string "${dir}" "#;<"
assert_dir_not_contains_string "${dir}" "#;>"
# Assert VORTEX_DEV guard markers were removed.
# assert_dir_not_contains_string "${dir}" "#;"
assert_dir_not_contains_string "${dir}" "#;<"
assert_dir_not_contains_string "${dir}" "#;>"
🤖 Prompt for AI Agents
.vortex/tests/bats/_helper.bash lines 534-538: The current comment "Assert all
special comments were removed." is ambiguous; change it to clearly state we are
only enforcing removal of VORTEX_DEV guard markers (the "#;<" and "#;>"
patterns), not every "#;"-style comment. Update the comment text to something
like "Assert VORTEX_DEV guard markers were removed (do not assert removal of all
'#;' comments)." so the intent matches the assertions below.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from ca9fb1e to 91ca2f7 Compare September 1, 2025 21:19
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 21:21 Inactive
@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from 91ca2f7 to f6913d4 Compare September 1, 2025 21:22
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 21:24 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.vortex/installer/src/Prompts/Handlers/Internal.php (1)

127-156: Guard response lookups to avoid undefined index notices.

Directly indexing $responses risks notices when a handler didn’t run or was pre-resolved. Use null-coalescing on Starter/ProvisionType/DatabaseDownloadSource.

-      if ($responses[Starter::id()] !== Starter::LOAD_DATABASE_DEMO) {
+      $starter = $responses[Starter::id()] ?? null;
+      if ($starter !== Starter::LOAD_DATABASE_DEMO) {
         $is_demo = FALSE;
       }
       // Check if it should be enabled based on the provision type and database
       // download source.
-      elseif ($responses[ProvisionType::id()] === ProvisionType::DATABASE) {
+      elseif (($responses[ProvisionType::id()] ?? null) === ProvisionType::DATABASE) {
         $db_file_exists = file_exists(Env::get('VORTEX_DB_DIR', './.data') . DIRECTORY_SEPARATOR . Env::get('VORTEX_DB_FILE', 'db.sql'));
         $has_comment = File::contains($this->dstDir . '/.env', 'Override project-specific values for demonstration purposes');
@@
-        if ($responses[DatabaseDownloadSource::id()] === DatabaseDownloadSource::URL) {
+        if (($responses[DatabaseDownloadSource::id()] ?? null) === DatabaseDownloadSource::URL) {
           // For a downloading from URL, demo mode is enabled if the database
           // file does not exist or if there is an explicit comment in the
           // destination .env file that indicates that this is a demo mode.
           $is_demo = !$db_file_exists || $has_comment;
         }
-        elseif ($responses[DatabaseDownloadSource::id()] === DatabaseDownloadSource::CONTAINER_REGISTRY) {
+        elseif (($responses[DatabaseDownloadSource::id()] ?? null) === DatabaseDownloadSource::CONTAINER_REGISTRY) {
           // For a downloading from container registry, demo mode is enabled if
           // there is an explicit comment in the destination .env file that
           // indicates that this is a demo mode.
           $is_demo = $has_comment;
         }
.vortex/tests/test.workflow.sh (1)

81-86: Swap Docker Compose and Drupal CMS tests in the default workflow
In .vortex/tests/test.workflow.sh (* ) block, lines 81–86), reorder to run workflow.docker-compose.bats before workflow.install.profile_drupalcms.bats:

-  bats "${TEST_DIR}"/bats/e2e/workflow.install.profile_drupalcms.bats
-  bats "${TEST_DIR}"/bats/e2e/workflow.docker-compose.bats
+  bats "${TEST_DIR}"/bats/e2e/workflow.docker-compose.bats
+  bats "${TEST_DIR}"/bats/e2e/workflow.install.profile_drupalcms.bats
.vortex/installer/src/Prompts/Handlers/Starter.php (1)

128-140: Use strict comparison for response checks.

Avoid loose equality on string enums.

-    if ($this->response == self::LOAD_DATABASE_DEMO) {
+    if ($this->response === self::LOAD_DATABASE_DEMO) {
@@
-    elseif ($this->response == self::INSTALL_PROFILE_CORE || $this->response == self::INSTALL_PROFILE_DRUPALCMS) {
+    elseif ($this->response === self::INSTALL_PROFILE_CORE || $this->response === self::INSTALL_PROFILE_DRUPALCMS) {

Also applies to: 133-140

♻️ Duplicate comments (18)
tests/behat/features/search.feature (2)

15-17: Batch the indexing steps to cut noise and speed up tests.

Replace three per-node enqueue steps with one “queue all created” step.

-    And I add the "page" content with the title "[TEST] Test page uniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page otheruniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page thirduniquestring" to the search index
+    And I queue all created "page" content for search indexing

19-19: Avoid fixed sleeps; wait for index readiness.

Use a deterministic wait to reduce flakiness.

-    And I wait for 5 seconds
+    And I wait for search indexing to complete
#!/bin/bash
# Find any remaining fixed sleeps in Behat features.
rg -nP --glob 'tests/behat/**/*.feature' 'I wait for \d+\s+seconds'
web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-68: Boolean “status” field added — good alignment with the View filter; plan update + reindex.

Ensure this config lands on existing sites and trigger a full reindex to populate the new field.

#!/bin/bash
# Deploy path (run on target env):
# 1) Import config
drush cim -y
# 2) Clear and reindex the "content" index
drush sapi-c
drush sapi-i -y content
# Verify "status" field exists on the index and documents are updated
drush eval '$i=\Drupal::service("search_api.index_pool")->getIndex("content"); print_r(array_keys($i->getFields()));'
web/modules/custom/ys_search/config/install/views.view.search.yml (2)

210-210: Switched to “teaser” view mode — verify every bundle has a teaser display.

Prevent regressions where a bundle lacks teaser config.

#!/bin/bash
# List teaser displays present in config; ensure all indexed bundles are covered.
fd -a 'core.entity_view_display.node.*.teaser.yml' | sort

165-202: Remove redundant expose/group_info for a non-exposed boolean filter.

YAML noise; keep only essentials for exposed: false.

         status:
           id: status
           table: search_api_index_content
           field: status
           relationship: none
           group_type: group
           admin_label: ''
           plugin_id: search_api_boolean
           operator: '='
           value: '1'
           group: 1
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
+          exposed: false
.vortex/tests/bats/_helper.bash (1)

534-538: Clarify the intent: only guard markers are enforced.

Update the comment to reflect that we now assert only VORTEX_DEV guard markers (“#;<”/“#;>”), not every “#;”-style comment.

-  # Assert all special comments were removed.
+  # Assert VORTEX_DEV guard markers were removed.
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

143-146: Fix doc typos: handlerManager→PromptManager; handlering→prompting.

-   * This is used by handlerManager to show an appropriate message (via
-   * info(), ok(), etc.) when using a resolved value instead of handlering
+   * This is used by PromptManager to show an appropriate message (via
+   * info(), ok(), etc.) when using a resolved value instead of prompting
.gitignore (1)

44-46: Keep the recipes directory via a .gitkeep exception.

Add an exception so an empty recipes/ survives in new repos; commit recipes/.gitkeep.

 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+!recipes/.gitkeep
composer.json (1)

133-135: Add drupal-recipe to installer-types to activate this mapping.

Without declaring "drupal-recipe" in extra.installer-types, oomphinc/composer-installers-extender won’t map type:drupal-recipe to recipes/{$name}.

   "installer-types": [
     "bower-asset",
     "npm-asset",
-    "drupal-library"
+    "drupal-library",
+    "drupal-recipe"
   ],

Optional check:

#!/bin/bash
rg -n '"type:drupal-recipe"|drupal-recipe' composer.json -n -C2
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (2)

14-15: Coverage annotation references the wrong class.

This test manipulates Starter; update CoversClass accordingly.

-#[CoversClass(ProvisionType::class)]
+#[CoversClass(Starter::class)]

27-34: Fix incorrect expectations: Drupal CMS composer entries should be present (not absent).

Replace “not contains” directory scan with precise composer.json assertions, including http-client.

       'starter, Drupal CMS profile' => [
         static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::INSTALL_PROFILE_DRUPALCMS)),
-        static::cw(fn(FunctionalTestCase $test) => $test->assertSutNotContains([
-          'drupal/cms',
-          'wikimedia/composer-merge-plugin',
-          'vendor/drupal/cms/composer.json',
-        ])),
+        static::cw(function (FunctionalTestCase $test) {
+          $composer = json_decode(file_get_contents(static::$sut . '/composer.json'), true);
+          $require  = $composer['require'] ?? [];
+          $merge    = $composer['extra']['merge-plugin'] ?? [];
+
+          $test->assertArrayHasKey('drupal/cms', $require);
+          $test->assertArrayHasKey('wikimedia/composer-merge-plugin', $require);
+          $test->assertArrayHasKey('symfony/http-client', $require);
+          $test->assertContains('vendor/drupal/cms/composer.json', $merge['require'] ?? []);
+        }),
       ],
.vortex/installer/src/Prompts/Handlers/Profile.php (2)

145-145: Anchor .env replacement to the DRUPAL_PROFILE line (defensive).

Prevents accidental partial matches elsewhere in the file.

-    File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);
+    File::replaceContentInFile($t . '/.env', '/^DRUPAL_PROFILE=.*/m', 'DRUPAL_PROFILE=' . $v);

147-149: Make contrib-path detection OS-agnostic.

Current check fails if forward slashes are used on Windows.

-    $is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
+    // Detect both Unix and Windows separators in provided path.
+    $is_contrib_profile = (strpbrk($v, '/\\') !== FALSE);
.vortex/installer/src/Prompts/PromptManager.php (2)

166-167: Apply the same guard for Webroot logging.

Mirror the Profile guard to avoid calling info(NULL).

-              info($handler->resolvedMessage($r, $resolved));
+              if ($m = $handler->resolvedMessage($r, $resolved)) {
+                info($m);
+              }

128-141: DRY the resolve/log/prompt flow with a helper.

Both blocks duplicate resolve→optional-log→prompt fallback. Extract a helper for consistency.

Diff within these blocks:

-        function (array $r, $pr, $n): string {
-          $handler = $this->handlers[Profile::id()];
-          $resolved = $handler->resolvedValue($r);
-          if (is_string($resolved)) {
-            if ($m = $handler->resolvedMessage($r, $resolved)) { info($m); }
-            return $resolved;
-          }
-          else {
-            return (string) select(...$this->args(Profile::class));
-          }
-        },
+        fn(array $r, $pr, $n): string
+          => $this->resolveOrPrompt(Profile::id(), $r, fn() => (string) select(...$this->args(Profile::class))),
-          function (array $r, $pr, $n): string {
-            $handler = $this->handlers[Webroot::id()];
-            $resolved = $handler->resolvedValue($r);
-            if (is_string($resolved)) {
-              if ($m = $handler->resolvedMessage($r, $resolved)) { info($m); }
-              return $resolved;
-            }
-            else {
-              return text(...$this->args(Webroot::class, NULL, $r));
-            }
-          },
+          fn(array $r, $pr, $n): string
+            => $this->resolveOrPrompt(Webroot::id(), $r, fn() => text(...$this->args(Webroot::class, NULL, $r))),

Add this method to the class (outside the shown ranges):

private function resolveOrPrompt(string $id, array $r, callable $prompt): string {
  $handler = $this->handlers[$id];
  $resolved = $handler->resolvedValue($r);
  if (is_string($resolved)) {
    if ($m = $handler->resolvedMessage($r, $resolved)) {
      info($m);
    }
    return $resolved;
  }
  return (string) $prompt();
}

Also applies to: 161-174

.vortex/installer/src/Prompts/Handlers/Starter.php (3)

31-39: Align label to say “recipe” for Drupal CMS.

Copy below already mentions “Drupal CMS recipe”; make the label match.

-    $label2 = Tui::bold('Drupal CMS, installed from profile');
+    $label2 = Tui::bold('Drupal CMS, installed from recipe');

69-72: Align option text with copy (“recipe” for Drupal CMS).

-      self::INSTALL_PROFILE_DRUPALCMS => 'Drupal CMS, installed from profile',
+      self::INSTALL_PROFILE_DRUPALCMS => 'Drupal CMS, installed from recipe',

93-115: Null-deref on missing composer.json; add installer-path and plugin allow; use strict compare.

  • fromFile() can return NULL → fatal on addLink/addProperty.
  • Recipes will install under vendor without installer-paths mapping.
  • Composer 2 may block composer/installers without allow-plugins.
  • Prefer strict equality.
-    if ($this->response == self::INSTALL_PROFILE_DRUPALCMS) {
-      $cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
+    if ($this->response === self::INSTALL_PROFILE_DRUPALCMS) {
+      $composerPath = $this->tmpDir . '/composer.json';
+      $cj = JsonManipulator::fromFile($composerPath);
+      if ($cj === NULL) {
+        throw new \RuntimeException(sprintf('composer.json not found or unreadable at %s', $composerPath));
+      }
 
       $cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
       $cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
       $cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);
 
       $cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
       $cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
+      $cj->addConfigSetting('allow-plugins.composer/installers', TRUE);
 
       $cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-false', TRUE);
       $cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
       $cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
       $cj->addProperty('extra.merge-plugin.recurse', TRUE);
       $cj->addProperty('extra.merge-plugin.replace', TRUE);
       $cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
+      $cj->addProperty('extra.installer-paths.web/recipes/{$name}', ['type:drupal-recipe']);
 
-      $c = $cj->getContents();
-      file_put_contents($this->tmpDir . '/composer.json', $c);
+      $c = $cj->getContents();
+      if (file_put_contents($composerPath, $c) === FALSE) {
+        throw new \RuntimeException(sprintf('Failed to write composer.json to %s', $composerPath));
+      }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ca9fb1e and f6913d4.

⛔ Files ignored due to path filters (23)
  • .vortex/installer/tests/Fixtures/install/_baseline/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/tests/behat/features/search.feature is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_demo_db/.ignorecontent is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupalcms_profile/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/theme_absent/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_none/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (19)
  • .gitignore (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Internal.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Profile.php (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/Starter.php (6 hunks)
  • .vortex/installer/src/Prompts/Handlers/Webroot.php (1 hunks)
  • .vortex/installer/src/Prompts/PromptManager.php (2 hunks)
  • .vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (2 hunks)
  • .vortex/tests/bats/_helper.bash (1 hunks)
  • .vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (1 hunks)
  • .vortex/tests/test.workflow.sh (2 hunks)
  • composer.json (1 hunks)
  • phpmd.xml (1 hunks)
  • tests/behat/features/search.feature (1 hunks)
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml (1 hunks)
  • web/modules/custom/ys_search/config/install/views.view.search.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
composer.json

📄 CodeRabbit inference engine (CLAUDE.md)

When adding JavaScript or CSS libraries as Drupal libraries with Composer, define them as inline Composer packages in the 'repositories' section of composer.json

Files:

  • composer.json
tests/behat/**/*.feature

📄 CodeRabbit inference engine (CLAUDE.md)

tests/behat/**/*.feature: All Behat feature files must use the standard user story format: 'As a [user type]\nI want to [action]\nSo that [benefit]'
Use consistent user types in Behat feature files: 'site visitor', 'site administrator', and 'content editor'
Do not use commas or periods in user story statements within Behat feature files

Files:

  • tests/behat/features/search.feature
web/modules/custom/**

📄 CodeRabbit inference engine (CLAUDE.md)

Custom modules should be placed in 'web/modules/custom/'

Files:

  • web/modules/custom/ys_search/config/install/search_api.index.content.yml
  • web/modules/custom/ys_search/config/install/views.view.search.yml
🧠 Learnings (1)
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.

Applied to files:

  • .vortex/tests/test.workflow.sh
  • .vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats
  • .vortex/tests/bats/_helper.bash
🧬 Code graph analysis (12)
.vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/Internal.php (3)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/tests/test.workflow.sh (3)
.vortex/tests/test.common.sh (1)
  • bats (28-37)
.vortex/tests/test.deployment.sh (1)
  • bats (29-38)
.vortex/tests/test.postbuild.sh (1)
  • bats (20-29)
.vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (2)
.vortex/tests/bats/_helper.workflow.bash (6)
  • prepare_sut (7-39)
  • assert_ahoy_build (73-130)
  • assert_gitignore (132-162)
  • assert_ahoy_lint (413-425)
  • assert_ahoy_test (527-544)
  • assert_ahoy_fe (753-777)
.vortex/tests/bats/_helper.bash (1)
  • assert_webpage_contains (1022-1028)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (8)
.vortex/installer/src/Prompts/Handlers/ProvisionType.php (1)
  • ProvisionType (11-103)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/PromptManager.php (2)
  • PromptManager (58-581)
  • makeEnvName (431-433)
.vortex/installer/src/Utils/Env.php (2)
  • Env (7-245)
  • put (24-26)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)
  • CoversClass (12-47)
.vortex/installer/tests/Functional/Handlers/AbstractInstallTestCase.php (1)
  • AbstractInstallTestCase (19-101)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/tests/Functional/FunctionalTestCase.php (1)
  • assertSutNotContains (139-154)
.vortex/installer/src/Prompts/PromptManager.php (4)
.vortex/installer/src/Prompts/Handlers/Profile.php (3)
  • Profile (10-165)
  • resolvedValue (76-88)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
  • id (52-62)
  • resolvedValue (130-132)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Webroot.php (2)
  • resolvedValue (98-110)
  • resolvedMessage (115-121)
.vortex/installer/src/Utils/Tui.php (1)
  • info (42-44)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (4)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • Profile (10-165)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/src/Prompts/Handlers/Starter.php (2)
.vortex/installer/src/Utils/JsonManipulator.php (2)
  • JsonManipulator (10-76)
  • fromFile (19-36)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • AbstractHandler (10-233)
.vortex/installer/src/Prompts/Handlers/Profile.php (6)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (4)
  • resolvedValue (130-132)
  • id (52-62)
  • resolvedMessage (137-139)
  • getResponseAsString (191-197)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • resolvedValue (138-138)
  • id (19-19)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Webroot.php (4)
  • resolvedValue (98-110)
  • discover (65-79)
  • resolvedMessage (115-121)
  • process (126-146)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/ProfileCustom.php (1)
  • ProfileCustom (9-86)
.vortex/installer/src/Utils/File.php (2)
  • File (10-102)
  • replaceContentAsync (59-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build (0)
  • GitHub Check: build (1)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.3)
🔇 Additional comments (15)
tests/behat/features/search.feature (2)

11-14: Dataset aligns with moderation-aware search — LGTM.

Using status + moderation_state is consistent with the new boolean status filter.


25-27: Moderation-aware assertions — LGTM.

Correctly ensures drafts are excluded and published items are visible.

Also applies to: 31-33

phpmd.xml (1)

17-18: Exclude recipes from PHPMD — correct scope.

Good call adding the recipes exclude; the glob will cover both top-level “recipes/” and potential “web/recipes/”.

.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

149-156: ResolvedMessage signature is consistent across all Handler implementations.
Verified no class defines resolvedMessage with the old one-argument signature; all inherit or override it as resolvedMessage(array $responses, mixed $resolved): ?string.

.gitignore (1)

34-34: Wording LGTM.

.vortex/tests/test.workflow.sh (1)

73-73: LGTM: added Drupal CMS e2e shard.

The new workflow.install.profile_drupalcms.bats invocation in case 3 is correctly wired after docker-compose.

.vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1)

124-124: LGTM: default updated to LOAD_DATABASE_DEMO.

Default mapping reflects the new Starter constants.

.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)

137-139: Update all resolvedMessage overrides and call sites
The method signature now requires mixed $resolved; verify every handler override and caller passes the new parameter.

.vortex/installer/src/Prompts/Handlers/Webroot.php (1)

115-118: LGTM: resolvedMessage uses the resolved value safely.

Message emits only when $resolved is a string; consistent with new contract.

.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)

7-7: LGTM: unit test updates align with new Starter/Profile flow.

  • Importing Profile is correct.
  • Defaults and selection expectations match new constants.
  • Drupal CMS case properly asserts resolved Profile path.

Also applies to: 21-21, 24-27, 28-34, 39-39

.vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (1)

24-27: Verify symfony/http-client assertion
I couldn’t locate where Starter::process adds the symfony/http-client package. Please confirm that the install step now includes this dependency and, if so, extend the Bats test at .vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (lines 24–27):

 assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+assert_file_contains composer.json "symfony/http-client"
.vortex/installer/src/Prompts/Handlers/Profile.php (1)

76-88: Overall: new resolution + processing logic looks correct.

  • resolvedValue bridges Drupal CMS starter selection to Profile path.
  • Process handles custom and contrib profiles as expected (post-anchors/OS fix applied).

Also applies to: 131-163

.vortex/installer/src/Prompts/Handlers/Starter.php (3)

7-7: Import looks correct.

JsonManipulator usage is appropriate for composer.json mutation.


12-19: Constants/readability LGTM.

Names are clear; path matches Profile::resolvedValue usage.


79-80: Default choice LGTM.

LOAD_DATABASE_DEMO as default matches tests and first-run UX.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from f6913d4 to fff572d Compare September 1, 2025 21:46
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 21:48 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.vortex/installer/src/Prompts/Handlers/Internal.php (1)

127-131: Use null-safe access for Starter response to avoid undefined index

Minor hardening: guard when Starter response is absent in edge flows (e.g., pre-resolution paths).

Apply:

-      if ($responses[Starter::id()] !== Starter::LOAD_DATABASE_DEMO) {
+      $starter = $responses[Starter::id()] ?? null;
+      if ($starter !== Starter::LOAD_DATABASE_DEMO) {
         $is_demo = FALSE;
       }
.vortex/installer/src/Prompts/Handlers/Profile.php (1)

30-32: Hint text: select uses Enter, not Space bar.

Minor UX copy fix.

-    return 'Use ⬆, ⬇ and Space bar to select which Drupal profile to use.';
+    return 'Use ⬆ and ⬇ to select a Drupal profile, then press Enter.';
.vortex/installer/src/Prompts/Handlers/Starter.php (1)

128-140: Use strict comparison in postInstall.
Avoid loose comparisons for clarity and safety.

-    if ($this->response == self::LOAD_DATABASE_DEMO) {
+    if ($this->response === self::LOAD_DATABASE_DEMO) {
@@
-    elseif ($this->response == self::INSTALL_PROFILE_CORE || $this->response == self::INSTALL_PROFILE_DRUPALCMS) {
+    elseif ($this->response === self::INSTALL_PROFILE_CORE || $this->response === self::INSTALL_PROFILE_DRUPALCMS) {
♻️ Duplicate comments (17)
web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-69: Plan a config update + reindex for existing sites.

This change in config/install won’t auto-apply to sites already installed. Add a post-update (or deploy runbook) to import the updated index config and trigger a full reindex (e.g., drush cim -y && drush sapi-c && drush sapi-i -y content).

tests/behat/features/search.feature (2)

15-17: Batch indexing to reduce noise and time.

Prefer a single step to enqueue all created pages for indexing if available.

Apply:

-    And I add the "page" content with the title "[TEST] Test page uniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page otheruniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page thirduniquestring" to the search index
+    And I queue all created "page" content for search indexing

19-20: Replace fixed sleep with deterministic wait.

Poll for index readiness instead of sleeping to cut flakiness.

-    And I wait for 5 seconds
-    And I visit "/search"
+    And I wait for search indexing to complete
+    And I visit "/search"
web/modules/custom/ys_search/config/install/views.view.search.yml (2)

210-210: Switch to teaser view mode — verify bundle displays.

Confirm every indexed bundle has a configured “teaser” display with intended fields; otherwise adjust.


176-202: Trim redundant expose/group blocks for a non-exposed filter.

Since exposed: false, the expose and group_info subtrees add noise.

           exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
           is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
.vortex/tests/bats/_helper.bash (1)

534-538: Clarify intent: only enforce VORTEX_DEV guard markers, not generic '#;' comments.

Keep the relaxed assertion but fix the misleading comment to avoid confusion.

-  # Assert all special comments were removed.
-  # assert_dir_not_contains_string "${dir}" "#;"
+  # Assert VORTEX_DEV guard markers were removed (do not assert removal of all '#;' comments).
+  # NOTE: Generic '#;' comments are allowed; only '#;<' and '#;>' markers are enforced.
   assert_dir_not_contains_string "${dir}" "#;<"
   assert_dir_not_contains_string "${dir}" "#;>"
composer.json (1)

133-135: Mapping alone won’t take effect without declaring the type. Add "drupal-recipe" to installer-types.

oomphinc/composer-installers-extender requires the custom type to be listed in extra.installer-types; otherwise packages of type "drupal-recipe" won’t install to recipes/{$name}.

         "installer-types": [
             "bower-asset",
             "npm-asset",
-            "drupal-library"
+            "drupal-library",
+            "drupal-recipe"
         ],
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

143-156: Fix doc typos and align with the actual manager name.

Use “PromptManager” and “prompting”; keep the new $resolved param as is.

-   * This is used by handlerManager to show an appropriate message (via
-   * info(), ok(), etc.) when using a resolved value instead of handlering
+   * This is used by PromptManager to show an appropriate message (via
+   * info(), ok(), etc.) when using a resolved value instead of prompting
    * for input.

To verify all implementers accept the new $resolved param:

#!/bin/bash
# Find implementations missing the $resolved param
rg -nP --type=php '(?s)implements\s+HandlerInterface.*?\bfunction\s+resolvedMessage\s*\(\s*array\s+\$responses\s*\)' -C2 .vortex/installer
.gitignore (1)

44-46: Optional: keep the recipes directory present with a tracked placeholder.

If you want an empty recipes/ directory in fresh clones, allow a keep file.

 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+!recipes/.gitkeep

If .gitkeep (or similar) is already tracked, this change is not required.

.vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (2)

16-16: Fix env value: must match Starter option key, not alias

Use the canonical option key so the non-interactive selection is applied.

Apply this diff:

-  export VORTEX_INSTALLER_PROMPT_STARTER="drupalcms_profile"
+  export VORTEX_INSTALLER_PROMPT_STARTER="install_profile_drupalcms"

39-40: Add teardown to prevent env leakage across tests

Unset env vars set by this test to avoid cross-test interference.

Apply this diff:

   assert_webpage_contains "/" "This is the home page of your new site."
 }
+teardown() {
+  unset VORTEX_INSTALLER_PROMPT_STARTER VORTEX_INSTALLER_IS_DEMO_DB_DOWNLOAD_SKIP
+}
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (2)

14-15: Fix coverage subject: this test targets Starter, not ProvisionType.

Align the CoversClass to the handler under test.

-#[CoversClass(ProvisionType::class)]
+#[CoversClass(Starter::class)]

27-34: Incorrect expectation: Drupal CMS deps should be present, not absent.

Starter::process() adds drupal/cms, composer-merge-plugin, symfony/http-client and wires merge-plugin. Assert presence with a composer.json read.

-      'starter, Drupal CMS profile' => [
-        static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::INSTALL_PROFILE_DRUPALCMS)),
-        static::cw(fn(FunctionalTestCase $test) => $test->assertSutNotContains([
-          'drupal/cms',
-          'wikimedia/composer-merge-plugin',
-          'vendor/drupal/cms/composer.json',
-        ])),
-      ],
+      'starter, Drupal CMS profile' => [
+        static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::INSTALL_PROFILE_DRUPALCMS)),
+        static::cw(function (FunctionalTestCase $test): void {
+          $composer = json_decode(file_get_contents(static::$sut . '/composer.json'), true, flags: JSON_THROW_ON_ERROR);
+          $require  = $composer['require'] ?? [];
+          $merge    = $composer['extra']['merge-plugin'] ?? [];
+
+          $test->assertArrayHasKey('drupal/cms', $require);
+          $test->assertArrayHasKey('wikimedia/composer-merge-plugin', $require);
+          $test->assertArrayHasKey('symfony/http-client', $require);
+          $test->assertContains('vendor/drupal/cms/composer.json', $merge['require'] ?? []);
+        }),
+      ],
.vortex/installer/src/Prompts/Handlers/Profile.php (2)

145-146: Anchor DRUPAL_PROFILE replacement to the line (defensive).

Prevents accidental matches elsewhere in .env.

-    File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);
+    File::replaceContentInFile($t . '/.env', '/^DRUPAL_PROFILE=.*/m', 'DRUPAL_PROFILE=' . $v);

147-149: Cross-OS path check: detect both '/' and ''.

str_contains with DIRECTORY_SEPARATOR misses Unix-style paths on Windows.

-    // Assume that profiles provided as a path are contrib profiles.
-    $is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
+    // Assume that profiles provided as a path are contrib profiles.
+    // Detect both Unix and Windows separators.
+    $is_contrib_profile = (strpbrk($v, '/\\') !== FALSE);
.vortex/installer/src/Prompts/Handlers/Starter.php (2)

31-39: Align labels with copy (“Drupal CMS”, “recipe”).
Update labels to match the agreed wording.

-    $label1 = Tui::bold('Drupal, installed from profile');
+    $label1 = Tui::bold('Drupal, installed from recipe');
@@
-    $label2 = Tui::bold('Drupal CMS, installed from profile');
+    $label2 = Tui::bold('Drupal CMS, installed from recipe');
@@
-    $label3 = Tui::bold('Drupal, loaded from the demo database');
+    $label3 = Tui::bold('Drupal CMS, loaded from the demo database');

69-72: Align option labels with copy (“Drupal CMS”, “recipe”).
Mirror the label changes in the options.

-      self::INSTALL_PROFILE_CORE => 'Drupal, installed from profile',
-      self::INSTALL_PROFILE_DRUPALCMS => 'Drupal CMS, installed from profile',
-      self::LOAD_DATABASE_DEMO => 'Drupal, loaded from the demo database',
+      self::INSTALL_PROFILE_CORE => 'Drupal, installed from recipe',
+      self::INSTALL_PROFILE_DRUPALCMS => 'Drupal CMS, installed from recipe',
+      self::LOAD_DATABASE_DEMO => 'Drupal CMS, loaded from the demo database',
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f6913d4 and fff572d.

⛔ Files ignored due to path filters (23)
  • .vortex/installer/tests/Fixtures/install/_baseline/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/tests/behat/features/search.feature is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_demo_db/.ignorecontent is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_cms_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_cms_profile/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/theme_absent/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_none/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (19)
  • .gitignore (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Internal.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Profile.php (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/Starter.php (6 hunks)
  • .vortex/installer/src/Prompts/Handlers/Webroot.php (1 hunks)
  • .vortex/installer/src/Prompts/PromptManager.php (3 hunks)
  • .vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (2 hunks)
  • .vortex/tests/bats/_helper.bash (1 hunks)
  • .vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (1 hunks)
  • .vortex/tests/test.workflow.sh (2 hunks)
  • composer.json (1 hunks)
  • phpmd.xml (1 hunks)
  • tests/behat/features/search.feature (1 hunks)
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml (1 hunks)
  • web/modules/custom/ys_search/config/install/views.view.search.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
tests/behat/**/*.feature

📄 CodeRabbit inference engine (CLAUDE.md)

tests/behat/**/*.feature: All Behat feature files must use the standard user story format: 'As a [user type]\nI want to [action]\nSo that [benefit]'
Use consistent user types in Behat feature files: 'site visitor', 'site administrator', and 'content editor'
Do not use commas or periods in user story statements within Behat feature files

Files:

  • tests/behat/features/search.feature
web/modules/custom/**

📄 CodeRabbit inference engine (CLAUDE.md)

Custom modules should be placed in 'web/modules/custom/'

Files:

  • web/modules/custom/ys_search/config/install/views.view.search.yml
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml
composer.json

📄 CodeRabbit inference engine (CLAUDE.md)

When adding JavaScript or CSS libraries as Drupal libraries with Composer, define them as inline Composer packages in the 'repositories' section of composer.json

Files:

  • composer.json
🧠 Learnings (1)
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.

Applied to files:

  • .vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats
  • .vortex/tests/bats/_helper.bash
  • .vortex/tests/test.workflow.sh
🧬 Code graph analysis (12)
.vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (2)
.vortex/tests/bats/_helper.workflow.bash (6)
  • prepare_sut (7-39)
  • assert_ahoy_build (73-130)
  • assert_gitignore (132-162)
  • assert_ahoy_lint (413-425)
  • assert_ahoy_test (527-544)
  • assert_ahoy_fe (753-777)
.vortex/tests/bats/_helper.bash (1)
  • assert_webpage_contains (1022-1028)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (4)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • Profile (10-165)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/installer/src/Prompts/Handlers/Internal.php (3)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (7)
.vortex/installer/src/Prompts/Handlers/ProvisionType.php (1)
  • ProvisionType (11-103)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/PromptManager.php (2)
  • PromptManager (58-597)
  • makeEnvName (413-415)
.vortex/installer/src/Utils/Env.php (2)
  • Env (7-245)
  • put (24-26)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)
  • CoversClass (12-47)
.vortex/installer/tests/Functional/Handlers/AbstractInstallTestCase.php (1)
  • AbstractInstallTestCase (19-101)
.vortex/installer/tests/Functional/FunctionalTestCase.php (1)
  • assertSutNotContains (139-154)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/src/Prompts/PromptManager.php (7)
.vortex/installer/src/Prompts/Handlers/Profile.php (3)
  • Profile (10-165)
  • resolvedValue (76-88)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
  • id (52-62)
  • resolvedValue (130-132)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • id (19-19)
  • resolvedValue (138-138)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
  • Webroot (12-148)
  • resolvedValue (98-110)
  • resolvedMessage (115-121)
.vortex/installer/src/Utils/Tui.php (1)
  • info (42-44)
scripts/custom/provision-10-example.sh (1)
  • info (22-22)
scripts/vortex/update-vortex.sh (1)
  • info (46-46)
.vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/tests/test.workflow.sh (3)
.vortex/tests/test.common.sh (1)
  • bats (28-37)
.vortex/tests/test.postbuild.sh (1)
  • bats (20-29)
.vortex/tests/test.deployment.sh (1)
  • bats (29-38)
.vortex/installer/src/Prompts/Handlers/Starter.php (3)
.vortex/installer/src/Utils/JsonManipulator.php (2)
  • JsonManipulator (10-76)
  • fromFile (19-36)
.vortex/installer/src/Utils/Tui.php (3)
  • Tui (15-233)
  • bold (74-76)
  • underscore (78-80)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (2)
  • AbstractHandler (10-233)
  • default (109-111)
.vortex/installer/src/Prompts/Handlers/Profile.php (4)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (4)
  • resolvedValue (130-132)
  • id (52-62)
  • resolvedMessage (137-139)
  • getResponseAsString (191-197)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/ProfileCustom.php (1)
  • ProfileCustom (9-86)
.vortex/installer/src/Utils/File.php (2)
  • File (10-102)
  • replaceContentAsync (59-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: build (0)
  • GitHub Check: build (1)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.3)
  • GitHub Check: vortex-test-docs
🔇 Additional comments (19)
web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-65: LGTM: boolean Published field added to the index mapping.

The status mapping (property_path: status, type: boolean) is correct and aligns with the view’s boolean filter.

tests/behat/features/search.feature (3)

11-14: Data schema update (status + moderation_state) — OK.

The table structure is sound and matches the published/draft expectations.


25-27: Assertions match published-only behavior — OK.


31-33: Scoped query assertions — OK.

web/modules/custom/ys_search/config/install/views.view.search.yml (1)

165-175: LGTM: Published-only filter added.

Filter configuration (plugin_id: search_api_boolean, value: '1') matches the index’s boolean status field.

phpmd.xml (1)

17-18: LGTM: exclude Drupal recipes from PHPMD.

The new exclude-pattern correctly skips recipes. No further changes needed.

.gitignore (1)

34-34: LGTM: clarify scaffold allowance.

Comment reads well and matches project conventions.

.vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1)

124-125: No stale legacy constants found
Ran a project-wide search for DRUPAL_LOAD_DATABASE_DEMO and DRUPAL_INSTALL_PROFILE outside of vendor/; no matches were found.

.vortex/installer/src/Prompts/Handlers/Webroot.php (1)

115-117: Signature alignment and message logic look correct

The handler now cleanly uses the resolved value threaded by PromptManager and guards on string type. Consistent with Profile::resolvedMessage.

.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (4)

7-7: Import added is necessary for expectations on Profile::id()

This fixes static resolution for the new profile expectation keys.


21-21: Updated default expectation matches new constant

Switch to LOAD_DATABASE_DEMO aligns with Starter::default().


24-27: Verify keystroke path matches options order/default selection

Given options order (Core, Drupal CMS, Demo) and default set to LOAD_DATABASE_DEMO, confirm Laravel Prompts starts the cursor on the default and how DOWN behaves (wrap vs. move). Otherwise this case may select the wrong item.


28-34: Add case looks good; confirm DOWN count maps to Drupal CMS

Same caveat: ensure two DOWNs from the initial position deterministically select INSTALL_PROFILE_DRUPALCMS in your prompt harness.

.vortex/installer/src/Prompts/PromptManager.php (2)

128-131: Good use of resolveOrPrompt for Profile.

Removes duplication and centralizes resolved-message logging.


153-154: Good: Webroot resolution also centralized.

.vortex/installer/src/Prompts/Handlers/Profile.php (1)

76-88: LGTM: Profile auto-resolves when Starter selects Drupal CMS.

Clean separation via resolvedValue/resolvedMessage.

.vortex/installer/src/Prompts/Handlers/Starter.php (3)

7-7: Import looks good.
JsonManipulator import is appropriate for composer.json mutations.


78-80: Default selection change LGTM.
Choosing demo DB by default aligns with “first run” guidance.


103-111: Composer merge-plugin keys: correct invalid option.

  • ‘merge-false’ is not a valid key; replace it with ‘merge-dev’.
  • ‘merge-replace’ and ‘replace’ are both valid per wikimedia/composer-merge-plugin v2.1 docs; no change needed.

Likely an incorrect or invalid review comment.

Comment on lines +137 to 139
public function resolvedMessage(array $responses, mixed $resolved): ?string {
return NULL;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Public API change to resolvedMessage — ensure all overrides and call sites updated

Signature expansion is fine; confirm no handlers still implement the old single-arg method and no call sites pass one argument.

Add/upd phpdoc here and in overrides to document $resolved purpose and types for IDEs/static analysis.


🏁 Script executed:

#!/bin/bash
# Find handlers overriding old signature (no second param).
rg -nP --type=php 'function\s+resolvedMessage\s*\(\s*array\s+\$responses\s*(?!,)\s*\)' .vortex/installer/src/Prompts/Handlers

# Find call sites still passing a single argument.
rg -nP --type=php '->resolvedMessage\s*\(\s*[^,)]*\)' .vortex/installer/src

Length of output: 610


🏁 Script executed:

#!/bin/bash
# re-run override search
rg -nP --type=php -n 'function\s+resolvedMessage\s*\(\s*array\s+\$responses\s*(?!,)' .vortex/installer/src/Prompts/Handlers

# find call sites still passing a single argument
rg -nP --type=php -n '\b->resolvedMessage\s*\(\s*[^,)]*\)' .vortex/installer/src

Length of output: 208


Legacy single-arg resolvedMessage usage removed—all overrides and call sites now include both parameters. Optional: add or update phpdoc on resolvedMessage (and its overrides) to document the $resolved parameter for IDEs/static analysis.

🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php around lines 137 to
139: the method signature was updated to accept the new $resolved parameter but
the phpdoc wasn't updated; add or update the docblock immediately above
resolvedMessage to document both parameters ($responses array and mixed
$resolved) and the ?string return type (including description of what $resolved
represents), and propagate the same phpdoc update to any overriding
implementations so IDEs and static analyzers recognize the new parameter.

Comment on lines +12 to 19
const INSTALL_PROFILE_CORE = 'install_profile_core';

const DRUPAL_INSTALL_PROFILE = 'drupal_profile';
const INSTALL_PROFILE_DRUPALCMS = 'install_profile_drupalcms';

const LOAD_DATABASE_DEMO = 'load_demodb';

const INSTALL_PROFILE_DRUPALCMS_PATH = '../recipes/drupal_cms_starter';

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Confirm usage of INSTALL_PROFILE_DRUPALCMS_PATH or remove it.
The constant isn’t referenced in this file. Verify it’s used elsewhere to avoid dead code.


🏁 Script executed:

#!/bin/bash
# Find references to the constant across the repo.
rg -nC2 '\bINSTALL_PROFILE_DRUPALCMS_PATH\b'

Length of output: 46


Remove the unused constant INSTALL_PROFILE_DRUPALCMS_PATH. It isn’t referenced anywhere in the repo.

🤖 Prompt for AI Agents
In .vortex/installer/src/Prompts/Handlers/Starter.php around lines 12 to 19, the
constant INSTALL_PROFILE_DRUPALCMS_PATH is declared but unused; remove the
INSTALL_PROFILE_DRUPALCMS_PATH constant declaration from this file (delete the
line defining it) and run a quick repo-wide search to confirm it has no
references; adjust surrounding spacing/commas if needed to keep file formatting
consistent.

Comment on lines +93 to +115
if ($this->response == self::INSTALL_PROFILE_DRUPALCMS) {
$cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');

$cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
$cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
$cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);

$cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
$cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);

$cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
$cj->addProperty('extra.merge-plugin.merge-false', TRUE);
$cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
$cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
$cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
$cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
$cj->addProperty('extra.merge-plugin.recurse', TRUE);
$cj->addProperty('extra.merge-plugin.replace', TRUE);
$cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);

$c = $cj->getContents();
file_put_contents($this->tmpDir . '/composer.json', $c);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Fix loose comparison, null deref, missing installer-path, and Composer plugin allowances.

  • Use strict comparison.
  • Guard JsonManipulator::fromFile() returning NULL and check write result.
  • Add allow-plugins for composer/installers to avoid Composer 2 blocks.
  • Ensure recipes of type drupal-recipe install under web/recipes/{$name}.
  • Replace invalid merge-false with merge-dev.
-    if ($this->response == self::INSTALL_PROFILE_DRUPALCMS) {
-      $cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
+    if ($this->response === self::INSTALL_PROFILE_DRUPALCMS) {
+      $composerPath = $this->tmpDir . '/composer.json';
+      $cj = JsonManipulator::fromFile($composerPath);
+      if ($cj === NULL) {
+        throw new \RuntimeException(sprintf('composer.json not found or unreadable at %s', $composerPath));
+      }
@@
       $cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
       $cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
       $cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);
@@
       $cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
       $cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
+      $cj->addConfigSetting('allow-plugins.composer/installers', TRUE);
@@
       $cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
-      $cj->addProperty('extra.merge-plugin.merge-false', TRUE);
+      $cj->addProperty('extra.merge-plugin.merge-dev', TRUE);
       $cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
       $cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
       $cj->addProperty('extra.merge-plugin.recurse', TRUE);
       $cj->addProperty('extra.merge-plugin.replace', TRUE);
       $cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
+      $cj->addProperty('extra.installer-paths.web/recipes/{$name}', ['type:drupal-recipe']);
@@
-      $c = $cj->getContents();
-      file_put_contents($this->tmpDir . '/composer.json', $c);
+      $c = $cj->getContents();
+      if (file_put_contents($composerPath, $c) === FALSE) {
+        throw new \RuntimeException(sprintf('Failed to write composer.json to %s', $composerPath));
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ($this->response == self::INSTALL_PROFILE_DRUPALCMS) {
$cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
$cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
$cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
$cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);
$cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
$cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
$cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
$cj->addProperty('extra.merge-plugin.merge-false', TRUE);
$cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
$cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
$cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
$cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
$cj->addProperty('extra.merge-plugin.recurse', TRUE);
$cj->addProperty('extra.merge-plugin.replace', TRUE);
$cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
$c = $cj->getContents();
file_put_contents($this->tmpDir . '/composer.json', $c);
}
if ($this->response === self::INSTALL_PROFILE_DRUPALCMS) {
$composerPath = $this->tmpDir . '/composer.json';
$cj = JsonManipulator::fromFile($composerPath);
if ($cj === NULL) {
throw new \RuntimeException(sprintf('composer.json not found or unreadable at %s', $composerPath));
}
$cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
$cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
$cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);
$cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
$cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
$cj->addConfigSetting('allow-plugins.composer/installers', TRUE);
$cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
$cj->addProperty('extra.merge-plugin.merge-dev', TRUE);
$cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
$cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
$cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
$cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
$cj->addProperty('extra.merge-plugin.recurse', TRUE);
$cj->addProperty('extra.merge-plugin.replace', TRUE);
$cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
$cj->addProperty('extra.installer-paths.web/recipes/{$name}', ['type:drupal-recipe']);
$c = $cj->getContents();
if (file_put_contents($composerPath, $c) === FALSE) {
throw new \RuntimeException(sprintf('Failed to write composer.json to %s', $composerPath));
}
}

Comment on lines +563 to +596
/**
* Resolve a value via handler or prompt the user.
*
* This method is used to resolve a value via a handler's resolvedValue()
* method. If the value is not resolved, it will prompt the user using the
* provided prompt callable.
*
* @param string $handler_id
* The handler ID.
* @param array $r
* Current form responses for context-aware methods.
* @param callable $prompt
* The prompt callable to use if the value is not resolved.
*
* @return string
* The resolved value.
*/
protected function resolveOrPrompt(string $handler_id, array $r, callable $prompt): string {
$handler = $this->handlers[$handler_id];
$resolved = $handler->resolvedValue($r);

if (is_string($resolved)) {
$message = $handler->resolvedMessage($r, $resolved);

if ($message) {
info($message);
}

return $resolved;
}

return (string) $prompt();
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Optional: guard handler existence in resolveOrPrompt.

Defensive check mirrors other parts of the class.

   protected function resolveOrPrompt(string $handler_id, array $r, callable $prompt): string {
-    $handler = $this->handlers[$handler_id];
+    if (!array_key_exists($handler_id, $this->handlers)) {
+      throw new \RuntimeException(sprintf('Handler for "%s" not found.', $handler_id));
+    }
+    $handler = $this->handlers[$handler_id];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Resolve a value via handler or prompt the user.
*
* This method is used to resolve a value via a handler's resolvedValue()
* method. If the value is not resolved, it will prompt the user using the
* provided prompt callable.
*
* @param string $handler_id
* The handler ID.
* @param array $r
* Current form responses for context-aware methods.
* @param callable $prompt
* The prompt callable to use if the value is not resolved.
*
* @return string
* The resolved value.
*/
protected function resolveOrPrompt(string $handler_id, array $r, callable $prompt): string {
$handler = $this->handlers[$handler_id];
$resolved = $handler->resolvedValue($r);
if (is_string($resolved)) {
$message = $handler->resolvedMessage($r, $resolved);
if ($message) {
info($message);
}
return $resolved;
}
return (string) $prompt();
}
/**
* Resolve a value via handler or prompt the user.
*
* This method is used to resolve a value via a handler's resolvedValue()
* method. If the value is not resolved, it will prompt the user using the
* provided prompt callable.
*
* @param string $handler_id
* The handler ID.
* @param array $r
* Current form responses for context-aware methods.
* @param callable $prompt
* The prompt callable to use if the value is not resolved.
*
* @return string
* The resolved value.
*/
protected function resolveOrPrompt(string $handler_id, array $r, callable $prompt): string {
if (!array_key_exists($handler_id, $this->handlers)) {
throw new \RuntimeException(sprintf('Handler for "%s" not found.', $handler_id));
}
$handler = $this->handlers[$handler_id];
$resolved = $handler->resolvedValue($r);
if (is_string($resolved)) {
$message = $handler->resolvedMessage($r, $resolved);
if ($message) {
info($message);
}
return $resolved;
}
return (string) $prompt();
}
🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/PromptManager.php around lines 563 to 596:
resolveOrPrompt assumes $this->handlers[$handler_id] exists; add a defensive
guard like if (!isset($this->handlers[$handler_id])) { throw new
\InvalidArgumentException("Unknown handler: {$handler_id}"); } (or return a
sensible default) before using it, so the method mirrors other parts of the
class that validate handler existence and avoids undefined index errors.

And I add the "page" content with the title "[TEST] Test page uniquestring" to the search index
And I add the "page" content with the title "[TEST] Test page otheruniquestring" to the search index
And I add the "page" content with the title "[TEST] Test page thirduniquestring" to the search index
And I run search indexing for 3 items
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Avoid hardcoding the count; derive from created items.

Use a generic step if supported to reduce brittleness.

-    And I run search indexing for 3 items
+    And I run search indexing for the created items
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
And I run search indexing for 3 items
And I run search indexing for the created items
🤖 Prompt for AI Agents
In tests/behat/features/search.feature around line 18, the step "And I run
search indexing for 3 items" hardcodes the count; replace it with a dynamic step
that derives the number from the created items (e.g., use the variable or
context that holds created items count) or switch to a generic step such as "And
I run search indexing for all items" if available, so the step uses the actual
number of created items instead of a fixed literal.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from fff572d to 866f52c Compare September 1, 2025 22:00
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 22:02 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
composer.json (1)

152-156: Add “drupal-recipe” to installer-types or mapping won’t apply.

Without this, oomphinc/composer-installers-extender won’t route type:drupal-recipe to recipes/{$name}.

         "installer-types": [
             "bower-asset",
             "npm-asset",
-            "drupal-library"
+            "drupal-library",
+            "drupal-recipe"
         ],
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (4)

21-27: Grammar: “Label for of the handler.” → “Label for the handler.”

-   * Label for of the handler.
+   * Label for the handler.

52-57: Missing @param for placeholder($responses)

   /**
    * Optional placeholder.
    *
+   * @param array $responses
+   *   Array of collected responses.
    * @return string|null
    *   The placeholder text for the handler, or NULL if none.
    */

84-87: Doc mismatch: shouldRun returns bool, not “condition callback”

   /**
    * Check if the handler should run based on collected responses.
    *
@@
-   * @return bool
-   *   The condition callback, or null if not conditional.
+   * @return bool
+   *   TRUE to run this handler, FALSE to skip.
    */

182-185: Add @return for postInstall()

   /**
    * Actions to perform and messages to print after installation is complete.
+   *
+   * @return string|null
+   *   A message to display post-install, or NULL for none.
    */
.vortex/installer/src/Prompts/Handlers/Starter.php (1)

133-140: Differentiate build instructions for Drupal CMS; include DRUPAL_PROFILE. Also use strict compares.
This fulfills the PR objective to set DRUPAL_PROFILE when using the Drupal CMS recipe.

-    elseif ($this->response == self::INSTALL_PROFILE_CORE || $this->response == self::INSTALL_PROFILE_DRUPALCMS) {
-      $output .= 'Build project locally:' . PHP_EOL;
-      $output .= '  VORTEX_PROVISION_TYPE=profile ahoy build' . PHP_EOL;
+    elseif ($this->response === self::INSTALL_PROFILE_CORE || $this->response === self::INSTALL_PROFILE_DRUPALCMS) {
+      $output .= 'Build project locally:' . PHP_EOL;
+      if ($this->response === self::INSTALL_PROFILE_DRUPALCMS) {
+        $output .= '  DRUPAL_PROFILE=' . self::INSTALL_PROFILE_DRUPALCMS_PATH . ' VORTEX_PROVISION_TYPE=profile ahoy build' . PHP_EOL;
+      }
+      else {
+        $output .= '  VORTEX_PROVISION_TYPE=profile ahoy build' . PHP_EOL;
+      }
♻️ Duplicate comments (21)
.vortex/tests/test.workflow.sh (1)

81-86: Run docker-compose before Drupal CMS profile in default case (match case 3).

Align ordering to avoid hidden deps and flakiness.

Apply:

   *)
     phpunit "${TEST_DIR}"/phpunit
     bats "${TEST_DIR}"/bats/e2e/workflow.install.db.bats
     bats "${TEST_DIR}"/bats/e2e/workflow.install.profile.bats
-    bats "${TEST_DIR}"/bats/e2e/workflow.install.profile_drupalcms.bats
-    bats "${TEST_DIR}"/bats/e2e/workflow.docker-compose.bats
+    bats "${TEST_DIR}"/bats/e2e/workflow.docker-compose.bats
+    bats "${TEST_DIR}"/bats/e2e/workflow.install.profile_drupalcms.bats
     bats "${TEST_DIR}"/bats/e2e/workflow.install.provision.bats
     bats "${TEST_DIR}"/bats/e2e/workflow.storage.image.bats
.vortex/tests/bats/_helper.bash (1)

534-538: Clarify intent: only guard markers are enforced, not all “#;” comments.

Update the comment to remove ambiguity; keep generic “#;” check disabled.

-  # Assert all special comments were removed.
+  # Assert VORTEX_DEV guard markers were removed (do not enforce removal of all "#;" comments).
-  # assert_dir_not_contains_string "${dir}" "#;"
+  # assert_dir_not_contains_string "${dir}" "#;"
   assert_dir_not_contains_string "${dir}" "#;<"
   assert_dir_not_contains_string "${dir}" "#;>"
.gitignore (1)

44-46: Keep an empty recipes/ directory in new repos.

Add an exception and commit a placeholder to preserve the directory.

 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+!recipes/.gitkeep
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (2)

149-155: BC: resolvedMessage signature expanded — verify all implementers and call sites

Ensure every handler override accepts the new $resolved param and all invocations pass it.

Run:

#!/bin/bash
# Overrides still using old single-arg signature
rg -nP --type=php '(?s)class\s+\w+\s+implements\s+HandlerInterface.*?\bfunction\s+resolvedMessage\s*\(\s*array\s+\$responses\s*(?!,)\s*\)' .vortex/installer/src/Prompts/Handlers -C2

# Call sites passing only one arg
rg -nP --type=php '\b->resolvedMessage\s*\(\s*[^,)\s]+\s*\)' .vortex/installer/src -n

143-145: Fix doc typos: PromptManager + prompting

Update the doc to reference the correct manager and verb.

-   * This is used by handlerManager to show an appropriate message (via
-   * info(), ok(), etc.) when using a resolved value instead of handlering
+   * This is used by PromptManager to show an appropriate message (via
+   * info(), ok(), etc.) when using a resolved value instead of prompting
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)

137-139: Add phpdoc for new $resolved param (even with {@inheritdoc})

Helpful for IDEs/static analysers; mirror interface docs here.

-  /**
-   * {@inheritdoc}
-   */
+  /**
+   * {@inheritdoc}
+   *
+   * @param array $responses
+   *   Collected responses.
+   * @param mixed $resolved
+   *   Resolved value passed by PromptManager.
+   *
+   * @return string|null
+   */
   public function resolvedMessage(array $responses, mixed $resolved): ?string {
tests/behat/features/search.feature (2)

18-18: Don’t hardcode “3 items”; derive from created items

Prevents fragility as data changes.

-    And I run search indexing for 3 items
+    And I run search indexing for the created items

19-20: Avoid fixed sleeps; wait for index readiness

Replace the arbitrary wait with a deterministic polling step.

-    And I wait for 5 seconds
-    And I visit "/search"
+    And I wait for search indexing to complete
+    And I visit "/search"
web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-69: Status field mapping looks good — plan update path + reindex

Add a post-update to import this config and trigger a full reindex on existing sites.

<?php
// web/modules/custom/ys_search/ys_search.post_update.php
/**
 * Add status field to search index and reindex content.
 */
function ys_search_post_update_add_status_field_and_reindex(&$sandbox = NULL): void {
  // Import updated config for the 'content' index.
  \Drupal::service('config.installer')->installOptionalConfig(['ys_search']);

  // Clear and reindex the 'content' index.
  $index = \Drupal::entityTypeManager()->getStorage('search_api_index')->load('content');
  if ($index) {
    $index->clear();
    \Drupal::service('search_api.index_task_manager')->addItems($index);
  }
}

Also document running: drush cim -y && drush sapi-c && drush sapi-i -y content.


</blockquote></details>
<details>
<summary>web/modules/custom/ys_search/config/install/views.view.search.yml (2)</summary><blockquote>

`210-211`: **Switch to teaser view mode — verify bundle displays**

Confirm all indexed bundles have a configured “teaser” display with expected fields; adjust if not.

---

`165-202`: **Trim redundant expose/group blocks for non-exposed boolean filter**

Keeps config lean; behaviour unchanged.


```diff
         status:
           id: status
           table: search_api_index_content
           field: status
           relationship: none
           group_type: group
           admin_label: ''
           plugin_id: search_api_boolean
           operator: '='
           value: '1'
           group: 1
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
+          exposed: false
.vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (3)

16-16: Good fix: use canonical Starter key for non-interactive path.

install_profile_drupalcms is the correct option key.


39-40: Add teardown to clean environment leakage between tests.

Unset env vars set in this test.

Apply:

   assert_webpage_contains "/" "This is the home page of your new site."
 }
+teardown() {
+  unset VORTEX_INSTALLER_PROMPT_STARTER VORTEX_INSTALLER_IS_DEMO_DB_DOWNLOAD_SKIP
+}

24-27: Assert all composer mutations for this flow (missing symfony/http-client).

Starter::process() also adds symfony/http-client; assert it to catch regressions.

Apply:

   assert_file_contains composer.json "drupal/cms"
   assert_file_contains composer.json "wikimedia/composer-merge-plugin"
   assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+  assert_file_contains composer.json "symfony/http-client"
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (2)

14-15: Fix incorrect coverage target (should cover Starter).

This test exercises the Starter handler. Point CoversClass to Starter.

-#[CoversClass(ProvisionType::class)]
+#[CoversClass(Starter::class)]

27-34: Correct Drupal CMS assertions: expect composer mutations, not absence.

Starter::process() adds drupal/cms, merge-plugin, http-client, and wires merge-plugin require. Assert presence and versions.

       'starter, Drupal CMS profile' => [
         static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::INSTALL_PROFILE_DRUPALCMS)),
-        static::cw(fn(FunctionalTestCase $test) => $test->assertSutNotContains([
-          'drupal/cms',
-          'wikimedia/composer-merge-plugin',
-          'vendor/drupal/cms/composer.json',
-        ])),
+        static::cw(function (FunctionalTestCase $test) {
+          $composer = json_decode(file_get_contents(static::$sut . '/composer.json'), true);
+          $require  = $composer['require'] ?? [];
+          $merge    = $composer['extra']['merge-plugin'] ?? [];
+
+          $test->assertArrayHasKey('drupal/cms', $require);
+          $test->assertEquals('^1.0', $require['drupal/cms']);
+
+          $test->assertArrayHasKey('wikimedia/composer-merge-plugin', $require);
+          $test->assertEquals('^2.1', $require['wikimedia/composer-merge-plugin']);
+
+          $test->assertArrayHasKey('symfony/http-client', $require);
+          $test->assertEquals('^6.4 || ^7.0', $require['symfony/http-client']);
+
+          $test->assertContains('vendor/drupal/cms/composer.json', $merge['require'] ?? []);
+        }),
       ],
.vortex/installer/src/Prompts/PromptManager.php (1)

563-596: Guard missing handler in resolveOrPrompt (defensive).

Mirror checks used elsewhere to avoid undefined index errors.

   protected function resolveOrPrompt(string $handler_id, array $r, callable $prompt): string {
-    $handler = $this->handlers[$handler_id];
+    if (!array_key_exists($handler_id, $this->handlers)) {
+      throw new \RuntimeException(sprintf('Handler for "%s" not found.', $handler_id));
+    }
+    $handler = $this->handlers[$handler_id];
.vortex/installer/src/Prompts/Handlers/Profile.php (2)

145-146: Anchor DRUPAL_PROFILE replacement to the line (avoid accidental matches).

-    File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);
+    File::replaceContentInFile($t . '/.env', '/^DRUPAL_PROFILE=.*/m', 'DRUPAL_PROFILE=' . $v);

147-149: Make contrib-profile path detection OS-agnostic.

Detect both '/' and '' to work on Windows and Unix.

-    // Assume that profiles provided as a path are contrib profiles.
-    $is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
+    // Assume that profiles provided as a path are contrib profiles (detect '/' or '\').
+    $is_contrib_profile = (strpbrk($v, '/\\') !== FALSE);

Optionally add a unit test covering '../recipes/...' and '..\recipes\...'.

.vortex/installer/src/Prompts/Handlers/Starter.php (2)

31-39: Align labels with copy: use “recipe” and “Drupal CMS” consistently.
Update both description and option labels.

-    $label1 = Tui::bold('Drupal, installed from profile');
+    $label1 = Tui::bold('Drupal, installed from recipe');
@@
-    $label2 = Tui::bold('Drupal CMS, installed from profile');
+    $label2 = Tui::bold('Drupal CMS, installed from recipe');
@@
-    $label3 = Tui::bold('Drupal, loaded from the demo database');
+    $label3 = Tui::bold('Drupal CMS, loaded from the demo database');
@@
-      self::INSTALL_PROFILE_CORE => 'Drupal, installed from profile',
-      self::INSTALL_PROFILE_DRUPALCMS => 'Drupal CMS, installed from profile',
-      self::LOAD_DATABASE_DEMO => 'Drupal, loaded from the demo database',
+      self::INSTALL_PROFILE_CORE => 'Drupal, installed from recipe',
+      self::INSTALL_PROFILE_DRUPALCMS => 'Drupal CMS, installed from recipe',
+      self::LOAD_DATABASE_DEMO => 'Drupal CMS, loaded from the demo database',

Also applies to: 69-72


93-115: Fix null-deref, strict compare, invalid merge flag, missing plugin allowance, and recipe installer-path.
Without these, installs can fatally fail or place recipes under vendor.

-    if ($this->response == self::INSTALL_PROFILE_DRUPALCMS) {
-      $cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
+    if ($this->response === self::INSTALL_PROFILE_DRUPALCMS) {
+      $composerPath = $this->tmpDir . '/composer.json';
+      $cj = JsonManipulator::fromFile($composerPath);
+      if ($cj === NULL) {
+        throw new \RuntimeException(sprintf('composer.json not found or unreadable at %s', $composerPath));
+      }
@@
       $cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
       $cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
       $cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);
@@
       $cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
       $cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
+      $cj->addConfigSetting('allow-plugins.composer/installers', TRUE);
@@
       $cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
-      $cj->addProperty('extra.merge-plugin.merge-false', TRUE);
+      $cj->addProperty('extra.merge-plugin.merge-dev', TRUE);
       $cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
       $cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
       $cj->addProperty('extra.merge-plugin.recurse', TRUE);
       $cj->addProperty('extra.merge-plugin.replace', TRUE);
       $cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
+      $cj->addProperty('extra.installer-paths.web/recipes/{$name}', ['type:drupal-recipe']);
@@
-      $c = $cj->getContents();
-      file_put_contents($this->tmpDir . '/composer.json', $c);
+      $c = $cj->getContents();
+      if (file_put_contents($composerPath, $c) === FALSE) {
+        throw new \RuntimeException(sprintf('Failed to write composer.json to %s', $composerPath));
+      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fff572d and 866f52c.

⛔ Files ignored due to path filters (23)
  • .vortex/installer/tests/Fixtures/install/_baseline/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/tests/behat/features/search.feature is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_demo_db/.ignorecontent is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_cms_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_cms_profile/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/theme_absent/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_none/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (19)
  • .gitignore (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Internal.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Profile.php (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/Starter.php (6 hunks)
  • .vortex/installer/src/Prompts/Handlers/Webroot.php (1 hunks)
  • .vortex/installer/src/Prompts/PromptManager.php (3 hunks)
  • .vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (2 hunks)
  • .vortex/tests/bats/_helper.bash (1 hunks)
  • .vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (1 hunks)
  • .vortex/tests/test.workflow.sh (2 hunks)
  • composer.json (1 hunks)
  • phpmd.xml (1 hunks)
  • tests/behat/features/search.feature (1 hunks)
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml (1 hunks)
  • web/modules/custom/ys_search/config/install/views.view.search.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
composer.json

📄 CodeRabbit inference engine (CLAUDE.md)

When adding JavaScript or CSS libraries as Drupal libraries with Composer, define them as inline Composer packages in the 'repositories' section of composer.json

Files:

  • composer.json
tests/behat/**/*.feature

📄 CodeRabbit inference engine (CLAUDE.md)

tests/behat/**/*.feature: All Behat feature files must use the standard user story format: 'As a [user type]\nI want to [action]\nSo that [benefit]'
Use consistent user types in Behat feature files: 'site visitor', 'site administrator', and 'content editor'
Do not use commas or periods in user story statements within Behat feature files

Files:

  • tests/behat/features/search.feature
web/modules/custom/**

📄 CodeRabbit inference engine (CLAUDE.md)

Custom modules should be placed in 'web/modules/custom/'

Files:

  • web/modules/custom/ys_search/config/install/views.view.search.yml
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml
🧠 Learnings (1)
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.

Applied to files:

  • .vortex/tests/bats/_helper.bash
  • .vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats
  • .vortex/tests/test.workflow.sh
🧬 Code graph analysis (12)
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (5)
.vortex/installer/src/Prompts/Handlers/ProvisionType.php (1)
  • ProvisionType (11-103)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/PromptManager.php (2)
  • PromptManager (58-597)
  • makeEnvName (413-415)
.vortex/installer/src/Utils/Env.php (2)
  • Env (7-245)
  • put (24-26)
.vortex/installer/tests/Functional/FunctionalTestCase.php (1)
  • assertSutNotContains (139-154)
.vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/src/Prompts/Handlers/Internal.php (3)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (2)
.vortex/tests/bats/_helper.workflow.bash (6)
  • prepare_sut (7-39)
  • assert_ahoy_build (73-130)
  • assert_gitignore (132-162)
  • assert_ahoy_lint (413-425)
  • assert_ahoy_test (527-544)
  • assert_ahoy_fe (753-777)
.vortex/tests/bats/_helper.bash (1)
  • assert_webpage_contains (1022-1028)
.vortex/tests/test.workflow.sh (3)
.vortex/tests/test.common.sh (1)
  • bats (28-37)
.vortex/tests/test.deployment.sh (1)
  • bats (29-38)
.vortex/tests/test.postbuild.sh (1)
  • bats (20-29)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/src/Prompts/Handlers/Starter.php (2)
.vortex/installer/src/Utils/JsonManipulator.php (2)
  • JsonManipulator (10-76)
  • fromFile (19-36)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • AbstractHandler (10-233)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (4)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • Profile (10-165)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/installer/src/Prompts/Handlers/Profile.php (6)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (4)
  • resolvedValue (130-132)
  • id (52-62)
  • resolvedMessage (137-139)
  • getResponseAsString (191-197)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • resolvedValue (138-138)
  • id (19-19)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Webroot.php (4)
  • resolvedValue (98-110)
  • discover (65-79)
  • resolvedMessage (115-121)
  • process (126-146)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/ProfileCustom.php (1)
  • ProfileCustom (9-86)
.vortex/installer/src/Utils/File.php (2)
  • File (10-102)
  • replaceContentAsync (59-83)
.vortex/installer/src/Prompts/PromptManager.php (4)
.vortex/installer/src/Prompts/Handlers/Profile.php (3)
  • Profile (10-165)
  • resolvedValue (76-88)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
  • id (52-62)
  • resolvedValue (130-132)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
  • Webroot (12-148)
  • resolvedValue (98-110)
  • resolvedMessage (115-121)
.vortex/installer/src/Utils/Tui.php (1)
  • info (42-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: build (1)
  • GitHub Check: build (0)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.3)
  • GitHub Check: vortex-test-docs
🔇 Additional comments (16)
phpmd.xml (1)

17-18: Exclude recipes from PHPMD — LGTM.

Matches new installer-paths and avoids linting vendor-like recipe code.

composer.json (2)

133-139: Confirm intended recipes target path (root vs web/recipes).

Issue text says web/recipes/{$name}, code uses recipes/{$name}. Tests reference ../recipes/… from web/, which aligns with root-level recipes. Please confirm this is the desired contract.


73-91: If “wikimedia/composer-merge-plugin” is injected during install, remember allow-plugins.

Composer will require allow-plugins entry for the merge plugin at runtime. Verify installer updates config when adding the plugin.

.gitignore (1)

34-34: Comment tweak — OK as-is.

No functional change; wording is fine.

.vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1)

124-124: Defaults updated to new Starter key — consistent with handler.

Matches Starter::default() and downstream gating. No further action.

.vortex/installer/src/Prompts/Handlers/Webroot.php (1)

115-118: Signature migration to resolvedMessage(responses, resolved) looks correct.

Message uses the centrally resolved value; aligns with HandlerInterface/AbstractHandler changes.

.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)

7-7: No stale constants; options order and default selection verified.

.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1)

7-7: Drop unused ProvisionType import after fixing CoversClass.
[ suggest_nitpick ]

-use DrevOps\VortexInstaller\Prompts\Handlers\ProvisionType;
.vortex/installer/src/Prompts/PromptManager.php (2)

128-130: Good reuse of resolveOrPrompt for Profile.


153-154: Good reuse of resolveOrPrompt for Webroot.

.vortex/installer/src/Prompts/Handlers/Profile.php (2)

76-88: LGTM: resolves DrupalCMS profile to recipe path via constant.


90-99: LGTM: concise resolved message.

.vortex/installer/src/Prompts/Handlers/Starter.php (4)

7-7: LGTM: JsonManipulator import is correct.


12-17: LGTM: New constants read clearly and align with flows.


79-79: Default choice change acknowledged.
Defaulting to demo DB is fine if it matches e2e expectations.


98-98: LGTM: Symfony constraint widened to ^6.4 || ^7.0.
Matches modern stacks.

Comment on lines +127 to 129
if ($responses[Starter::id()] !== Starter::LOAD_DATABASE_DEMO) {
$is_demo = FALSE;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden access to Starter response to avoid undefined index.

In some non-interactive or partial-resolution paths, Starter::id() may be absent; use null coalescing.

Apply:

-      if ($responses[Starter::id()] !== Starter::LOAD_DATABASE_DEMO) {
+      if (($responses[Starter::id()] ?? null) !== Starter::LOAD_DATABASE_DEMO) {
         $is_demo = FALSE;
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ($responses[Starter::id()] !== Starter::LOAD_DATABASE_DEMO) {
$is_demo = FALSE;
}
if (($responses[Starter::id()] ?? null) !== Starter::LOAD_DATABASE_DEMO) {
$is_demo = FALSE;
}
🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/Internal.php around lines 127 to 129: the
code directly indexes $responses[Starter::id()] which can be undefined; change
the check to use a null-coalescing lookup (e.g. $responses[Starter::id()] ??
null) or assign a local variable with ($responses[Starter::id()] ?? null) and
compare that to Starter::LOAD_DATABASE_DEMO so the undefined index is avoided
and $is_demo is set correctly.


const LOAD_DATABASE_DEMO = 'load_demodb';

const INSTALL_PROFILE_DRUPALCMS_PATH = '../recipes/drupal_cms_starter';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Verify this constant is actually used; otherwise remove or use it in postInstall.
If unused, delete; if used, prefer referencing it when constructing DRUPAL_PROFILE.

Run to confirm:


🏁 Script executed:

#!/bin/bash
rg -nC2 '\bINSTALL_PROFILE_DRUPALCMS_PATH\b'

Length of output: 46


Remove unused INSTALL_PROFILE_DRUPALCMS_PATH constant
This constant is defined in .vortex/installer/src/Prompts/Handlers/Starter.php but never referenced—delete it.

🤖 Prompt for AI Agents
In .vortex/installer/src/Prompts/Handlers/Starter.php around line 18, the
constant INSTALL_PROFILE_DRUPALCMS_PATH is declared but never used; remove the
unused constant declaration from the file to eliminate dead code and unused
symbol warnings, and run tests/lint to confirm no references remain.

$output = '';

if ($this->response == self::DRUPAL_LOAD_DATABASE_DEMO) {
if ($this->response == self::LOAD_DATABASE_DEMO) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Use strict comparison.

-    if ($this->response == self::LOAD_DATABASE_DEMO) {
+    if ($this->response === self::LOAD_DATABASE_DEMO) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ($this->response == self::LOAD_DATABASE_DEMO) {
if ($this->response === self::LOAD_DATABASE_DEMO) {
🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/Starter.php around line 128: the
condition uses loose equality (==) when comparing $this->response to the
constant self::LOAD_DATABASE_DEMO; change it to a strict comparison (===) so the
types and values must match exactly (or cast $this->response to the expected
type before comparing) to prevent unintended truthy/loose-equality matches.

Comment on lines 5 to 14
namespace DrevOps\VortexInstaller\Tests\Functional\Handlers;

use DrevOps\VortexInstaller\Prompts\Handlers\ProvisionType;
use DrevOps\VortexInstaller\Prompts\Handlers\Starter;
use DrevOps\VortexInstaller\Prompts\PromptManager;
use DrevOps\VortexInstaller\Tests\Functional\FunctionalTestCase;
use DrevOps\VortexInstaller\Utils\Env;
use PHPUnit\Framework\Attributes\CoversClass;

#[CoversClass(ProvisionType::class)]
class StarterInstallTest extends AbstractInstallTestCase {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Import the correct base test class to avoid namespacing error.

AbstractInstallTestCase is in the parent namespace; without an import this will fatal.

 namespace DrevOps\VortexInstaller\Tests\Functional\Handlers;

+use DrevOps\VortexInstaller\Tests\Functional\AbstractInstallTestCase;
 use DrevOps\VortexInstaller\Prompts\Handlers\ProvisionType;
 use DrevOps\VortexInstaller\Prompts\Handlers\Starter;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
namespace DrevOps\VortexInstaller\Tests\Functional\Handlers;
use DrevOps\VortexInstaller\Prompts\Handlers\ProvisionType;
use DrevOps\VortexInstaller\Prompts\Handlers\Starter;
use DrevOps\VortexInstaller\Prompts\PromptManager;
use DrevOps\VortexInstaller\Tests\Functional\FunctionalTestCase;
use DrevOps\VortexInstaller\Utils\Env;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(ProvisionType::class)]
class StarterInstallTest extends AbstractInstallTestCase {
namespace DrevOps\VortexInstaller\Tests\Functional\Handlers;
use DrevOps\VortexInstaller\Tests\Functional\AbstractInstallTestCase;
use DrevOps\VortexInstaller\Prompts\Handlers\ProvisionType;
use DrevOps\VortexInstaller\Prompts\Handlers\Starter;
use DrevOps\VortexInstaller\Prompts\PromptManager;
use DrevOps\VortexInstaller\Tests\Functional\FunctionalTestCase;
use DrevOps\VortexInstaller\Utils\Env;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(ProvisionType::class)]
class StarterInstallTest extends AbstractInstallTestCase {
// …
}
🤖 Prompt for AI Agents
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php lines 5-15:
the class extends AbstractInstallTestCase but the file doesn't import that base
class from the parent namespace, causing a fatal namespacing error; add the
correct use statement (use
DrevOps\VortexInstaller\Tests\Functional\AbstractInstallTestCase;) near the
other use lines so the class can resolve the parent test class.

Comment on lines +15 to +17
And I add the "page" content with the title "[TEST] Test page uniquestring" to the search index
And I add the "page" content with the title "[TEST] Test page otheruniquestring" to the search index
And I add the "page" content with the title "[TEST] Test page thirduniquestring" to the search index
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Reduce duplication: use a single step to index created content (if available)

Keeps the scenario concise and less brittle.

-    And I add the "page" content with the title "[TEST] Test page uniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page otheruniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page thirduniquestring" to the search index
+    And I add the created items to the search index
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
And I add the "page" content with the title "[TEST] Test page uniquestring" to the search index
And I add the "page" content with the title "[TEST] Test page otheruniquestring" to the search index
And I add the "page" content with the title "[TEST] Test page thirduniquestring" to the search index
And I add the created items to the search index
🤖 Prompt for AI Agents
In tests/behat/features/search.feature around lines 15 to 17, there are three
nearly identical steps that add separate pages to the search index; replace
these with a single consolidated step that indexes multiple pages in one call to
reduce duplication. Change the three lines to one step that accepts multiple
titles (for example "And I add the following pages to the search index:"
followed by a data table of titles) or convert the scenario into a Scenario
Outline with an Examples table so the step definition can loop over each title
and call the existing index step once for each item; update or reuse the step
definition to iterate the table rows and call the indexing logic so behavior
stays the same but the feature is concise.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from 866f52c to 5be8d87 Compare September 1, 2025 22:40
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 22:42 Inactive
@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from 5be8d87 to a3328d6 Compare September 1, 2025 22:43
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 22:45 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (22)
tests/behat/features/search.feature (3)

15-17: Deduplicate indexing steps.

Replace the three identical “add … to the search index” steps with a single bulk step to reduce brittleness.

Apply:

-    And I add the "page" content with the title "[TEST] Test page uniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page otheruniquestring" to the search index
-    And I add the "page" content with the title "[TEST] Test page thirduniquestring" to the search index
+    And I add the created items to the search index

18-18: Avoid hardcoded count in indexing.

Derive the number from created items (or use an “all/created items” step) to prevent drift.

Apply:

-    And I run search indexing for 3 items
+    And I run search indexing for the created items

19-21: Remove fixed sleep; wait deterministically for index readiness.

Use a polling/wait step instead of “wait 5 seconds” to reduce flakiness.

Apply:

-    And I wait for 5 seconds
-    And I visit "/search"
+    And I wait for search indexing to complete
+    And I visit "/search"

Run to confirm supporting steps exist:

#!/bin/bash
rg -nC2 -P 'I wait for search indexing to complete|I run search indexing for the created items|I add the created items to the search index' -- tests behat
web/modules/custom/ys_search/config/install/search_api.index.content.yml (1)

61-69: Adding boolean status field — good; ensure deploy path + reindex.

Ship a config update/post-update to import this change and trigger a full reindex to avoid stale results on existing sites.

Suggestion (deploy ops):

  • drush cim -y
  • drush sapi-c
  • drush sapi-i -y content
web/modules/custom/ys_search/config/install/views.view.search.yml (1)

165-202: Trim redundant expose/group blocks for a non-exposed filter.

Since exposed: false, drop expose and group_info to reduce noise.

Apply:

         status:
           id: status
           table: search_api_index_content
           field: status
           relationship: none
           group_type: group
           admin_label: ''
           plugin_id: search_api_boolean
           operator: '='
           value: '1'
           group: 1
-          exposed: false
-          expose:
-            operator_id: ''
-            label: ''
-            description: ''
-            use_operator: false
-            operator: ''
-            operator_limit_selection: false
-            operator_list: {  }
-            identifier: ''
-            required: false
-            remember: false
-            multiple: false
-            remember_roles:
-              authenticated: authenticated
-          is_grouped: false
-          group_info:
-            label: ''
-            description: ''
-            identifier: ''
-            optional: true
-            widget: select
-            multiple: false
-            remember: false
-            default_group: All
-            default_group_multiple: {  }
-            group_items: {  }
+          exposed: false
.vortex/tests/bats/_helper.bash (1)

534-538: Align the comment with the actual assertions and drop the dead check.

We only assert removal of VORTEX_DEV guard markers, not all “#;” comments. Update the wording and remove the commented-out assertion to avoid confusion.

-  # Assert all special comments were removed.
-  # assert_dir_not_contains_string "${dir}" "#;"
+  # Assert VORTEX_DEV guard markers were removed (do not assert removal of all '#;' comments).
   assert_dir_not_contains_string "${dir}" "#;<"
   assert_dir_not_contains_string "${dir}" "#;>"
composer.json (1)

133-136: Add "drupal-recipe" to extra.installer-types in composer.json

Mapping recipes/{$name} → type:drupal-recipe will not be applied until "drupal-recipe" is declared.

         "installer-types": [
           "bower-asset",
           "npm-asset",
-          "drupal-library"
+          "drupal-library",
+          "drupal-recipe"
         ],

Confirm recipes are located at project root (recipes/{$name}); tests may expect ../recipes from web/ — verify.

.gitignore (1)

44-46: Keep the recipes directory while ignoring its contents.

Add an exception for a keep file so the empty directory can be committed when needed.

 # Ignore all recipes by default. Custom recipes should be added explicitly.
 recipes/*
+!recipes/.gitkeep
.vortex/installer/src/Prompts/Handlers/Internal.php (1)

127-131: Harden Starter lookup to avoid undefined index notices.

Use a null-coalescing read when $responses may not include Starter::id().

-      if ($responses[Starter::id()] !== Starter::LOAD_DATABASE_DEMO) {
+      if (($responses[Starter::id()] ?? null) !== Starter::LOAD_DATABASE_DEMO) {
         $is_demo = FALSE;
       }
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)

137-139: Add phpdoc for the new resolvedMessage signature.

Document $resolved and return type for IDEs/static analysis.

-  public function resolvedMessage(array $responses, mixed $resolved): ?string {
+  /**
+   * Message shown when a value was resolved without prompting.
+   *
+   * @param array $responses
+   *   All collected responses.
+   * @param mixed $resolved
+   *   The resolved value for this handler.
+   *
+   * @return string|null
+   *   Message to display, or NULL for none.
+   */
+  public function resolvedMessage(array $responses, mixed $resolved): ?string {
     return NULL;
   }
.vortex/tests/test.workflow.sh (1)

83-89: Swap default-case order to match case 4 and avoid hidden deps.

Run docker-compose before the Drupal CMS profile test.

     bats "${TEST_DIR}"/bats/e2e/workflow.install.profile.bats
-    bats "${TEST_DIR}"/bats/e2e/workflow.install.profile_drupalcms.bats
-    bats "${TEST_DIR}"/bats/e2e/workflow.docker-compose.bats
+    bats "${TEST_DIR}"/bats/e2e/workflow.docker-compose.bats
+    bats "${TEST_DIR}"/bats/e2e/workflow.install.profile_drupalcms.bats
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

141-146: Fix doc typos (“handlerManager” → “PromptManager”; “handlering” → “prompting”).
Small doc nits right above the changed block.

Apply:

-   * This is used by handlerManager to show an appropriate message (via
-   * info(), ok(), etc.) when using a resolved value instead of handlering
+   * This is used by PromptManager to show an appropriate message (via
+   * info(), ok(), etc.) when using a resolved value instead of prompting
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (2)

5-15: Missing import for the base class will fatal.
Add the parent test class import.

 use DrevOps\VortexInstaller\Prompts\Handlers\Starter;
 use DrevOps\VortexInstaller\Prompts\PromptManager;
 use DrevOps\VortexInstaller\Tests\Functional\FunctionalTestCase;
+use DrevOps\VortexInstaller\Tests\Functional\AbstractInstallTestCase;
 use DrevOps\VortexInstaller\Utils\Env;

26-33: Wrong assertion: these packages should be present, not absent. Also assert all composer mutations.
Flip to positive checks and validate merge-plugin config to catch regressions (e.g., key typos).

-      'starter, Drupal CMS profile' => [
-        static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::INSTALL_PROFILE_DRUPALCMS)),
-        static::cw(fn(FunctionalTestCase $test) => $test->assertSutNotContains([
-          'drupal/cms',
-          'wikimedia/composer-merge-plugin',
-          'vendor/drupal/cms/composer.json',
-        ])),
-      ],
+      'starter, Drupal CMS profile' => [
+        static::cw(fn() => Env::put(PromptManager::makeEnvName(Starter::id()), Starter::INSTALL_PROFILE_DRUPALCMS)),
+        static::cw(function (FunctionalTestCase $test): void {
+          $composer = json_decode(file_get_contents(static::$sut . '/composer.json'), true);
+          $require  = $composer['require'] ?? [];
+          $merge    = $composer['extra']['merge-plugin'] ?? [];
+
+          $test->assertArrayHasKey('drupal/cms', $require);
+          $test->assertArrayHasKey('wikimedia/composer-merge-plugin', $require);
+          $test->assertArrayHasKey('symfony/http-client', $require);
+          $test->assertContains('vendor/drupal/cms/composer.json', $merge['require'] ?? []);
+
+          // Ensure correct merge-plugin option name.
+          $test->assertArrayHasKey('merge-dev', $merge);
+          $test->assertArrayNotHasKey('merge-false', $merge);
+        }),
+      ],
.vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (2)

24-27: Also assert symfony/http-client to cover all composer edits.
Catches partial writes/regressions.

   assert_file_contains composer.json "drupal/cms"
   assert_file_contains composer.json "wikimedia/composer-merge-plugin"
   assert_file_contains composer.json "vendor/drupal/cms/composer.json"
+  assert_file_contains composer.json "symfony/http-client"

39-40: Add teardown to prevent env leakage across tests.
Keeps suite isolated.

   assert_webpage_contains "/" "This is the home page of your new site."
 }
+
+teardown() {
+  unset VORTEX_INSTALLER_PROMPT_STARTER VORTEX_INSTALLER_IS_DEMO_DB_DOWNLOAD_SKIP
+}
.vortex/installer/src/Prompts/PromptManager.php (1)

563-596: Defensively guard handler existence in resolveOrPrompt.
Prevents undefined-index fatals if a handler is missing.

   protected function resolveOrPrompt(string $handler_id, array $r, callable $prompt): string {
-    $handler = $this->handlers[$handler_id];
+    if (!array_key_exists($handler_id, $this->handlers)) {
+      throw new \RuntimeException(sprintf('Handler for "%s" not found.', $handler_id));
+    }
+    $handler = $this->handlers[$handler_id];
.vortex/installer/src/Prompts/Handlers/Profile.php (2)

145-146: Anchor DRUPAL_PROFILE replacement to the line (multiline).
Prevents accidental replacements elsewhere in the file.

-    File::replaceContentInFile($t . '/.env', '/DRUPAL_PROFILE=.*/', 'DRUPAL_PROFILE=' . $v);
+    File::replaceContentInFile($t . '/.env', '/^DRUPAL_PROFILE=.*/m', 'DRUPAL_PROFILE=' . $v);

147-149: Make contrib-path detection OS-agnostic.
Detect both '/' and '' to work on Windows paths too.

-    $is_contrib_profile = str_contains($v, DIRECTORY_SEPARATOR);
+    // Detect both Unix and Windows separators in provided path.
+    $is_contrib_profile = (strpbrk($v, '/\\') !== FALSE);
.vortex/installer/src/Prompts/Handlers/Starter.php (3)

128-140: Use strict comparisons in postInstall.
Prevents loose-equality surprises and reads cleaner.

-    if ($this->response == self::LOAD_DATABASE_DEMO) {
+    if ($this->response === self::LOAD_DATABASE_DEMO) {
@@
-    elseif ($this->response == self::INSTALL_PROFILE_CORE || $this->response == self::INSTALL_PROFILE_DRUPALCMS) {
+    elseif (in_array($this->response, [self::INSTALL_PROFILE_CORE, self::INSTALL_PROFILE_DRUPALCMS], TRUE)) {

Also applies to: 133-140


34-39: Align wording: use “recipe” for Drupal CMS.
Match the description and PR objective terminology.

-    $label2 = Tui::bold('Drupal CMS, installed from profile');
+    $label2 = Tui::bold('Drupal CMS, installed from recipe');
@@
-      self::INSTALL_PROFILE_DRUPALCMS => 'Drupal CMS, installed from profile',
+      self::INSTALL_PROFILE_DRUPALCMS => 'Drupal CMS, installed from recipe',

Also applies to: 69-72


93-115: Fix Composer mutations: strict compare, null-guard, installer path, plugin allowances, and merge option.
Without these, CMS install can fail or write invalid config.

-    if ($this->response == self::INSTALL_PROFILE_DRUPALCMS) {
-      $cj = JsonManipulator::fromFile($this->tmpDir . '/composer.json');
+    if ($this->response === self::INSTALL_PROFILE_DRUPALCMS) {
+      $composerPath = $this->tmpDir . '/composer.json';
+      $cj = JsonManipulator::fromFile($composerPath);
+      if ($cj === NULL) {
+        throw new \RuntimeException(sprintf('composer.json not found or unreadable at %s', $composerPath));
+      }
 
       $cj->addLink('require', 'drupal/cms', '^1.0', TRUE);
       $cj->addLink('require', 'wikimedia/composer-merge-plugin', '^2.1', TRUE);
       $cj->addLink('require', 'symfony/http-client', '^6.4 || ^7.0', TRUE);
 
       $cj->addConfigSetting('allow-plugins.drupal/core-project-message', TRUE);
       $cj->addConfigSetting('allow-plugins.wikimedia/composer-merge-plugin', TRUE);
+      $cj->addConfigSetting('allow-plugins.composer/installers', TRUE);
 
       $cj->addProperty('extra.merge-plugin.ignore-duplicates', FALSE);
-      $cj->addProperty('extra.merge-plugin.merge-false', TRUE);
+      $cj->addProperty('extra.merge-plugin.merge-dev', TRUE);
       $cj->addProperty('extra.merge-plugin.merge-extra', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-extra-deep', FALSE);
       $cj->addProperty('extra.merge-plugin.merge-replace', TRUE);
       $cj->addProperty('extra.merge-plugin.merge-scripts', FALSE);
       $cj->addProperty('extra.merge-plugin.recurse', TRUE);
       $cj->addProperty('extra.merge-plugin.replace', TRUE);
       $cj->addProperty('extra.merge-plugin.require', ['vendor/drupal/cms/composer.json']);
+      $cj->addProperty('extra.installer-paths.web/recipes/{$name}', ['type:drupal-recipe']);
 
-      $c = $cj->getContents();
-      file_put_contents($this->tmpDir . '/composer.json', $c);
+      $c = $cj->getContents();
+      if (file_put_contents($composerPath, $c) === FALSE) {
+        throw new \RuntimeException(sprintf('Failed to write composer.json to %s', $composerPath));
+      }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 866f52c and a3328d6.

⛔ Files ignored due to path filters (23)
  • .vortex/installer/tests/Fixtures/install/_baseline/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/tests/behat/features/search.feature is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/_baseline/web/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/docroot/modules/custom/sw_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/hosting_acquia/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/search_api.index.content.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/names/web/modules/custom/the_force_search/config/install/views.view.search.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_demo_db/.ignorecontent is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_cms_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_cms_profile/composer.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/starter_drupal_profile/.env is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/theme_absent/phpmd.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_groups_no_be_tests_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_no_phpunit_circleci/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/install/tools_none/.gitignore is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (21)
  • .github/workflows/build-test-deploy.yml (1 hunks)
  • .github/workflows/vortex-test-common.yml (2 hunks)
  • .gitignore (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Internal.php (1 hunks)
  • .vortex/installer/src/Prompts/Handlers/Profile.php (2 hunks)
  • .vortex/installer/src/Prompts/Handlers/Starter.php (6 hunks)
  • .vortex/installer/src/Prompts/Handlers/Webroot.php (1 hunks)
  • .vortex/installer/src/Prompts/PromptManager.php (3 hunks)
  • .vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1 hunks)
  • .vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (2 hunks)
  • .vortex/tests/bats/_helper.bash (1 hunks)
  • .vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (1 hunks)
  • .vortex/tests/test.workflow.sh (1 hunks)
  • composer.json (1 hunks)
  • phpmd.xml (1 hunks)
  • tests/behat/features/search.feature (1 hunks)
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml (1 hunks)
  • web/modules/custom/ys_search/config/install/views.view.search.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
composer.json

📄 CodeRabbit inference engine (CLAUDE.md)

When adding JavaScript or CSS libraries as Drupal libraries with Composer, define them as inline Composer packages in the 'repositories' section of composer.json

Files:

  • composer.json
web/modules/custom/**

📄 CodeRabbit inference engine (CLAUDE.md)

Custom modules should be placed in 'web/modules/custom/'

Files:

  • web/modules/custom/ys_search/config/install/views.view.search.yml
  • web/modules/custom/ys_search/config/install/search_api.index.content.yml
tests/behat/**/*.feature

📄 CodeRabbit inference engine (CLAUDE.md)

tests/behat/**/*.feature: All Behat feature files must use the standard user story format: 'As a [user type]\nI want to [action]\nSo that [benefit]'
Use consistent user types in Behat feature files: 'site visitor', 'site administrator', and 'content editor'
Do not use commas or periods in user story statements within Behat feature files

Files:

  • tests/behat/features/search.feature
🧠 Learnings (1)
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
PR: drevops/vortex#1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.

Applied to files:

  • .vortex/tests/bats/_helper.bash
  • .vortex/tests/test.workflow.sh
  • .vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats
🧬 Code graph analysis (12)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/Webroot.php (1)
  • resolvedMessage (115-121)
.vortex/installer/src/Prompts/Handlers/Profile.php (5)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (4)
  • resolvedValue (130-132)
  • id (52-62)
  • resolvedMessage (137-139)
  • getResponseAsString (191-197)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • resolvedValue (138-138)
  • id (19-19)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/ProfileCustom.php (1)
  • ProfileCustom (9-86)
.vortex/installer/src/Utils/File.php (2)
  • File (10-102)
  • replaceContentAsync (59-83)
.vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (3)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (4)
.vortex/installer/src/Prompts/Handlers/Profile.php (1)
  • Profile (10-165)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)
  • id (19-19)
.vortex/installer/tests/Functional/Handlers/StarterInstallTest.php (6)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/PromptManager.php (2)
  • PromptManager (58-597)
  • makeEnvName (413-415)
.vortex/installer/src/Utils/Env.php (2)
  • Env (7-245)
  • put (24-26)
.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (1)
  • CoversClass (12-47)
.vortex/installer/tests/Functional/Handlers/AbstractInstallTestCase.php (1)
  • AbstractInstallTestCase (19-101)
.vortex/installer/tests/Functional/FunctionalTestCase.php (1)
  • assertSutNotContains (139-154)
.vortex/installer/src/Prompts/Handlers/Internal.php (2)
.vortex/installer/src/Prompts/Handlers/Starter.php (1)
  • Starter (10-150)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • id (52-62)
.vortex/tests/test.workflow.sh (3)
.vortex/tests/test.common.sh (1)
  • bats (28-37)
.vortex/tests/test.deployment.sh (1)
  • bats (29-38)
.vortex/tests/test.postbuild.sh (1)
  • bats (20-29)
.vortex/installer/src/Prompts/Handlers/Starter.php (2)
.vortex/installer/src/Utils/JsonManipulator.php (2)
  • JsonManipulator (10-76)
  • fromFile (19-36)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (1)
  • AbstractHandler (10-233)
.vortex/tests/bats/e2e/workflow.install.profile_drupalcms.bats (2)
.vortex/tests/bats/_helper.workflow.bash (6)
  • prepare_sut (7-39)
  • assert_ahoy_build (73-130)
  • assert_gitignore (132-162)
  • assert_ahoy_lint (413-425)
  • assert_ahoy_test (527-544)
  • assert_ahoy_fe (753-777)
.vortex/tests/bats/_helper.bash (1)
  • assert_webpage_contains (1022-1028)
.vortex/installer/src/Prompts/PromptManager.php (5)
.vortex/installer/src/Prompts/Handlers/Profile.php (3)
  • Profile (10-165)
  • resolvedValue (76-88)
  • resolvedMessage (93-99)
.vortex/installer/src/Prompts/Handlers/AbstractHandler.php (3)
  • id (52-62)
  • resolvedValue (130-132)
  • resolvedMessage (137-139)
.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (3)
  • id (19-19)
  • resolvedValue (138-138)
  • resolvedMessage (155-155)
.vortex/installer/src/Prompts/Handlers/Webroot.php (3)
  • Webroot (12-148)
  • resolvedValue (98-110)
  • resolvedMessage (115-121)
.vortex/installer/src/Utils/Tui.php (1)
  • info (42-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: build (0)
  • GitHub Check: build (1)
  • GitHub Check: vortex-test-workflow (4)
  • GitHub Check: vortex-test-workflow (2)
  • GitHub Check: vortex-test-workflow (1)
  • GitHub Check: vortex-test-workflow (3)
  • GitHub Check: vortex-test-workflow (0)
  • GitHub Check: vortex-test-deployment (0)
  • GitHub Check: vortex-test-deployment (1)
  • GitHub Check: vortex-test-common
  • GitHub Check: vortex-test-installer (8.4)
  • GitHub Check: vortex-test-installer (8.2)
  • GitHub Check: vortex-test-installer (8.3)
🔇 Additional comments (18)
tests/behat/features/search.feature (3)

11-14: Fixtures updated for moderation — looks good.

Using both status and moderation_state makes intent clear and aligns with the new published-only filtering.


25-27: Assertions match published-only expectation.

Draft item correctly excluded; keep.


31-33: Scoped query checks are correct.

Positive/negative assertions look precise.

web/modules/custom/ys_search/config/install/views.view.search.yml (1)

210-210: Teaser view mode mapping — verify bundle displays.

Confirm all indexed bundles have a configured teaser matching prior output to avoid regressions.

.github/workflows/build-test-deploy.yml (1)

164-168: Fail-fast flips behavior; confirm impact on artifacts and diagnostics.

With fail-fast: true, the first failing matrix leg cancels the other. This can drop logs/artifacts from the cancelled leg(s) and shorten diagnostics. Verify this is intentional for build, especially around artifact upload (per-instance) and subsequent troubleshooting needs.

.github/workflows/vortex-test-common.yml (1)

116-120: Batch expanded to 5 and fail-fast enabled — validate test runner coverage.

Matrix now includes batch 4 and fail-fast: true. Ensure .vortex/tests/test.workflow.sh executes the new Drupal CMS batch and that cancellation won’t hide useful failures from other batches.

phpmd.xml (1)

17-18: Exclude recipes from PHPMD — sensible.

Skipping /recipes/ mirrors contrib exclusions and reduces noise from vendor-like recipe code.

.gitignore (1)

34-34: Wording change looks good.

The scaffold guidance comment is consistent with project style.

.vortex/installer/tests/Unit/Handlers/AbstractPromptManagerTestCase.php (1)

124-124: Default updated to LOAD_DATABASE_DEMO — aligned with Starter.

Matches the new constant and Starter::default().

.vortex/tests/test.workflow.sh (1)

71-73: New case for Drupal CMS e2e — OK.

Dedicated selector improves isolation.

.vortex/installer/src/Prompts/Handlers/HandlerInterface.php (1)

149-156: API change looks good; signature aligns with implementers.
The new $resolved parameter is clear and consistent with AbstractHandler/Profile/Webroot.

.vortex/installer/src/Prompts/Handlers/Webroot.php (1)

115-118: Resolved-message implementation is correct.
Accepts $resolved, guards type, and formats the message. Matches interface change.

.vortex/installer/tests/Unit/Handlers/StarterPromptManagerTest.php (3)

7-7: Import for Profile is correct.
Needed for referencing path constant in expectations.


21-21: Updated constants in expectations are correct.
LOAD_DATABASE_DEMO and INSTALL_PROFILE_CORE align with Starter::options/default.

Also applies to: 39-39


28-33: New Drupal CMS profile case looks right.
Covers resolved path propagation via Profile::id() => INSTALL_PROFILE_DRUPALCMS_PATH.

.vortex/installer/src/Prompts/PromptManager.php (2)

128-130: Good use of the new resolver for Profile.
Keeps the flow DRY and centralized.


153-154: Good reuse of resolver for Webroot.
Consistent with Profile handling.

.vortex/installer/src/Prompts/Handlers/Profile.php (1)

76-88: Resolution path looks correct.
Discovery wins; otherwise map Drupal CMS starter correctly via Starter constant.

Comment on lines +186 to 189
fail-fast: true
matrix:
batch: [0, 1]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Fail-fast for deployment tests — tradeoff check.

Ok if speed > parallel failure visibility. If you still want all batches’ logs even after one fails, keep fail-fast false here.

🤖 Prompt for AI Agents
.github/workflows/vortex-test-common.yml lines 186-189: the matrix step
currently sets fail-fast: true which aborts remaining batch jobs on the first
failure and prevents collecting logs from other batches; if you need logs and
results from all batches change fail-fast to false, commit that change, and
re-run the workflow to verify all batch jobs execute to completion so you can
inspect their logs even when one fails.

Comment on lines +93 to 99
public function resolvedMessage(array $responses, mixed $resolved): ?string {
if (is_string($resolved)) {
return sprintf('Profile will be set to "%s".', $resolved);
}

return NULL;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Avoid logging “custom” as a final profile value.
Skip the message when the resolved value is the sentinel “custom” to avoid misleading output.

   public function resolvedMessage(array $responses, mixed $resolved): ?string {
-    if (is_string($resolved)) {
+    if (is_string($resolved)) {
+      if ($resolved === self::CUSTOM) {
+        return NULL;
+      }
       return sprintf('Profile will be set to "%s".', $resolved);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function resolvedMessage(array $responses, mixed $resolved): ?string {
if (is_string($resolved)) {
return sprintf('Profile will be set to "%s".', $resolved);
}
return NULL;
}
public function resolvedMessage(array $responses, mixed $resolved): ?string {
if (is_string($resolved)) {
if ($resolved === self::CUSTOM) {
return NULL;
}
return sprintf('Profile will be set to "%s".', $resolved);
}
return NULL;
}
🤖 Prompt for AI Agents
.vortex/installer/src/Prompts/Handlers/Profile.php around lines 93 to 99: the
resolvedMessage currently returns a message for any string value, which can
incorrectly log the sentinel "custom"; update the logic to return NULL when the
resolved value is the string "custom" and only return the sprintf message for
other string values — i.e., check is_string($resolved) and if $resolved ===
'custom' return NULL, otherwise return the existing formatted message.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/1882-drupal-cms branch from a3328d6 to c90ee6a Compare September 1, 2025 23:06
@github-actions github-actions bot temporarily deployed to commit September 1, 2025 23:08 Inactive
@AlexSkrypnyk AlexSkrypnyk merged commit 5ac8b62 into develop Sep 1, 2025
31 checks passed
@AlexSkrypnyk AlexSkrypnyk deleted the feature/1882-drupal-cms branch September 1, 2025 23:52
@github-project-automation github-project-automation bot moved this from BACKLOG to Release queue in Vortex Sep 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Installing Drupal CMS with Vortex

2 participants