Skip to content
Snippets Groups Projects
Commit 2b6761a7 authored by Joe 🤘 Shindelar's avatar Joe 🤘 Shindelar
Browse files

Merge branch '0.x' into 3484893-e2ejs-docs

parents f9717f0e 445b8d3e
No related branches found
No related tags found
1 merge request!166[#3484893] Add some additional documentation to e2e.js.
Showing
with 191 additions and 31 deletions
......@@ -111,9 +111,13 @@ build project:
# ------
# ------ END UNPACKING ------
# ------
# Speed up the installer by pre-parsing all of the recipes available in the installer
# and storing them in a serialized file format.
- cd $BUILD_DIR
- cd $_WEB_ROOT && PATH="$PWD/vendor/bin:$PATH" ./profiles/drupal_cms_installer/build-cache.sh
# Turn the built project into an archive that can be downloaded by the public
# to spin up projects with Drupal CMS.
- composer archive --format=zip --file=drupal-cms --dir=$CI_PROJECT_DIR --working-dir=$BUILD_DIR
- cd - && composer archive --format=zip --file=drupal-cms --dir=$CI_PROJECT_DIR
artifacts:
paths:
- $BUILD_DIR
......
# Drupal CMS
**WARNING:** This repository is for Drupal CMS development only. Do not use it to build sites.
**WARNING:** This repository is only for contributing to Drupal CMS. Do not use it to build sites.
If you want to contribute, or test Drupal CMS, consult the [Contributing to Drupal CMS](https://git.drupalcode.org/project/drupal_cms/-/wikis/Contributing-to-Drupal-CMS) wiki page to get up and running.
\ No newline at end of file
If you want to give Drupal CMS a try, don't use this repository. Instead, do the following:
1. Install [DDEV](https://ddev.com/) according to [its documentation](https://ddev.readthedocs.io/en/stable/).
2. Download and unzip [Drupal CMS's current development snapshot](https://git.drupalcode.org/api/v4/projects/157093/jobs/artifacts/0.x/raw/drupal-cms.zip?job=build+project).
3. In the unzipped directory, run `ddev launch` at the command line.
That's it! DDEV will automatically configure everything and open Drupal CMS in your default browser.
If you want to contribute to Drupal CMS, consult the [Contributing to Drupal CMS](https://git.drupalcode.org/project/drupal_cms/-/wikis/Contributing-to-Drupal-CMS) wiki page to get up and running.
......@@ -4,7 +4,7 @@
"Do NOT use it to build a real site!"
],
"require-dev": {
"drupal/core-dev": "^11.1",
"drupal/core-dev": "^11.1-rc1",
"drupal/default_content": "^2"
},
"config": {
......
......@@ -17,9 +17,9 @@
"require": {
"composer/installers": "^2.3",
"cweagans/composer-patches": "^2",
"drupal/core-composer-scaffold": "^11.1",
"drupal/core-project-message": "^11.1",
"drupal/core-recommended": "^11.1",
"drupal/core-composer-scaffold": "^11.1-rc1",
"drupal/core-project-message": "^11.1-rc1",
"drupal/core-recommended": "^11.1-rc1",
"drupal/drupal_cms_starter": "*",
"drupal/drupal_cms_accessibility_tools": "*",
"drupal/drupal_cms_blog": "*",
......
/cache
#!/usr/bin/env sh
# Boosts the installer's performance by caching the recipes available to it.
# This script is intended for developers, only works on Linux and macOS, and
# requires PHP to support SQLite. Expects to be run in the web root and Drush
# to be present in $PATH.
# Abort this script if any one step fails.
set -e
if [ ! -f index.php ]; then
echo "This script must be run from the web root."
exit 1
fi
# Create a backup of `sites.php`. If it doesn't exist, no worries.
cp -f sites/sites.php sites-backup.php | true
# Install Drupal CMS, and all of the recipes available to the installer, writing them
# to the cache.
DRUPAL_CMS_INSTALLER_WRITE_CACHE=1 drush site:install --yes --uri=installer-cache --db-url=sqlite://localhost/installer-cache.sqlite drupal_cms_installer 'drupal_cms_installer_recipes_form.add_ons=*'
cd sites
# Delete the temporary site directory.
chmod -R +w installer-cache
rm -r -f installer-cache
# `sites.php` might have been altered, so remove it and restore the backup copy.
# If the backup copy doesn't exist, assume it was never created because there was
# no `sites.php` to begin with.
rm -f sites.php
cd ..
mv -f sites-backup.php sites | true
# Delete the SQLite database. We need to do this with `find` to work around a Drupal
# core bug that can cause the database file to live in unexpected locations.
find . -type f -name installer-cache.sqlite -delete
......@@ -12,6 +12,7 @@ use Drupal\Core\Recipe\RecipeRunner;
use Drupal\drupal_cms_installer\Form\RecipesForm;
use Drupal\drupal_cms_installer\Form\SiteNameForm;
use Drupal\drupal_cms_installer\MessageInterceptor;
use Drupal\drupal_cms_installer\RecipeLoader;
const SQLITE_DRIVER = 'Drupal\sqlite\Driver\Database\sqlite';
......@@ -119,7 +120,13 @@ function drupal_cms_installer_apply_recipes(array &$install_state): array {
$cookbook_path .= '/recipes';
foreach ($install_state['parameters']['recipes'] as $recipe) {
$recipe = Recipe::createFromDirectory($cookbook_path . '/' . $recipe);
$recipe = RecipeLoader::load(
$cookbook_path . '/' . $recipe,
// Only save a cached copy of the recipe if this environment variable is
// set. This allows us to ship a pre-primed cache of recipes to improve
// installer performance for first-time users.
(bool) getenv('DRUPAL_CMS_INSTALLER_WRITE_CACHE'),
);
foreach (RecipeRunner::toBatchOperations($recipe) as $operation) {
$batch['operations'][] = $operation;
......
......@@ -5,6 +5,7 @@ namespace Drupal\drupal_cms_installer\Form;
use Composer\InstalledVersions;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Checkboxes;
/**
* Provides a form to choose the site template and optional add-on recipes.
......@@ -37,8 +38,7 @@ final class RecipesForm extends InstallerFormBase {
'#prefix' => '<div class="cms-installer__form-group">',
'#suffix' => '</div>',
'#type' => 'checkboxes',
'#options' => [],
'#default_value' => [],
'#value_callback' => static::class . '::valueCallback',
];
// @todo Remove this try-catch wrapper when all our components are published
......@@ -72,10 +72,12 @@ final class RecipesForm extends InstallerFormBase {
'#type' => 'submit',
'#value' => $this->t('Next'),
'#button_type' => 'primary',
'#op' => 'submit',
],
'skip' => [
'#type' => 'submit',
'#value' => $this->t('Skip this step'),
'#op' => 'skip',
],
'#type' => 'actions',
];
......@@ -90,12 +92,26 @@ final class RecipesForm extends InstallerFormBase {
$install_state['parameters']['recipes'] = ['drupal_cms_starter'];
$pressed_button = $form_state->getTriggeringElement();
// Only choose add-ons if the Next button was pressed.
if ($pressed_button && end($pressed_button['#array_parents']) === 'submit') {
// Only choose add-ons if the Next button was pressed, or if the form was
// submitted programmatically (i.e., by `drush site:install`).
if (($pressed_button && $pressed_button['#op'] === 'submit') || $form_state->isProgrammed()) {
$add_ons = $form_state->getValue('add_ons', []);
$add_ons = array_filter($add_ons);
array_push($install_state['parameters']['recipes'], ...array_values($add_ons));
}
}
public static function valueCallback(&$element, $input, FormStateInterface $form_state): array {
// If the input was a comma-separated string or `*`, transform it -- this is
// for compatibility with `drush site:install`.
if (is_string($input)) {
$selections = $input === '*'
? array_keys($element['#options'])
: array_map('trim', explode(',', $input));
$input = array_combine($selections, $selections);
}
return Checkboxes::valueCallback($element, $input, $form_state);
}
}
<?php
declare(strict_types=1);
namespace Drupal\drupal_cms_installer;
use Composer\InstalledVersions;
use Drupal\Core\Recipe\Recipe;
use Symfony\Component\Filesystem\Filesystem;
/**
* Allows recipes to be written to, and loaded from, serialized cache files.
*
* Because the Drupal CMS installer is only meant to be used once, this class
* implements a write-once, read-once (flash bag) caching strategy. If a recipe
* is loaded from cache, it is immediately invalidated.
*/
final class RecipeLoader {
/**
* Loads a recipe.
*
* This will ALWAYS try to read the recipe from the cache. If there is a cache
* hit, the cached recipe is returned, and the cached data is cleared. If
* there is a cache miss, the recipe will be loaded normally using
* `\Drupal\Core\Recipe\Recipe::createFromDirectory()`. The loaded recipe is
* only written to the cache if $write_cache is TRUE.
*
* @param string $path
* The path of the recipe to load.
* @param bool $write_cache
* (optional) Whether to cache the loaded recipe. Defaults to FALSE.
*
* @return \Drupal\Core\Recipe\Recipe
*/
public static function load(string $path, bool $write_cache = FALSE): Recipe {
$cache_dir = __DIR__ . '/../cache';
$cache_file = $cache_dir . '/' . basename($path);
$file_system = new Filesystem();
['install_path' => $project_root] = InstalledVersions::getRootPackage();
$recipe = NULL;
if (file_exists($cache_file)) {
// The recipe is cached as a single giant serialized object.
$data = file_get_contents($cache_file);
$matches = [];
// Scan the data for serialized strings that start with `%PROJECT_ROOT%`,
// which is an arbitrary placeholder that we use when we write a recipe to
// the cache (see the $write_cache logic below).
preg_match_all('/s:[0-9]+:"(%PROJECT_ROOT%[^\"]+)";/', $data, $matches);
$matches = array_map('array_unique', $matches);
assert(count($matches) === 2);
assert(count($matches[0]) === count($matches[1]));
// Replace the `%PROJECT_ROOT%` placeholder with the actual project root,
// and re-serialize the modified string so that will have the correct
// length (and therefore be readable by unserialize()).
$matches[1] = str_replace('%PROJECT_ROOT%', realpath($project_root), $matches[1]);
$matches[1] = array_map('serialize', $matches[1]);
// Replace the serialized strings that have the `%PROJECT_ROOT%`
// placeholder with serialized versions that have the placeholder
// replaced.
$data = str_replace($matches[0], $matches[1], $data);
// If unserialization fails, $recipe will be FALSE (which is covered by
// the empty() check below).
$recipe = unserialize($data);
// Immediately invalidate the cache file so that the cache acts like a
// flash bag (write once, read once). We assume that we are running as
// the owner of the cache directory and everything in it.
$file_system->chmod([$cache_dir, $cache_file], 0755);
$file_system->remove($cache_file);
}
// We couldn't load the recipe from cache, so load it normally.
if (empty($recipe)) {
$recipe = Recipe::createFromDirectory($path);
}
// Cache the recipe if requested.
if ($write_cache) {
$file_system->mkdir($cache_dir);
// Serialize the loaded recipe and write it to the cache, replacing the
// path to the project root (which can appear in various forms) with the
// arbitrary `%PROJECT_ROOT%` placeholder (see the loading logic above).
// This is necessary because Recipe objects contain many immutable strings
// with absolute path references, which makes them non-portable. We would
// not need to do this if the recipe system _only_ used relative paths.
file_put_contents($cache_file, str_replace(
[
$project_root,
realpath($project_root),
],
'%PROJECT_ROOT%',
serialize($recipe),
));
}
return $recipe;
}
}
......@@ -10,12 +10,5 @@
"drupal/gin": "^3-rc11",
"drupal/gin_toolbar": "^1-rc6",
"drupal/sam": "^1.2"
},
"extra": {
"patches": {
"drupal/core": {
"#3395527: Integrate Dashboard with Navigation": "https://www.drupal.org/files/issues/2024-11-14/navigation-dashboard-3395527-11.x.patch"
}
}
}
}
......@@ -7,7 +7,7 @@
"require": {
"drupal/automatic_updates": "^3.1.6",
"drupal/core": ">=10.3",
"drupal/dashboard": "^2",
"drupal/dashboard": "^2-alpha2",
"drupal/drupal_cms_admin_ui": "*",
"drupal/drupal_cms_anti_spam": "*",
"drupal/drupal_cms_authentication": "*",
......
{
"name": "drupal/drupal_cms_trial",
"version": "dev-main",
"type": "drupal-custom-module",
"description": "Provides support code for the trial of Drupal CMS. Not meant for production use."
}
name: drupal_cms_trial
type: module
description: 'Provides support code for the in-browser trial of Drupal CMS. Not meant for production use.'
core_version_requirement: '*'
hidden: true
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment