diff --git a/src/Commands/StarterKitCommand.php b/src/Commands/StarterKitCommand.php
new file mode 100644
index 00000000..aec6b8a9
--- /dev/null
+++ b/src/Commands/StarterKitCommand.php
@@ -0,0 +1,268 @@
+acquiaCmsCli = $cli;
+ $this->installTask = $installTask;
+ $this->installerQuestions = $installerQuestions;
+ parent::__construct();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure(): void {
+ $this->setName("starterkit")
+ ->setDescription("Use this command to setup & install a site.")
+ ->setDefinition([
+ new InputArgument('name', NULL, "Name of the starter kit"),
+ new InputOption('uri', 'l', InputOption::VALUE_OPTIONAL, "Multisite uri to setup drupal site."),
+ new InputOption('no-install', NULL, InputOption::VALUE_NONE, "Omit Drupal install or database drop operations. Will use an existing database."),
+ new InputOption('no-composer', NULL, InputOption::VALUE_NONE, "Omit composer operations. Will fail if codebase dependencies are missing."),
+ ])
+ ->setHelp("The starterkit command downloads & setup Drupal site based on user selected use case.");
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ try {
+ $name = $input->getArgument('name');
+ $args = [];
+ if ($name) {
+ $this->validationOptions($name);
+ $this->acquiaCmsCli->printLogo();
+ $this->acquiaCmsCli->printHeadline();
+ }
+ else {
+ $this->acquiaCmsCli->printLogo();
+ $this->acquiaCmsCli->printHeadline();
+ $name = $this->askBundleQuestion($input, $output);
+ }
+ $args['keys'] = $this->askKeysQuestions($input, $output, $name);
+ $this->installTask->configure($input, $output, $name);
+
+ $args['drush_install'] = !$input->getOption('no-install');
+ $args['use_composer'] = !$input->getOption('no-composer');
+
+ $this->installTask->run($args);
+ $this->postSiteInstall($name, $output);
+ }
+ catch (AcmsCliException $e) {
+ $output->writeln("" . $e->getMessage() . "");
+ return StatusCodes::ERROR;
+ }
+ return StatusCodes::OK;
+ }
+
+ /**
+ * Validate all input options/arguments.
+ *
+ * @param string $name
+ * A name of the user selected use-case.
+ */
+ protected function validationOptions(string $name): bool {
+ $starterKits = array_keys($this->acquiaCmsCli->getStarterKits());
+ if (!in_array($name, $starterKits)) {
+ throw new InvalidArgumentException("Invalid starter kit. It should be from one of the following: " . implode(", ", $starterKits) . ".");
+ }
+ return TRUE;
+ }
+
+ /**
+ * Providing input to user, asking to select the starter-kit.
+ */
+ protected function askBundleQuestion(InputInterface $input, OutputInterface $output): string {
+ $helper = $this->getHelper('question');
+ $bundles = array_keys($this->acquiaCmsCli->getStarterKits());
+ $this->renderStarterKits($output);
+ $starterKit = "acquia_cms_enterprise_low_code";
+ $question = new Question($this->styleQuestion("Please choose bundle from one of the above use case", $starterKit), $starterKit);
+ $question->setAutocompleterValues($bundles);
+ $question->setValidator(function ($answer) use ($bundles) {
+ if (!is_string($answer) || !in_array($answer, $bundles)) {
+ throw new \RuntimeException(
+ "Please choose from one of the use case defined above. Ex: acquia_cms_enterprise_low_code."
+ );
+ }
+ return $answer;
+ });
+ $question->setMaxAttempts(3);
+ return $helper->ask($input, $output, $question);
+ }
+
+ /**
+ * Providing input to user, asking to provide key.
+ */
+ protected function askKeysQuestions(InputInterface $input, OutputInterface $output, string $bundle): array {
+ // The questions defined in acms.yml file.
+ $questions = $this->installerQuestions->getQuestions($this->acquiaCmsCli->getInstallerQuestions(), $bundle);
+ // Get all questions for user selected use-case.
+ $processedQuestions = $this->installerQuestions->process(array_merge($questions['questionMustAsk'], $questions['questionSkipped']));
+
+ // Initialize the value with default answer for question, so that
+ // if any question is dependent on other question which is skipped,
+ // we can use the value for that question to make sure the cli
+ // doesn't throw following RunTime exception:"Not able to resolve variable".
+ // @see AcquiaCMS\Cli\Helpers::shouldAskQuestion().
+ $userInputValues = $processedQuestions['default'];
+ if (isset($processedQuestions['questionToAsk'])) {
+ foreach ($processedQuestions['questionToAsk'] as $key => $question) {
+ $userInputValues[$key] = $this->askQuestion($question, $key, $input, $output);
+ }
+ foreach ($questions['questionCanAsk'] as $key => $question) {
+ if ($this->installerQuestions->shouldAskQuestion($question, $userInputValues)) {
+ $userInputValues[$key] = $this->askQuestion($question, $key, $input, $output);
+ }
+ }
+ }
+ return array_merge($processedQuestions['default'], $userInputValues);
+ }
+
+ /**
+ * Renders the table showing list of all starter kits.
+ */
+ protected function renderStarterKits(OutputInterface $output): void {
+ $table = new Table($output);
+ $table->setHeaders(['ID', 'Name', 'Description']);
+ $starter_kits = $this->acquiaCmsCli->getStarterKits();
+ $total = count($starter_kits);
+ $key = 0;
+ foreach ($starter_kits as $id => $starter_kit) {
+ $useCases[$id] = $starter_kit;
+ $table->addRow([$id, $starter_kit['name'], $starter_kit['description']]);
+ if ($key + 1 != $total) {
+ $table->addRow(["", "", ""]);
+ }
+ $key++;
+ }
+ $table->setColumnMaxWidth(2, 81);
+ $table->setStyle('box');
+ $table->render();
+ }
+
+ /**
+ * Show successful message post site installation.
+ *
+ * @param string $bundle
+ * User selected starter-kit.
+ * @param \Symfony\Component\Console\Output\OutputInterface $output
+ * A Symfony console output object.
+ */
+ protected function postSiteInstall(string $bundle, OutputInterface $output): void {
+ $output->writeln("");
+ $formatter = $this->getHelper('formatter');
+ $infoMessage = "[OK] Thank you for choosing Acquia CMS. We've successfully setup your project using bundle: `$bundle`.";
+ $formattedInfoBlock = $formatter->formatBlock($infoMessage, 'fg=black;bg=green', TRUE);
+ $output->writeln($formattedInfoBlock);
+ $output->writeln("");
+ }
+
+ /**
+ * Function to ask question to user.
+ *
+ * @param array $question
+ * An array of question.
+ * @param string $key
+ * A unique key for question.
+ * @param \Symfony\Component\Console\Input\InputInterface $input
+ * A Console input interface object.
+ * @param \Symfony\Component\Console\Output\OutputInterface $output
+ * A Console output interface object.
+ */
+ public function askQuestion(array $question, string $key, InputInterface $input, OutputInterface $output): string {
+ $helper = $this->getHelper('question');
+ $isRequired = $question['required'] ?? FALSE;
+ $defaultValue = $this->installerQuestions->getDefaultValue($question, $key);
+ $skipOnValue = $question['skip_on_value'] ?? TRUE;
+ if ($skipOnValue && $defaultValue) {
+ return $defaultValue;
+ }
+ $askQuestion = new Question($this->styleQuestion($question['question'], $defaultValue, $isRequired, TRUE));
+ $askQuestion->setValidator(function ($answer) use ($question, $key, $isRequired, $output, $defaultValue) {
+ if (!is_string($answer) && !$defaultValue) {
+ if ($isRequired) {
+ throw new \RuntimeException(
+ "The `" . $key . "` cannot be left empty."
+ );
+ }
+ else {
+ if (isset($question['warning'])) {
+ $warning = str_replace(PHP_EOL, PHP_EOL . " ", $question['warning']);
+ $output->writeln($this->style(" " . $warning, 'warning', FALSE));
+ }
+ }
+ }
+ if ($answer && isset($question['allowed_values']['options']) && !in_array($answer, $question['allowed_values']['options'])) {
+ throw new \RuntimeException(
+ "Invalid value. It should be from one of the following: " . implode(", ", $question['allowed_values']['options'])
+ );
+ }
+ return $answer ?: $defaultValue;
+ });
+ $askQuestion->setMaxAttempts(3);
+ if (isset($question['allowed_values']['options'])) {
+ $askQuestion->setAutocompleterValues($question['allowed_values']['options']);
+ }
+ $response = $helper->ask($input, $output, $askQuestion);
+ return ($response === NULL) ? $defaultValue : $response;
+ }
+
+}
diff --git a/src/Helpers/Task/InstallTask.php b/src/Helpers/Task/InstallTask.php
index 64bea97b..3d7034d1 100644
--- a/src/Helpers/Task/InstallTask.php
+++ b/src/Helpers/Task/InstallTask.php
@@ -22,7 +22,6 @@
* Executes the task needed to run site:install command.
*/
class InstallTask {
-
use StatusMessageTrait;
/**
@@ -162,7 +161,7 @@ public function __construct(Cli $cli, ContainerInterface $container) {
* @poram Symfony\Component\Console\Command\Command $output
* The site:install Symfony console command object.
*/
- public function configure(InputInterface $input, OutputInterface $output, string $bundle) :void {
+ public function configure(InputInterface $input, OutputInterface $output, string $bundle): void {
$this->bundle = $bundle;
$this->input = $input;
$this->output = $output;
@@ -174,28 +173,48 @@ public function configure(InputInterface $input, OutputInterface $output, string
* @param array $args
* An array of params argument to pass.
*/
- public function run(array $args) :void {
+ public function run(array $args): void {
$installedDrupalVersion = $this->validateDrupal->execute();
- if (!$installedDrupalVersion) {
+ if (!$this->validateDrupal->isInComposer()) {
$this->print("Looks like, current project is not a Drupal project:", 'warning');
$this->print("Converting the current project to Drupal project:", 'headline');
$this->downloadDrupal->execute();
}
else {
- $this->print("Seems Drupal is already downloaded. " .
+ $this->print(
+ "Seems Drupal is already downloaded. " .
"The downloaded Drupal core version is: $installedDrupalVersion. " .
- "Skipping downloading Drupal.", 'success'
- );
+ "Skipping downloading Drupal.",
+ 'success'
+ );
}
- $this->print("Downloading all packages/modules/themes required by the starter-kit:", 'headline');
- $this->acquiaCmsCli->alterModulesAndThemes($this->starterKits[$this->bundle], $args['keys']);
- $this->downloadModules->execute($this->starterKits[$this->bundle]);
- $this->print("Installing Site:", 'headline');
- $this->siteInstall->execute([
- 'no-interaction' => $this->input->getOption('no-interaction'),
- 'name' => $this->starterKits[$this->bundle]['name'],
- ]);
+ if (!$this->input->hasOption('no-composer') || !$this->input->getOption('no-composer')) {
+ $this->print("Downloading all packages/modules/themes required by the starter-kit:", 'headline');
+ $this->acquiaCmsCli->alterModulesAndThemes($this->starterKits[$this->bundle], $args['keys']);
+ $this->downloadModules->execute($this->starterKits[$this->bundle]);
+ }
+ else {
+ $this->print("Omitting composer dependency management. Drupal may not have all required dependencies.", 'warning');
+ }
+
+ if (!$this->input->hasOption('no-install') || !$this->input->getOption('no-install')) {
+ $this->print("Installing Site:", 'headline');
+ $this->siteInstall->execute([
+ 'no-interaction' => $this->input->getOption('no-interaction'),
+ 'name' => $this->starterKits[$this->bundle]['name'],
+ ]);
+ }
+ else {
+ $this->print("Omiting Drupal install.", 'notice');
+ }
+
+ // Revalidate Drupal is installed.
+ $this->validateDrupal->execute();
+ if (!$this->validateDrupal->isInstalled()) {
+ $this->print("Drupal is not installed. Discontinuing starter kit initialization.", 'warning');
+ return;
+ }
$bundle_modules = $this->starterKits[$this->bundle]['modules']['install'] ?? [];
$modules_list = JsonParser::installPackages($bundle_modules);
@@ -235,11 +254,12 @@ public function run(array $args) :void {
]);
}
else {
- $this->print("Skipped importing Site Studio Packages." .
+ $this->print(
+ "Skipped importing Site Studio Packages." .
PHP_EOL .
"You can set the key later from: /admin/cohesion/configuration/account-settings & import Site Studio packages.",
- "warning",
- );
+ "warning",
+ );
}
}
@@ -279,7 +299,7 @@ public function run(array $args) :void {
* @param string $type
* Type of styling the message.
*/
- protected function print(string $message, string $type) :void {
+ protected function print(string $message, string $type): void {
$this->output->writeln($this->style($message, $type));
}
diff --git a/src/Helpers/Task/Steps/ValidateDrupal.php b/src/Helpers/Task/Steps/ValidateDrupal.php
index 05f82072..ebc1edb5 100644
--- a/src/Helpers/Task/Steps/ValidateDrupal.php
+++ b/src/Helpers/Task/Steps/ValidateDrupal.php
@@ -3,6 +3,7 @@
namespace AcquiaCMS\Cli\Helpers\Task\Steps;
use AcquiaCMS\Cli\Helpers\Process\Commands\Composer;
+use AcquiaCMS\Cli\Helpers\Process\Commands\Drush;
/**
* Provides the class to validate if current project is Drupal project.
@@ -14,16 +15,40 @@ class ValidateDrupal {
*
* @var \AcquiaCMS\Cli\Helpers\Process\Commands\Composer
*/
- protected $composerCommand;
+ protected Composer $composerCommand;
+
+ /**
+ * Flag indicating if Drupal is in composer.lock.
+ *
+ * @var bool
+ */
+ protected $isInComposer = FALSE;
+
+ /**
+ * A drush command object.
+ *
+ * @var \AcquiaCMS\Cli\Helpers\Process\Commands\Drush
+ */
+ protected Drush $drushCommand;
+
+ /**
+ * Flag indicating if Drupal is installed.
+ *
+ * @var bool
+ */
+ protected $isInstalled = FALSE;
/**
* Constructs an object.
*
* @param \AcquiaCMS\Cli\Helpers\Process\Commands\Composer $composer
* Holds the composer command class object.
+ * @param \AcquiaCMS\Cli\Helpers\Process\Commands\Drush $drush
+ * Holds the drush command class object.
*/
- public function __construct(Composer $composer) {
+ public function __construct(Composer $composer, Drush $drush) {
$this->composerCommand = $composer;
+ $this->drushCommand = $drush;
}
/**
@@ -41,9 +66,35 @@ public function execute(array $args = []) :string {
$version = '';
$json_output = json_decode($output);
if (json_last_error() === JSON_ERROR_NONE) {
+ $this->isInComposer = TRUE;
$version = implode(', ', $json_output->versions);
}
+
+ $statusCommand = [
+ "status",
+ "--format=json",
+ ];
+ $status_information = $this->drushCommand->prepare($statusCommand)->runQuietly([], FALSE);
+ $json_output = json_decode($status_information, TRUE);
+ if (json_last_error() === JSON_ERROR_NONE) {
+ $this->isInstalled = isset($json_output['bootstrap']) && ($json_output['bootstrap'] == 'Successful');
+ }
+
return $version;
}
+ /**
+ * Indicate if Drupal is available in composer.
+ */
+ public function isInComposer(): bool {
+ return $this->isInComposer;
+ }
+
+ /**
+ * Indicate if Drupal is installed and bootstrapped.
+ */
+ public function isInstalled(): bool {
+ return $this->isInstalled;
+ }
+
}