Drupal Composer Scaffold - A flexible Composer project scaffold builder

Overview

Drupal Composer Scaffold

This project provides a composer plugin for placing scaffold files (like index.php, update.php, …) from the drupal/core project into their desired location inside the web root. Only individual files may be scaffolded with this plugin.

The purpose of scaffolding files is to allow Drupal sites to be fully managed by Composer, and still allow individual asset files to be placed in arbitrary locations. The goal of doing this is to enable a properly configured composer template to produce a file layout that exactly matches the file layout of a Drupal 8.7.x and earlier tarball distribution. Other file layouts will also be possible; for example, a project layout very similar to the current drupal-composer/drupal-project template will also be provided. When one of these projects is used, the user should be able to use composer require and composer update on a Drupal site immediately after untarring the downloaded archive.

Note that the dependencies of a Drupal site are only able to scaffold files if explicitly granted that right in the top-level composer.json file. See allowed packages, below.

Usage

Drupal Composer Scaffold is used by requiring drupal/core-composer-scaffold in your project, and providing configuration settings in the extra section of your project's composer.json file. Additional configuration from the composer.json file of your project's dependencies is also consulted in order to scaffold the files a project needs. Additional information may be added to the beginning or end of scaffold files, as is commonly done to .htaccess and robots.txt files. See altering scaffold files for more information.

Typically, the scaffold operations run automatically as needed, e.g. after composer install, so it is usually not necessary to do anything different to scaffold a project once the configuration is set up in the project composer.json file, as described below. To scaffold files directly, run:

composer drupal:scaffold

Allowed Packages

Scaffold files are stored inside of projects that are required from the main project's composer.json file as usual. The scaffolding operation happens after composer install, and involves copying or symlinking the desired assets to their destination location. In order to prevent arbitrary dependencies from copying files via the scaffold mechanism, only those projects that are specifically permitted by the top-level project will be used to scaffold files.

Example: Permit scaffolding from the project drupal/core

  "name": "my/project",
  ...
  "extra": {
    "drupal-scaffold": {
      "allowed-packages": [
        "drupal/core"
      ],
      ...
    }
  }

Allowing a package to scaffold files also permits it to delegate permission to scaffold to any project that it requires itself. This allows a package to organize its scaffold assets as it sees fit. For example, the project drupal/core may choose to store its assets in a subproject drupal/assets.

It is possible for a project to obtain scaffold files from multiple projects. For example, a Drupal project using a distribution, and installing on a specific web hosting service provider might take its scaffold files from:

  • Drupal core
  • Its distribution
  • A project provided by the hosting provider
  • The project itself

Each project allowed to scaffold by the top-level project will be used in turn, with projects declared later in the allowed-packages list taking precedence over the projects named before. The top-level composer.json itself is always implicitly allowed to scaffold files, and its scaffold files have highest priority.

Defining Project Locations

The top-level project in turn must define where the web root is located. It does so via the locations mapping, as shown below:

  "name": "my/project",
  ...
  "extra": {
    "drupal-scaffold": {
      "locations": {
        "web-root": "./docroot"
      },
      ...
    }
  }

This makes it possible to configure a project with different file layouts; for example, either the drupal/drupal file layout or the drupal-composer/drupal-project file layout could be used to set up a project.

If a web-root is not explicitly defined, then it will default to ./.

Altering Scaffold Files

Sometimes, a project might wish to use a scaffold file provided by a dependency, but alter it in some way. Two forms of alteration are supported: appending and patching.

The example below shows a project that appends additional entries onto the end of the robots.txt file provided by drupal/core:

  "name": "my/project",
  ...
  "extra": {
    "drupal-scaffold": {
      "file-mapping": {
        "[web-root]/robots.txt": {
          "append": "assets/my-robots-additions.txt",
        }
      }
    }
  }

It is also possible to prepend to a scaffold file instead of, or in addition to appending by including a "prepend" entry that provides the relative path to the file to prepend to the scaffold file.

The example below demonstrates the use of the post-drupal-scaffold-cmd hook to patch the .htaccess file using a patch.

  "name": "my/project",
  ...
  "scripts": {
    "post-drupal-scaffold-cmd": [
      "cd docroot && patch -p1 <../patches/htaccess-ssl.patch"
    ]
  }

