diff --git a/core/drupalci.yml b/core/drupalci.yml
index 66a3b12..c7538d5 100644
--- a/core/drupalci.yml
+++ b/core/drupalci.yml
@@ -20,36 +20,9 @@ build:
         sniff-all-files: false
         halt-on-fail: false
     testing:
-      # run_tests task is executed several times in order of performance speeds.
-      # halt-on-fail can be set on the run_tests tasks in order to fail fast.
-      # suppress-deprecations is false in order to be alerted to usages of
-      # deprecated code.
-      run_tests.phpunit:
-        types: 'PHPUnit-Unit'
-        testgroups: '--all'
-        suppress-deprecations: false
-        halt-on-fail: false
-      run_tests.kernel:
-        types: 'PHPUnit-Kernel'
-        testgroups: '--all'
-        suppress-deprecations: false
-        halt-on-fail: false
-      run_tests.simpletest:
-         types: 'Simpletest'
-         testgroups: '--all'
-         suppress-deprecations: false
-         halt-on-fail: false
-      run_tests.functional:
-        types: 'PHPUnit-Functional'
-        testgroups: '--all'
-        suppress-deprecations: false
-        halt-on-fail: false
-      run_tests.javascript:
-        concurrency: 15
-        types: 'PHPUnit-FunctionalJavascript'
-        testgroups: '--all'
-        suppress-deprecations: false
-        halt-on-fail: false
-      # Run nightwatch testing.
-      # @see https://www.drupal.org/project/drupal/issues/2869825
-      nightwatchjs:
+      container_command.drupal_scaffold:
+        commands:
+          - "cd ${SOURCE_DIR}/core/scripts/composer/scaffold && sudo -u www-data composer install --no-suggest --no-progress --no-interaction"
+          - "mkdir -p /var/lib/drupalci/artifacts/container_command.drupal_scaffold/junitxml"
+          - "cd ${SOURCE_DIR}/core/scripts/composer/scaffold && ./vendor/bin/phpunit --log-junit /var/lib/drupalci/artifacts/container_command.drupal_scaffold/junitxml/drupal_scaffold.xml"
+        halt-on-fail: true
diff --git a/core/scripts/composer/scaffold/.gitignore b/core/scripts/composer/scaffold/.gitignore
new file mode 100644
index 0000000..de4a392
--- /dev/null
+++ b/core/scripts/composer/scaffold/.gitignore
@@ -0,0 +1,2 @@
+/vendor
+/composer.lock
diff --git a/core/scripts/composer/scaffold/README.md b/core/scripts/composer/scaffold/README.md
new file mode 100644
index 0000000..a8e0a76
--- /dev/null
+++ b/core/scripts/composer/scaffold/README.md
@@ -0,0 +1,127 @@
+# drupal-scaffold
+
+Composer plugin for automatically downloading Drupal scaffold files (like
+`index.php`, `update.php`, …) when using `drupal/core` via Composer.
+
+It is recommended that the vendor directory be placed in its standard location
+at the project root, outside of the Drupal root; however, the location of the
+vendor directory and the name of the Drupal root may be placed in whatever
+location suits the project. Drupal-scaffold will generate the autoload.php
+file at the Drupal root to require the Composer-generated autoload file in the
+vendor directory.
+
+## Usage
+
+Run `composer require drupal/drupal-scaffold` in your composer
+project before installing or updating `drupal/core`.
+
+Once drupal-scaffold is required by your project, it will automatically update
+your scaffold files whenever `composer update` changes the version of
+`drupal/core` installed.
+
+## Configuration
+
+You can configure the plugin with providing some settings in the `extra` section
+of your root `composer.json`.
+
+```json
+{
+  "extra": {
+    "drupal-scaffold": {
+      "source": "https://cgit.drupalcode.org/drupal/plain/{path}?h={version}",
+      "excludes": [
+        "google123.html",
+        "robots.txt"
+      ],
+      "includes": [
+        "sites/default/example.settings.my.php"
+      ],
+      "initial": {
+        "sites/default/default.services.yml": "sites/default/services.yml",
+        "sites/default/default.settings.php": "sites/default/settings.php"
+      },
+      "omit-defaults": false
+    }
+  }
+}
+```
+The `source` option may be used to specify the URL to download the
+scaffold files from; the default source is drupal.org. The literal string
+`{version}` in the `source` option is replaced with the current version of
+Drupal core being updated prior to download.
+
+With the `drupal-scaffold` option `excludes`, you can provide additional paths
+that should not be copied or overwritten. The plugin provides no excludes by
+default.
+
+Default includes are provided by the plugin:
+```
+.csslintrc
+.editorconfig
+.eslintignore
+.eslintrc.json
+.gitattributes
+.ht.router.php
+.htaccess
+example.gitignore
+index.php
+INSTALL.txt
+README.txt
+robots.txt
+modules/README.txt
+profiles/README.txt
+sites/README.txt
+sites/default/default.settings.php
+sites/default/default.services.yml
+sites/development.services.yml
+sites/example.settings.local.php
+sites/example.sites.php
+themes/README.txt
+update.php
+web.config
+```
+
+When setting `omit-defaults` to `true`, neither the default excludes nor the
+default includes will be provided; in this instance, only those files explicitly
+listed in the `excludes` and `includes` options will be considered. If
+`omit-defaults` is `false` (the default), then any items listed in `excludes`
+or `includes` will be in addition to the usual defaults.
+
+The `initial` hash lists files that should be copied over only if they do not
+exist in the destination. The key specifies the path to the source file, and
+the value indicates the path to the destination file.
+
+## Limitation
+
+When using Composer to install or update the Drupal development branch, the
+scaffold files are always taken from the HEAD of the branch (or, more
+specifically, from the most recent development .tar.gz archive). This might
+not be what you want when using an old development version (e.g. when the
+version is fixed via composer.lock). To avoid problems, always commit your
+scaffold files to the repository any time that composer.lock is committed.
+Note that the correct scaffold files are retrieved when using a tagged release
+of `drupal/core` (recommended).
+
+## Custom command
+
+The plugin by default is only downloading the scaffold files when installing or
+updating `drupal/core`. You can call it manually with "composer drupal:scaffold"
+or "@composer drupal:scaffold" in Composer Scripts.
+
+```json
+"scripts": {
+    "post-install-cmd": [
+        "@composer drupal:scaffold",
+        "..."
+    ],
+    "post-update-cmd": [
+        "@composer drupal:scaffold",
+        "..."
+    ]
+},
+```
+
+It is assumed that the scaffold files will be committed to the repository, to
+ensure that the correct files are used on the CI server (see **Limitation**,
+above). After running `composer install` for the first time commit the scaffold
+files to your repository.
diff --git a/core/scripts/composer/scaffold/composer.json b/core/scripts/composer/scaffold/composer.json
new file mode 100644
index 0000000..907d736
--- /dev/null
+++ b/core/scripts/composer/scaffold/composer.json
@@ -0,0 +1,22 @@
+{
+    "name": "drupal/drupal-scaffold",
+    "description": "Composer Plugin for updating the Drupal scaffold files when using drupal/core",
+    "type": "composer-plugin",
+    "license": "GPL-2.0-or-later",
+    "require": {
+        "php": ">=5.4.5",
+        "composer-plugin-api": "^1.0.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "DrupalComposer\\DrupalScaffold\\": "src/"
+        }
+    },
+    "extra": {
+        "class": "DrupalComposer\\DrupalScaffold\\Plugin"
+    },
+    "require-dev": {
+        "composer/composer": "^1.6.5",
+        "phpunit/phpunit": "^4.8.35 || ^6.5"
+    }
+}
diff --git a/core/scripts/composer/scaffold/phpunit.xml.dist b/core/scripts/composer/scaffold/phpunit.xml.dist
new file mode 100644
index 0000000..ac16a3e
--- /dev/null
+++ b/core/scripts/composer/scaffold/phpunit.xml.dist
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+    backupGlobals="false"
+    colors="true"
+    bootstrap="vendor/autoload.php"
+    verbose="true"
+>
+    <testsuites>
+        <testsuite name="drupal-scaffold">
+            <directory>./tests/</directory>
+        </testsuite>
+    </testsuites>
+</phpunit>
diff --git a/core/scripts/composer/scaffold/src/CommandProvider.php b/core/scripts/composer/scaffold/src/CommandProvider.php
new file mode 100644
index 0000000..327a7f2
--- /dev/null
+++ b/core/scripts/composer/scaffold/src/CommandProvider.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace DrupalComposer\DrupalScaffold;
+
+use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
+
+/**
+ * List of all commands provided by this package.
+ */
+class CommandProvider implements CommandProviderCapability {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCommands() {
+    return [
+      new DrupalScaffoldCommand(),
+    ];
+  }
+
+}
diff --git a/core/scripts/composer/scaffold/src/DrupalScaffoldCommand.php b/core/scripts/composer/scaffold/src/DrupalScaffoldCommand.php
new file mode 100644
index 0000000..485a2f5
--- /dev/null
+++ b/core/scripts/composer/scaffold/src/DrupalScaffoldCommand.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace DrupalComposer\DrupalScaffold;
+
+use Composer\Command\BaseCommand;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * The "drupal:scaffold" command class.
+ *
+ * Downloads scaffold files and generates the autoload.php file.
+ */
+class DrupalScaffoldCommand extends BaseCommand {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function configure() {
+    parent::configure();
+    $this
+      ->setName('drupal:scaffold')
+      ->setDescription('Update the Drupal scaffold files.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function execute(InputInterface $input, OutputInterface $output) {
+    $handler = new Handler($this->getComposer(), $this->getIO());
+    $handler->downloadScaffold();
+    // Generate the autoload.php file after generating the scaffold files.
+    $handler->generateAutoload();
+  }
+
+}
diff --git a/core/scripts/composer/scaffold/src/FileFetcher.php b/core/scripts/composer/scaffold/src/FileFetcher.php
new file mode 100644
index 0000000..653e7f2
--- /dev/null
+++ b/core/scripts/composer/scaffold/src/FileFetcher.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace DrupalComposer\DrupalScaffold;
+
+use Composer\IO\IOInterface;
+use Composer\Util\Filesystem;
+use Composer\Util\RemoteFilesystem;
+
+/**
+ * Downloads all required files and writes it to the file system.
+ */
+class FileFetcher {
+
+  /**
+   * @var \Composer\Util\RemoteFilesystem
+   */
+  protected $remoteFilesystem;
+
+  /**
+   * @var \Composer\IO\IOInterface
+   */
+  protected $io;
+
+  /**
+   * @var bool
+   *
+   * A boolean indicating if progress should be displayed.
+   */
+  protected $progress;
+
+  protected $source;
+  protected $filenames;
+  protected $fs;
+
+  /**
+   * Constructs this FileFetcher object.
+   */
+  public function __construct(RemoteFilesystem $remoteFilesystem, $source, IOInterface $io, $progress = TRUE) {
+    $this->remoteFilesystem = $remoteFilesystem;
+    $this->io = $io;
+    $this->source = $source;
+    $this->fs = new Filesystem();
+    $this->progress = $progress;
+  }
+
+  /**
+   * Downloads all required files and writes it to the file system.
+   */
+  public function fetch($version, $destination, $override) {
+    foreach ($this->filenames as $sourceFilename => $filename) {
+      $target = "$destination/$filename";
+      if ($override || !file_exists($target)) {
+        $url = $this->getUri($sourceFilename, $version);
+        $this->fs->ensureDirectoryExists($destination . '/' . dirname($filename));
+        if ($this->progress) {
+          $this->io->writeError("  - <info>$filename</info> (<comment>$url</comment>): ", FALSE);
+          $this->remoteFilesystem->copy($url, $url, $target, $this->progress);
+          // Used to put a new line because the remote file system does not put
+          // one.
+          $this->io->writeError('');
+        }
+        else {
+          $this->remoteFilesystem->copy($url, $url, $target, $this->progress);
+        }
+      }
+    }
+  }
+
+  /**
+   * Set filenames.
+   */
+  public function setFilenames(array $filenames) {
+    $this->filenames = $filenames;
+  }
+
+  /**
+   * Replace filename and version in the source pattern with their values.
+   */
+  protected function getUri($filename, $version) {
+    $map = [
+      '{path}' => $filename,
+      '{version}' => $version,
+    ];
+    return str_replace(array_keys($map), array_values($map), $this->source);
+  }
+
+}
diff --git a/core/scripts/composer/scaffold/src/Handler.php b/core/scripts/composer/scaffold/src/Handler.php
new file mode 100644
index 0000000..2f076d3
--- /dev/null
+++ b/core/scripts/composer/scaffold/src/Handler.php
@@ -0,0 +1,398 @@
+<?php
+
+namespace DrupalComposer\DrupalScaffold;
+
+use Composer\Script\Event;
+use Composer\Installer\PackageEvent;
+use Composer\Plugin\CommandEvent;
+use Composer\Composer;
+use Composer\DependencyResolver\Operation\InstallOperation;
+use Composer\DependencyResolver\Operation\UpdateOperation;
+use Composer\EventDispatcher\EventDispatcher;
+use Composer\IO\IOInterface;
+use Composer\Package\PackageInterface;
+use Composer\Util\Filesystem;
+use Composer\Util\RemoteFilesystem;
+use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
+
+/**
+ * Core class of the plugin, contains all logic which files should be fetched.
+ */
+class Handler {
+
+  const PRE_DRUPAL_SCAFFOLD_CMD = 'pre-drupal-scaffold-cmd';
+  const POST_DRUPAL_SCAFFOLD_CMD = 'post-drupal-scaffold-cmd';
+
+  /**
+   * @var \Composer\Composer
+   */
+  protected $composer;
+
+  /**
+   * @var \Composer\IO\IOInterface
+   */
+  protected $io;
+
+  /**
+   * @var bool
+   *
+   * A boolean indicating if progress should be displayed.
+   */
+  protected $progress;
+
+  /**
+   * @var \Composer\Package\PackageInterface
+   */
+  protected $drupalCorePackage;
+
+  /**
+   * Handler constructor.
+   *
+   * @param \Composer\Composer $composer
+   * @param \Composer\IO\IOInterface $io
+   */
+  public function __construct(Composer $composer, IOInterface $io) {
+    $this->composer = $composer;
+    $this->io = $io;
+    $this->progress = TRUE;
+
+    // Pre-load all of our sources so that we do not run up
+    // against problems in `composer update` operations.
+    $this->manualLoad();
+  }
+
+  protected function manualLoad() {
+    $src_dir = __DIR__;
+
+    $classes = [
+      'CommandProvider',
+      'DrupalScaffoldCommand',
+      'FileFetcher',
+      'PrestissimoFileFetcher',
+    ];
+
+    foreach ($classes as $src) {
+      if (!class_exists('\\DrupalComposer\\DrupalScaffold\\' . $src)) {
+        include "{$src_dir}/{$src}.php";
+      }
+    }
+  }
+
+  /**
+   * @param $operation
+   * @return mixed
+   */
+  protected function getCorePackage($operation) {
+    if ($operation instanceof InstallOperation) {
+      $package = $operation->getPackage();
+    }
+    elseif ($operation instanceof UpdateOperation) {
+      $package = $operation->getTargetPackage();
+    }
+    if (isset($package) && $package instanceof PackageInterface && $package->getName() == 'drupal/core') {
+      return $package;
+    }
+    return NULL;
+  }
+
+  /**
+   * Get the command options.
+   *
+   * @param \Composer\Plugin\CommandEvent $event
+   */
+  public function onCmdBeginsEvent(CommandEvent $event) {
+    if ($event->getInput()->hasOption('no-progress')) {
+      $this->progress = !($event->getInput()->getOption('no-progress'));
+    }
+    else {
+      $this->progress = TRUE;
+    }
+  }
+
+  /**
+   * Marks scaffolding to be processed after an install or update command.
+   *
+   * @param \Composer\Installer\PackageEvent $event
+   */
+  public function onPostPackageEvent(PackageEvent $event) {
+    $package = $this->getCorePackage($event->getOperation());
+    if ($package) {
+      // By explicitly setting the core package, the onPostCmdEvent() will
+      // process the scaffolding automatically.
+      $this->drupalCorePackage = $package;
+    }
+  }
+
+  /**
+   * Post install command event to execute the scaffolding.
+   *
+   * @param \Composer\Script\Event $event
+   */
+  public function onPostCmdEvent(Event $event) {
+    // Only install the scaffolding if drupal/core was installed,
+    // AND there are no scaffolding files present.
+    if (isset($this->drupalCorePackage)) {
+      $this->downloadScaffold();
+      // Generate the autoload.php file after generating the scaffold files.
+      $this->generateAutoload();
+    }
+  }
+
+  /**
+   * Downloads drupal scaffold files for the current process.
+   */
+  public function downloadScaffold() {
+    $drupalCorePackage = $this->getDrupalCorePackage();
+    $webroot = realpath($this->getWebRoot());
+
+    // Collect options, excludes and settings files.
+    $options = $this->getOptions();
+    $files = array_diff($this->getIncludes(), $this->getExcludes());
+
+    // Call any pre-scaffold scripts that may be defined.
+    $dispatcher = new EventDispatcher($this->composer, $this->io);
+    $dispatcher->dispatch(self::PRE_DRUPAL_SCAFFOLD_CMD);
+
+    $version = $this->getDrupalCoreVersion($drupalCorePackage);
+
+    $remoteFs = new RemoteFilesystem($this->io);
+
+    $fetcher = new PrestissimoFileFetcher($remoteFs, $options['source'], $this->io, $this->progress, $this->composer->getConfig());
+    $fetcher->setFilenames(array_combine($files, $files));
+    $fetcher->fetch($version, $webroot, TRUE);
+
+    $fetcher->setFilenames($this->getInitial());
+    $fetcher->fetch($version, $webroot, FALSE);
+
+    // Call post-scaffold scripts.
+    $dispatcher->dispatch(self::POST_DRUPAL_SCAFFOLD_CMD);
+  }
+
+  /**
+   * Generate the autoload file at the project root.  Include the
+   * autoload file that Composer generated.
+   */
+  public function generateAutoload() {
+    $vendorPath = $this->getVendorPath();
+    $webroot = $this->getWebRoot();
+
+    // Calculate the relative path from the webroot (location of the
+    // project autoload.php) to the vendor directory.
+    $fs = new SymfonyFilesystem();
+    $relativeVendorPath = $fs->makePathRelative($vendorPath, realpath($webroot));
+
+    $fs->dumpFile($webroot . "/autoload.php", $this->autoLoadContents($relativeVendorPath));
+  }
+
+  /**
+   * Build the contents of the autoload file.
+   *
+   * @return string
+   */
+  protected function autoLoadContents($relativeVendorPath) {
+    $relativeVendorPath = rtrim($relativeVendorPath, '/');
+
+    $autoloadContents = <<<EOF
+<?php
+
+/**
+ * @file
+ * Includes the autoloader created by Composer.
+ *
+ * @see composer.json
+ * @see index.php
+ * @see core/install.php
+ * @see core/rebuild.php
+ * @see core/modules/statistics/statistics.php
+ */
+
+return require __DIR__ . '/$relativeVendorPath/autoload.php';
+
+EOF;
+    return $autoloadContents;
+  }
+
+  /**
+   * Get the path to the 'vendor' directory.
+   *
+   * @return string
+   */
+  public function getVendorPath() {
+    $config = $this->composer->getConfig();
+    $filesystem = new Filesystem();
+    $filesystem->ensureDirectoryExists($config->get('vendor-dir'));
+    $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
+
+    return $vendorPath;
+  }
+
+  /**
+   * Look up the Drupal core package object, or return it from where we cached
+   * it in the $drupalCorePackage field.
+   *
+   * @return \Composer\Package\PackageInterface
+   */
+  public function getDrupalCorePackage() {
+    if (!isset($this->drupalCorePackage)) {
+      $this->drupalCorePackage = $this->getPackage('drupal/core');
+    }
+    return $this->drupalCorePackage;
+  }
+
+  /**
+   * Returns the Drupal core version for the given package.
+   *
+   * @param \Composer\Package\PackageInterface $drupalCorePackage
+   *
+   * @return string
+   */
+  protected function getDrupalCoreVersion(PackageInterface $drupalCorePackage) {
+    $version = $drupalCorePackage->getPrettyVersion();
+    if ($drupalCorePackage->getStability() == 'dev' && substr($version, -4) == '-dev') {
+      $version = substr($version, 0, -4);
+      return $version;
+    }
+    return $version;
+  }
+
+  /**
+   * Retrieve the path to the web root.
+   *
+   * @return string
+   */
+  public function getWebRoot() {
+    $drupalCorePackage = $this->getDrupalCorePackage();
+    $installationManager = $this->composer->getInstallationManager();
+    $corePath = $installationManager->getInstallPath($drupalCorePackage);
+    // Webroot is the parent path of the drupal core installation path.
+    $webroot = dirname($corePath);
+
+    return $webroot;
+  }
+
+  /**
+   * Retrieve a package from the current composer process.
+   *
+   * @param string $name
+   *   Name of the package to get from the current composer installation.
+   *
+   * @return \Composer\Package\PackageInterface
+   */
+  protected function getPackage($name) {
+    return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*');
+  }
+
+  /**
+   * Retrieve excludes from optional "extra" configuration.
+   *
+   * @return array
+   */
+  protected function getExcludes() {
+    return $this->getNamedOptionList('excludes', 'getExcludesDefault');
+  }
+
+  /**
+   * Retrieve list of additional settings files from optional "extra" configuration.
+   *
+   * @return array
+   */
+  protected function getIncludes() {
+    return $this->getNamedOptionList('includes', 'getIncludesDefault');
+  }
+
+  /**
+   * Retrieve list of initial files from optional "extra" configuration.
+   *
+   * @return array
+   */
+  protected function getInitial() {
+    return $this->getNamedOptionList('initial', 'getInitialDefault');
+  }
+
+  /**
+   * Retrieve a named list of options from optional "extra" configuration.
+   * Respects 'omit-defaults', and either includes or does not include the
+   * default values, as requested.
+   *
+   * @return array
+   */
+  protected function getNamedOptionList($optionName, $defaultFn) {
+    $options = $this->getOptions($this->composer);
+    $result = [];
+    if (empty($options['omit-defaults'])) {
+      $result = $this->$defaultFn();
+    }
+    $result = array_merge($result, (array) $options[$optionName]);
+
+    return $result;
+  }
+
+  /**
+   * Retrieve excludes from optional "extra" configuration.
+   *
+   * @return array
+   */
+  protected function getOptions() {
+    $extra = $this->composer->getPackage()->getExtra() + ['drupal-scaffold' => []];
+    $options = $extra['drupal-scaffold'] + [
+      'omit-defaults' => FALSE,
+      'excludes' => [],
+      'includes' => [],
+      'initial' => [],
+      'source' => 'https://cgit.drupalcode.org/drupal/plain/{path}?h={version}',
+      // Github: https://raw.githubusercontent.com/drupal/drupal/{version}/{path}
+    ];
+    return $options;
+  }
+
+  /**
+   * Holds default excludes.
+   */
+  protected function getExcludesDefault() {
+    return [];
+  }
+
+  /**
+   * Holds default settings files list.
+   */
+  protected function getIncludesDefault() {
+
+    $common = [
+      '.csslintrc',
+      '.editorconfig',
+      '.eslintignore',
+      '.eslintrc.json',
+      '.gitattributes',
+      '.ht.router.php',
+      '.htaccess',
+      'autoload.php',
+      'example.gitignore',
+      'index.php',
+      'INSTALL.txt',
+      'README.txt',
+      'robots.txt',
+      'update.php',
+      'web.config',
+      'modules/README.txt',
+      'profiles/README.txt',
+      'sites/default/default.settings.php',
+      'sites/default/default.services.yml',
+      'sites/development.services.yml',
+      'sites/example.settings.local.php',
+      'sites/example.sites.php',
+      'sites/README.txt',
+      'themes/README.txt',
+    ];
+
+    sort($common);
+    return $common;
+  }
+
+  /**
+   * Holds default initial files.
+   */
+  protected function getInitialDefault() {
+    return [];
+  }
+
+}
diff --git a/core/scripts/composer/scaffold/src/Plugin.php b/core/scripts/composer/scaffold/src/Plugin.php
new file mode 100644
index 0000000..76677cb
--- /dev/null
+++ b/core/scripts/composer/scaffold/src/Plugin.php
@@ -0,0 +1,86 @@
+<?php
+
+namespace DrupalComposer\DrupalScaffold;
+
+use Composer\Script\Event;
+use Composer\Plugin\CommandEvent;
+use Composer\Composer;
+use Composer\EventDispatcher\EventSubscriberInterface;
+use Composer\Installer\PackageEvent;
+use Composer\Installer\PackageEvents;
+use Composer\IO\IOInterface;
+use Composer\Plugin\Capable;
+use Composer\Plugin\PluginEvents;
+use Composer\Plugin\PluginInterface;
+use Composer\Script\ScriptEvents;
+
+/**
+ * Composer plugin for handling drupal scaffold.
+ */
+class Plugin implements PluginInterface, EventSubscriberInterface, Capable {
+
+  /**
+   * @var \DrupalComposer\DrupalScaffold\Handler
+   */
+  protected $handler;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function activate(Composer $composer, IOInterface $io) {
+    // We use a separate PluginScripts object. This way we separate
+    // functionality and also avoid some debug issues with the plugin being
+    // copied on initialisation.
+    // @see \Composer\Plugin\PluginManager::registerPackage()
+    $this->handler = new Handler($composer, $io);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCapabilities() {
+    return [
+      'Composer\Plugin\Capability\CommandProvider' => 'DrupalComposer\DrupalScaffold\CommandProvider',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    return [
+      PackageEvents::POST_PACKAGE_INSTALL => 'postPackage',
+      PackageEvents::POST_PACKAGE_UPDATE => 'postPackage',
+      ScriptEvents::POST_UPDATE_CMD => 'postCmd',
+      PluginEvents::COMMAND => 'cmdBegins',
+    ];
+  }
+
+  /**
+   * Command begins event callback.
+   *
+   * @param \Composer\Plugin\CommandEvent $event
+   */
+  public function cmdBegins(CommandEvent $event) {
+    $this->handler->onCmdBeginsEvent($event);
+  }
+
+  /**
+   * Post package event behaviour.
+   *
+   * @param \Composer\Installer\PackageEvent $event
+   */
+  public function postPackage(PackageEvent $event) {
+    $this->handler->onPostPackageEvent($event);
+  }
+
+  /**
+   * Post command event callback.
+   *
+   * @param \Composer\Script\Event $event
+   */
+  public function postCmd(Event $event) {
+    $this->handler->onPostCmdEvent($event);
+  }
+
+}
diff --git a/core/scripts/composer/scaffold/src/PrestissimoFileFetcher.php b/core/scripts/composer/scaffold/src/PrestissimoFileFetcher.php
new file mode 100644
index 0000000..bd7ec1e
--- /dev/null
+++ b/core/scripts/composer/scaffold/src/PrestissimoFileFetcher.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace DrupalComposer\DrupalScaffold;
+
+use Composer\Util\RemoteFilesystem;
+use Composer\Config;
+use Composer\IO\IOInterface;
+use Hirak\Prestissimo\CopyRequest;
+use Hirak\Prestissimo\CurlMulti;
+
+/**
+ * Extends the default FileFetcher and uses hirak/prestissimo for parallel
+ * downloads.
+ */
+class PrestissimoFileFetcher extends FileFetcher {
+
+  /**
+   * @var \Composer\Config
+   */
+  protected $config;
+
+  /**
+   * Constructs this PrestissimoFileFetcher object.
+   */
+  public function __construct(RemoteFilesystem $remoteFilesystem, $source, IOInterface $io, $progress = TRUE, Config $config) {
+    parent::__construct($remoteFilesystem, $source, $io, $progress);
+    $this->config = $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetch($version, $destination, $override) {
+    if (class_exists(CurlMulti::class)) {
+      $this->fetchWithPrestissimo($version, $destination, $override);
+      return;
+    }
+    parent::fetch($version, $destination, $override);
+  }
+
+  /**
+   * Fetch files in parallel.
+   */
+  protected function fetchWithPrestissimo($version, $destination, $override) {
+    $requests = [];
+
+    foreach ($this->filenames as $sourceFilename => $filename) {
+      $target = "$destination/$filename";
+      if ($override || !file_exists($target)) {
+        $url = $this->getUri($sourceFilename, $version);
+        $this->fs->ensureDirectoryExists($destination . '/' . dirname($filename));
+        $requests[] = new CopyRequest($url, $target, FALSE, $this->io, $this->config);
+      }
+    }
+
+    $successCnt = $failureCnt = 0;
+    $totalCnt = count($requests);
+    if ($totalCnt == 0) {
+      return;
+    }
+
+    $multi = new CurlMulti();
+    $multi->setRequests($requests);
+    do {
+      $multi->setupEventLoop();
+      $multi->wait();
+      $result = $multi->getFinishedResults();
+      $successCnt += $result['successCnt'];
+      $failureCnt += $result['failureCnt'];
+      if ($this->progress) {
+        foreach ($result['urls'] as $url) {
+          $this->io->writeError("  - Downloading <comment>$successCnt</comment>/<comment>$totalCnt</comment>: <info>$url</info>", TRUE);
+        }
+      }
+    } while ($multi->remain());
+  }
+
+}
diff --git a/core/scripts/composer/scaffold/tests/FetcherTest.php b/core/scripts/composer/scaffold/tests/FetcherTest.php
new file mode 100644
index 0000000..ea822f9
--- /dev/null
+++ b/core/scripts/composer/scaffold/tests/FetcherTest.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace DrupalComposer\DrupalScaffold\Tests;
+
+use Composer\IO\NullIO;
+use Composer\Util\Filesystem;
+use Composer\Util\RemoteFilesystem;
+use DrupalComposer\DrupalScaffold\FileFetcher;
+use PHPUnit\Framework\TestCase;
+
+class FetcherTest extends TestCase {
+
+  /**
+   * @var \Composer\Util\Filesystem
+   */
+  protected $fs;
+
+  /**
+   * @var string
+   */
+  protected $tmpDir;
+
+  /**
+   * @var string
+   */
+  protected $rootDir;
+
+  /**
+   * @var string
+   */
+  protected $tmpReleaseTag;
+
+  /**
+   * SetUp test.
+   */
+  public function setUp() {
+    $this->rootDir = realpath(realpath(__DIR__ . '/..'));
+
+    // Prepare temp directory.
+    $this->fs = new Filesystem();
+    $this->tmpDir = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'drupal-scaffold';
+    $this->ensureDirectoryExistsAndClear($this->tmpDir);
+
+    chdir($this->tmpDir);
+  }
+
+  /**
+   * Makes sure the given directory exists and has no content.
+   *
+   * @param string $directory
+   */
+  protected function ensureDirectoryExistsAndClear($directory) {
+    if (is_dir($directory)) {
+      $this->fs->removeDirectory($directory);
+    }
+    mkdir($directory, 0777, TRUE);
+  }
+
+  public function testFetch() {
+    $fetcher = new FileFetcher(new RemoteFilesystem(new NullIO()), 'https://cgit.drupalcode.org/drupal/plain/{path}?h={version}', new NullIO());
+    $fetcher->setFilenames([
+      '.htaccess' => '.htaccess',
+      'sites/default/default.settings.php' => 'sites/default/default.settings.php',
+    ]);
+    $fetcher->fetch('8.1.1', $this->tmpDir, TRUE);
+    $this->assertFileExists($this->tmpDir . '/.htaccess');
+    $this->assertFileExists($this->tmpDir . '/sites/default/default.settings.php');
+  }
+
+  public function testInitialFetch() {
+    $fetcher = new FileFetcher(new RemoteFilesystem(new NullIO()), 'https://cgit.drupalcode.org/drupal/plain/{path}?h={version}', new NullIO());
+    $fetcher->setFilenames([
+      'sites/default/default.settings.php' => 'sites/default/settings.php',
+    ]);
+    $fetcher->fetch('8.1.1', $this->tmpDir, FALSE);
+    $this->assertFileExists($this->tmpDir . '/sites/default/settings.php');
+  }
+
+}
diff --git a/core/scripts/composer/scaffold/tests/PluginTest.php b/core/scripts/composer/scaffold/tests/PluginTest.php
new file mode 100644
index 0000000..9da8308
--- /dev/null
+++ b/core/scripts/composer/scaffold/tests/PluginTest.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace DrupalComposer\DrupalScaffold\Tests;
+
+use Composer\Util\Filesystem;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Tests composer plugin functionality.
+ */
+class PluginTest extends TestCase {
+
+  /**
+   * @var \Composer\Util\Filesystem
+   */
+  protected $fs;
+
+  /**
+   * @var string
+   */
+  protected $tmpDir;
+
+  /**
+   * @var string
+   */
+  protected $rootDir;
+
+  /**
+   * SetUp test.
+   */
+  public function setUp() {
+    $this->rootDir = realpath(realpath(__DIR__ . '/..'));
+
+    // Prepare temp directory.
+    $this->fs = new Filesystem();
+    $this->tmpDir = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'drupal-scaffold';
+    $this->ensureDirectoryExistsAndClear($this->tmpDir);
+    $this->writeComposerJSON();
+
+    chdir($this->tmpDir);
+  }
+
+  /**
+   * TearDown.
+   *
+   * @return void
+   */
+  public function tearDown() {
+    $this->fs->removeDirectory($this->tmpDir);
+  }
+
+  /**
+   * Tests a simple composer install without core, but adding core later.
+   */
+  public function testComposerInstallAndUpdate() {
+    $exampleScaffoldFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'index.php';
+    $this->assertFileNotExists($exampleScaffoldFile, 'Scaffold file should not be exist.');
+    $this->composer('install --no-dev --prefer-dist');
+    $this->assertFileExists($this->tmpDir . DIRECTORY_SEPARATOR . 'core', 'Drupal core is installed.');
+    $this->assertFileExists($exampleScaffoldFile, 'Scaffold file should be automatically installed.');
+    $this->fs->remove($exampleScaffoldFile);
+    $this->assertFileNotExists($exampleScaffoldFile, 'Scaffold file should not be exist.');
+    $this->composer('drupal:scaffold');
+    $this->assertFileExists($exampleScaffoldFile, 'Scaffold file should be installed by "drupal:scaffold" command.');
+
+    // We touch a scaffold file, so we can check the file was modified after
+    // the scaffold update.
+    $version = '8.6.x-dev';
+    touch($exampleScaffoldFile);
+    $mtime_touched = filemtime($exampleScaffoldFile);
+    // Requiring a newer version triggers "composer update".
+    $this->composer('require --update-with-dependencies --prefer-dist --update-no-dev drupal/core:"' . $version . '"');
+    clearstatcache();
+    $mtime_after = filemtime($exampleScaffoldFile);
+    $this->assertNotEquals($mtime_after, $mtime_touched, 'Scaffold file was modified by composer update. (' . $version . ')');
+
+    // We touch a scaffold file, so we can check the file was modified by the
+    // custom command.
+    file_put_contents($exampleScaffoldFile, 1);
+    $this->composer('drupal:scaffold');
+    $this->assertNotEquals(file_get_contents($exampleScaffoldFile), 1, 'Scaffold file was modified by custom command.');
+  }
+
+  /**
+   * Writes the default composer json to the temp direcoty.
+   */
+  protected function writeComposerJSON() {
+    $json = json_encode($this->composerJSONDefaults(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+    // Write composer.json.
+    file_put_contents($this->tmpDir . '/composer.json', $json);
+  }
+
+  /**
+   * Provides the default composer.json data.
+   *
+   * @return array
+   */
+  protected function composerJSONDefaults() {
+    $package = json_decode(file_get_contents($this->rootDir . '/composer.json'), TRUE);
+    $package['dist'] = [
+      'type' => 'path',
+      'url' => $this->rootDir,
+    ];
+    $package['version'] = '999.0.' . time();
+    return [
+      'repositories' => [
+        [
+          'type' => 'package',
+          'package' => $package,
+        ],
+      ],
+      'require' => [
+        'composer/installers' => '^1.0.20',
+        'drupal/drupal-scaffold' => $package['version'],
+        'drupal/core' => '8.5.5',
+      ],
+      'minimum-stability' => 'dev',
+      'prefer-stable' => TRUE,
+    ];
+  }
+
+  /**
+   * Wrapper for the composer command.
+   *
+   * @param string $command
+   *   Composer command name, arguments and/or options.
+   */
+  protected function composer($command) {
+    chdir($this->tmpDir);
+    passthru(escapeshellcmd($this->rootDir . '/vendor/bin/composer ' . $command), $exit_code);
+    if ($exit_code !== 0) {
+      throw new \Exception('Composer returned a non-zero exit code');
+    }
+  }
+
+  /**
+   * Makes sure the given directory exists and has no content.
+   *
+   * @param string $directory
+   */
+  protected function ensureDirectoryExistsAndClear($directory) {
+    if (is_dir($directory)) {
+      $this->fs->removeDirectory($directory);
+    }
+    mkdir($directory, 0777, TRUE);
+  }
+
+}