Defining Scaffold Files

The placement of scaffold assets is under the control of the project that provides them, but the location is always relative to some directory defined by the root project -- usually the web root. For example, the scaffold file robots.txt is copied from its source location, assets/robots.txt into the web root in the snippet below.

{
  "name": "drupal/assets",
  ...
  "extra": {
    "drupal-scaffold": {
      "file-mapping": {
        "[web-root]/robots.txt": "assets/robots.txt",
        ...
      }
    }
  }
}

Excluding Scaffold Files

Sometimes, a project might prefer to entirely replace a scaffold file provided by a dependency, and receive no further updates for it. This can be done by setting the value for the scaffold file to exclude to false:

  "name": "my/project",
  ...
  "extra": {
    "drupal-scaffold": {
      "file-mapping": {
        "[web-root]/robots.txt": false
      }
    }
  }

If possible, use the append and prepend directives as explained in altering scaffold files, above. Excluding a file means that your project will not get any bug fixes or other updates to files that are modified locally.

Overwrite

By default, scaffold files overwrite whatever content exists at the target location. Sometimes a project may wish to provide the initial contents for a file that will not be changed in subsequent updates. This can be done by setting the overwrite flag to false, as shown in the example below:

{
  "name": "service-provider/d8-scaffold-files",
  "extra": {
    "drupal-scaffold": {
      "file-mapping": {
        "[web-root]/sites/default/settings.php": {
          "mode": "replace",
          "path": "assets/sites/default/settings.php",
          "overwrite": false
        }
      }
    }
  }
}

Note that the overwrite directive is intended to be used by starter kits, service providers, and so on. Individual Drupal sites should exclude the file by setting its value to false instead.

Autoload File

The scaffold tool automatically creates the required autoload.php file at the Drupal root as part of the scaffolding operation. This file should not be modified or customized in any way. If it is committed to the repository, though, then the scaffold tool will stop managing it. If the location of the vendor directory is changed for any reason, and the autoload.php file has been committed to the repository, manually delete it and then run composer install to update it.

Specifications

Reference section for the configuration directives for the "drupal-scaffold" section of the "extra" section of a composer.json file appear below.

allowed-packages

The allowed-packages configuration setting contains an ordered list of package names that will be used during the scaffolding phase.

"allowed-packages": [
  "drupal/core",
],

file-mapping

The file-mapping configuration setting consists of a map from the destination path of the file to scaffold to a set of properties that control how the file should be scaffolded.

The available properties are as follows:

  • mode: One of "replace", "append" or "skip".
  • path: The path to the source file to write over the destination file.
  • prepend: The path to the source file to prepend to the destination file, which must always be a scaffold file provided by some other project.
  • append: Like prepend, but appends content rather than prepends.
  • overwrite: If false, prevents a replace from happening if the destination already exists.

The mode may be inferred from the other properties. If the mode is not specified, then the following defaults will be supplied:

  • replace: Selected if a path property is present, or if the entry's value is a string rather than a property set.
  • append: Selected if a prepend or append property is present.
  • skip: Selected if the entry's value is a boolean false.

Examples:

"file-mapping": {
  "[web-root]/sites/default/default.settings.php": {
    "mode": "replace",
    "path": "assets/sites/default/default.settings.php",
    "overwrite": true
  },
  "[web-root]/sites/default/settings.php": {
    "mode": "replace",
    "path": "assets/sites/default/settings.php",
    "overwrite": false
  },
  "[web-root]/robots.txt": {
    "mode": "append",
    "prepend": "assets/robots-prequel.txt",
    "append": "assets/robots-append.txt"
  },
  "[web-root]/.htaccess": {
    "mode": "skip",
  }
}

The short-form of the above example would be:

"file-mapping": {
  "[web-root]/sites/default/default.settings.php": "assets/sites/default/default.settings.php",
  "[web-root]/sites/default/settings.php": {
    "path": "assets/sites/default/settings.php",
    "overwrite": false
  },
  "[web-root]/robots.txt": {
    "prepend": "assets/robots-prequel.txt",
    "append": "assets/robots-append.txt"
  },
  "[web-root]/.htaccess": false
}

Note that there is no distinct "prepend" mode; "append" mode is used to both append and prepend to scaffold files. The reason for this is that scaffold file entries are identified in the file-mapping section keyed by their destination path, and it is not possible for multiple entries to have the same key. If "prepend" were a separate mode, then it would not be possible to both prepend and append to the same file.

By default, append operations may only be applied to files that were scaffolded by a previously evaluated project. If the force-append attribute is added to an append operation, though, then the append will be made to non-scaffolded files if and only if the append text does not already appear in the file. When using this mode, it is also possible to provide default contents to use in the event that the destination file is entirely missing.

The example below demonstrates scaffolding a settings-custom.php file, and including it from the existing settings.php file.

"file-mapping": {
  "[web-root]/sites/default/settings-custom.php": "assets/settings-custom.php",
  "[web-root]/sites/default/settings.php": {
    "append": "assets/include-settings-custom.txt",
    "force-append": true,
    "default": "assets/initial-default-settings.txt"
  }
}

Note that the example above still works if used with a project that scaffolds the settings.php file.

gitignore

The gitignore configuration setting controls whether or not this plugin will manage .gitignore files for files written during the scaffold operation.

  • true: .gitignore files will be updated when scaffold files are written.
  • false: .gitignore files will never be modified.
  • Not set: .gitignore files will be updated if the target directory is a local working copy of a git repository, and the vendor directory is ignored in that repository.

locations

The locations configuration setting contains a list of named locations that may be used in placing scaffold files. The only required location is web-root. Other locations may also be defined if desired.

"locations": {
  "web-root": "./docroot"
},

symlink

The symlink property causes replace operations to make a symlink to the source file rather than copying it. This is useful when doing core development, as the symlink files themselves should not be edited. Note that append operations override the symlink option, to prevent the original scaffold assets from being altered.

"symlink": true,

Managing Scaffold Files

Scaffold files should be treated the same way that the vendor directory is handled. If you need to commit vendor (e.g. in order to deploy your site), then you should also commit your scaffold files. You should not commit your vendor directory or scaffold files unless it is necessary.

If a dependency provides a scaffold file with overwrite set to false, that file should be committed to your repository.

By default, .gitignore files will be automatically updated if needed when scaffold files are written. See the gitignore setting in the Specifications section above.

Examples

Some full-length examples appear below.

Sample composer.json for a project that relies on packages that use composer-scaffold:

{
  "name": "my/project",
  "require": {
    "drupal/core-composer-scaffold": "*",
    "composer/installers": "^1.2",
    "cweagans/composer-patches": "^1.6.5",
    "drupal/core": "^8.8.x-dev",
    "service-provider/d8-scaffold-files": "^1"
  },
  "config": {
    "optimize-autoloader": true,
    "sort-packages": true
  },
  "extra": {
    "drupal-scaffold": {
      "allowed-packages": [
        "drupal/core"
      ],
      "locations": {
        "web-root": "./docroot"
      },
      "symlink": true,
      "overwrite": true,
      "file-mapping": {
        "[web-root]/.htaccess": false,
        "[web-root]/robots.txt": "assets/robots-default.txt"
      }
    }
  }
}

Sample composer.json for drupal/core, with assets placed in a different project:

{
  "name": "drupal/core",
  "extra": {
    "drupal-scaffold": {
      "allowed-packages": [
        "drupal/assets",
      ]
    }
  }
}

Sample composer.json for composer-scaffold files in drupal/assets:

{
  "name": "drupal/assets",
  "extra": {
    "drupal-scaffold": {
      "file-mapping": {
        "[web-root]/.csslintrc": "assets/.csslintrc",
        "[web-root]/.editorconfig": "assets/.editorconfig",
        "[web-root]/.eslintignore": "assets/.eslintignore",
        "[web-root]/.eslintrc.json": "assets/.eslintrc.json",
        "[web-root]/.gitattributes": "assets/.gitattributes",
        "[web-root]/.ht.router.php": "assets/.ht.router.php",
        "[web-root]/.htaccess": "assets/.htaccess",
        "[web-root]/sites/default/default.services.yml": "assets/default.services.yml",
        "[web-root]/sites/default/default.settings.php": "assets/default.settings.php",
        "[web-root]/sites/example.settings.local.php": "assets/example.settings.local.php",
        "[web-root]/sites/example.sites.php": "assets/example.sites.php",
        "[web-root]/index.php": "assets/index.php",
        "[web-root]/robots.txt": "assets/robots.txt",
        "[web-root]/update.php": "assets/update.php",
        "[web-root]/web.config": "assets/web.config"
      }
    }
  }
}

Sample composer.json for a library that implements composer-scaffold:

{
  "name": "service-provider/d8-scaffold-files",
  "extra": {
    "drupal-scaffold": {
      "file-mapping": {
        "[web-root]/sites/default/settings.php": "assets/sites/default/settings.php"
      }
    }
  }
}

Append to robots.txt:

{
  "name": "service-provider/d8-scaffold-files",
  "extra": {
    "drupal-scaffold": {
      "file-mapping": {
        "[web-root]/robots.txt": {
          "append": "assets/my-robots-additions.txt",
        }
      }
    }
  }
}

Patch a file after it's copied:

"post-drupal-scaffold-cmd": [
  "cd docroot && patch -p1 <../patches/htaccess-ssl.patch"
]

Related Plugins

drupal-composer/drupal-scaffold

Previous versions of Drupal Composer Scaffold (see community project, drupal-composer/drupal-scaffold) downloaded each scaffold file directly from its distribution server (e.g. https://git.drupalcode.org) to the desired destination directory. This was necessary, because there was no subtree split of the scaffold files available. Copying the scaffold assets from projects already downloaded by Composer is more effective, as downloading and unpacking archive files is more efficient than downloading each scaffold file individually.

composer/installers

The composer/installers plugin is similar to this plugin in that it allows dependencies to be installed in locations other than the vendor directory. However, Composer and the composer/installers plugin have a limitation that one project cannot be moved inside of another project. Therefore, if you use composer/installers to place Drupal modules inside the directory web/modules/contrib, then you cannot also use composer/installers to place files such as index.php and robots.txt into the web directory. The drupal-scaffold plugin was created to work around this limitation.

Comments
  • Fix PHP CodeSniffer warning.

    Fix PHP CodeSniffer warning.

    PHPCS reports the following for the autogenerated autoloader:

    FILE: /srv/app/docroot/autoload.php
    ----------------------------------------------------------------------
    FOUND 1 ERROR AFFECTING 1 LINE
    ----------------------------------------------------------------------
     8 | ERROR | [x] Expected 1 space after asterisk; 0 found
    ----------------------------------------------------------------------
    PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
    ----------------------------------------------------------------------
    
    opened by manarth 2
  • ensure the file exists when using an AppendOp over an existing ReplaceOp

    ensure the file exists when using an AppendOp over an existing ReplaceOp

    Hello,

    I've found a bug when using a particular config in my project (exemple below). The main info is that I'm Skipping or Appending every file inside sites/default.

    {
      "name": "*******",
      "repositories": [
          {
              "type": "composer",
              "url": "https://packages.drupal.org/8"
          }
      ],
      "require": {
          "composer/installers": "^1.9",
          "drupal/admin_toolbar": "^3.0",
          "drupal/core-composer-scaffold": "^9.2",
          "drupal/core-recommended": "^9.2",
          "drush/drush": "^10.5"
      },
      "conflict": {
          "drupal/drupal": "*"
      },
      "minimum-stability": "stable",
      "prefer-stable": true,
      "config": {
          "sort-packages": true
      },
      "extra": {
          "drupal-scaffold": {
              "locations": {
                  "web-root": "web/"
              },
              "file-mapping": {
                  "[web-root]/.csslintrc": false,
                  "[web-root]/.eslintignore": false,
                  "[web-root]/.eslintrc.json": false,
                  "[web-root]/.ht.router.php": false,
                  "[web-root]/example.gitignore": false,
                  "[web-root]/INSTALL.txt": false,
                  "[web-root]/README.md": false,
                  "[web-root]/update.php": false,
                  "[web-root]/web.config": false,
                  "[web-root]/sites/README.txt": false,
                  "[web-root]/sites/development.services.yml": false,
                  "[web-root]/sites/example.settings.local.php": false,
                  "[web-root]/sites/example.sites.php": false,
                  "[web-root]/sites/default/default.services.yml": false,
                  "[web-root]/modules/README.txt": false,
                  "[web-root]/profiles/README.txt": false,
                  "[web-root]/themes/README.txt": false,
                  "[web-root]/sites/default/default.settings.php": {
                      "mode": "append",
                      "append": "assets/default-settings-php-additions.txt"
                  }
              }
          },
          "installer-paths": {
              "web/core": [
                  "type:drupal-core"
              ],
              "web/libraries/{$name}": [
                  "type:drupal-library"
              ],
              "web/modules/contrib/{$name}": [
                  "type:drupal-module"
              ],
              "web/profiles/contrib/{$name}": [
                  "type:drupal-profile"
              ],
              "web/themes/contrib/{$name}": [
                  "type:drupal-theme"
              ],
              "drush/Commands/contrib/{$name}": [
                  "type:drupal-drush"
              ],
              "web/modules/custom/{$name}": [
                  "type:drupal-custom-module"
              ],
              "web/profiles/custom/{$name}": [
                  "type:drupal-custom-profile"
              ],
              "web/themes/custom/{$name}": [
                  "type:drupal-custom-theme"
              ]
          }
      },
      "require-dev": {
          "drupal/devel": "^4.1",
          "kint-php/kint": "^3.3",
          "marcocesarato/php-conventional-changelog": "^1.10"
      },
      "scripts": {
          "changelog": "conventional-changelog --history"
      },
      "version": "1.0.0"
    }
    

    In this case, the sites/default directory is never created by a ReplaceOp and the AppendOp fails with a

    [ErrorException]                                                                                               
      file_put_contents(/workspace/backend/web/sites/default/default.settings.php): Failed to open stream: No such   
      file or directory 
    

    I'm suggesting the following modification to fix this.

    Regards

    opened by epieddy 1
  • Core is implicitly allowed to scaffold files

    Core is implicitly allowed to scaffold files

    Mention in the documentation that Drupal core is implicitly allowed to scaffold files. (drupal/legacy-scaffold-assets is too, but imho we should not be totally complete in the docs).

    One allowed package example uses drupal/core as allowed-package, but that is not a realistic example. Therefore I propose to change it. Not having a realistic example I chose example/assets.

    opened by Sutharsan 1
  • Remove 'overwrite' as example in scaffold options

    Remove 'overwrite' as example in scaffold options

    If I interpret the code correctly, it is not possible to set a default 'overwrite' at scaffold level. ScaffoldOptions::__construct does not define an 'overwrite' default. Overwriting is default, but adding it explicitly to an example at scaffold level gives a wrong signal as if other defaults are possible.

    opened by Sutharsan 1
  • Document the correct web-root default value

    Document the correct web-root default value

    The web root default is set in ::__construct() in https://github.com/drupal/core-composer-scaffold/edit/8.8.x/ScaffoldOptions.php as '.' (dot).

    The current .\ default may give the impression that the web root should end with a slash.

    opened by Sutharsan 1
Perform static analysis of Drupal PHP code with phpstan-drupal.

Perform static analysis of Drupal PHP code with PHPStan and PHPStan-Drupal on Drupal using PHP 8. For example: docker run --rm \ -v $(pwd)/example01

Dcycle 0 Dec 10, 2021
The Drupal Vendor Hardening Composer Plugin

The Drupal Vendor Hardening Composer Plugin

Drupal 13 Oct 4, 2022
A composer plugin, to install differenty types of composer packages in custom directories outside the default composer default installation path which is in the vendor folder.

composer-custom-directory-installer A composer plugin, to install differenty types of composer packages in custom directories outside the default comp

Mina Nabil Sami 136 Dec 30, 2022
Workshop environment for Decoupled Drupal

Decoupled Drupal Workshop ?? Welcome to the Bluehorn Digital Decoupled Drupal workshop repository! This repository contains a decoupled ready Drupal b

Bluehorn Digital 6 Feb 2, 2022
Drall - a tool to that helps run drush commands on multi-site Drupal installations

Drall Drall is a tool to that helps run drush commands on multi-site Drupal installations. One command to drush them all. — Jigarius A big thanks and

Jigar Mehta 23 Nov 25, 2022
Drupal / Eleventy Docs Site for the Design System

Drupal / Eleventy Docs Site for the Design System

University of Michigan Library 2 Oct 31, 2022
Extension for PHPStan to allow analysis of Drupal code.

phpstan-drupal Extension for PHPStan to allow analysis of Drupal code. Sponsors Would you like to sponsor? Usage When you are using phpstan/extension-

Matt Glaman 154 Jan 2, 2023
Roach-example-project - Example project to demonstrate how to use RoachPHP in a Laravel project.

Example repository to illustrate how to use roach-php/laravel in a Laravel app. Check app/Spiders/FussballdatenSpider.php for an example spider that c

Kai Sassnowski 11 Dec 15, 2022
Very flexible git hook manager for php developers

CaptainHook CaptainHook is an easy to use and very flexible git hook library for php developers. It enables you to configure your git hook actions in

CaptainHook 812 Dec 30, 2022
A robust and flexible way to add double-opt-in (DOI) to any form in Mautic

Mautic double-opt-in (DOI) plugin Adds a robust and flexible way to add a double-opt-in process (DOI) to any form in Mautic. What is the plugin for? I

Content Optimizer GmbH 11 Dec 29, 2022
Powerful and flexible component for building site breadcrumbs.

Phalcon Breadcrumbs Phalcon Breadcrumbs is a powerful and flexible component for building site breadcrumbs. You can adapt it to your own needs or impr

Serghei Iakovlev 40 Nov 5, 2022
Tars is a high-performance RPC framework based on name service and Tars protocol, also integrated administration platform, and implemented hosting-service via flexible schedule.

TARS - A Linux Foundation Project TARS Foundation Official Website TARS Project Official Website WeChat Group: TARS01 WeChat Offical Account: TarsClou

THE TARS FOUNDATION PROJECTS 9.6k Jan 1, 2023
Admidio is a free open source user management system for websites of organizations and groups. The system has a flexible role model so that it’s possible to reflect the structure and permissions of your organization.

Admidio Admidio is a free open source user management system for websites of organizations and groups. The system has a flexible role model so that it

Admidio 212 Dec 30, 2022
Simple JQL builder for Jira search

Jql Builder Simple JQL builder for Jira search Installation composer require devmoath/jql-builder Usage Generate query with one condition: \DevMoath\J

Moath 11 Jan 2, 2023
A visual process builder

DataStory ⚡ visual programming DataStory provides a workbench for designing data flow diagrams. ⚠️ We have moved to an organisation ?? Live Demo data-

Anders Jürisoo 120 Dec 12, 2022
Bundle providing Honeypot field for the Form Builder in Ibexa DXP Experience/Commerce (3.X)

IbexaHoneypot Bundle providing Honeypot field for the Form Builder in Ibexa DXP Experience/Commerce (3.X) What is Honey pot? A honey pot trap involves

null 1 Oct 14, 2021
Phalcon Builder - is a packaging system that make it easy and quick to build Phalcon packages such as rpms, debs, etc. Phalcon's distribution that hosted at PackageCloud.

Phalcon Builder - is a packaging system that make it easy and quick to build Phalcon packages such as rpms, debs, etc. Phalcon's distribution that hos

The Phalcon PHP Framework 26 Oct 7, 2022
🧩 Laravel Query Builder integration for PhpStorm

Laravel Query Laravel + DataGrip = ♥️ This plugin provides database integration for Laravel query builder. Features Schemas, tables, views and columns

Ernestas Kvedaras 41 Dec 14, 2022
FFCMS 3 version core MVC architecture. Build-on use with ffcms main architecture builder.

FFCMS 3 version core MVC architecture. Build-on use with ffcms main architecture builder.

FFCMS 0 Feb 25, 2022