Esempio n. 1
0
 /**
  * {@inheritdoc}
  */
 public function listContents()
 {
     $files = array();
     foreach ($this->tar->listContent() as $file_data) {
         $files[] = $file_data['filename'];
     }
     return $files;
 }
Esempio n. 2
0
 /**
  * {@inheritdoc}
  */
 public function submitForm(array &$form, FormStateInterface $form_state)
 {
     global $config_directories, $install_state;
     $sync_directory = $form_state->getValue('sync_directory');
     if ($sync_directory != config_get_config_directory(CONFIG_SYNC_DIRECTORY)) {
         $settings['config_directories'][CONFIG_SYNC_DIRECTORY] = (object) array('value' => $sync_directory, 'required' => TRUE);
         drupal_rewrite_settings($settings);
         $config_directories[CONFIG_SYNC_DIRECTORY] = $sync_directory;
     }
     if ($path = $form_state->getValue('import_tarball')) {
         // Ensure that we have an empty directory if we're going.
         $sync = new FileStorage($sync_directory);
         $sync->deleteAll();
         try {
             $archiver = new ArchiveTar($path, 'gz');
             $files = array();
             foreach ($archiver->listContent() as $file) {
                 $files[] = $file['filename'];
             }
             $archiver->extractList($files, config_get_config_directory(CONFIG_SYNC_DIRECTORY));
             drupal_set_message($this->t('Your configuration files were successfully uploaded, ready for import.'));
         } catch (\Exception $e) {
             drupal_set_message($this->t('Could not extract the contents of the tar file. The error message is <em>@message</em>', array('@message' => $e->getMessage())), 'error');
         }
         drupal_unlink($path);
     }
     // Change the langcode to the site default langcode provided by the
     // configuration.
     $config_storage = new FileStorage(config_get_config_directory(CONFIG_SYNC_DIRECTORY));
     $install_state['parameters']['langcode'] = $config_storage->read('system.site')['langcode'];
 }
Esempio n. 3
0
 /**
  * Extracts the contents of the archive file into the config directory.
  *
  * @param DrupalStyle $io
  *   IO object to print messages.
  * @param string $archiveFile
  *   The archive file to extract
  * @param string $configDir
  *   The directory to extract the files into.
  *
  * @return \Drupal\Core\Archiver\ArchiveTar
  *   The initialised object.
  *
  * @throws \Exception
  *   If something went wrong during extraction.
  */
 private function extractArchive(DrupalStyle $io, $archiveFile, $configDir)
 {
     $archiveTar = new ArchiveTar($archiveFile, 'gz');
     $io->simple($this->trans('commands.config.import.messages.config_files_imported'));
     foreach ($archiveTar->listContent() as $file) {
         $io->info('[-] ' . $file['filename']);
     }
     try {
         $archiveTar->extract($configDir . '/');
     } catch (\Exception $e) {
         $io->error($e->getMessage());
         return;
     }
 }
 /**
  * {@inheritdoc}
  */
 public function submitForm(array &$form, FormStateInterface $form_state)
 {
     if ($path = $form_state->getValue('import_tarball')) {
         $this->configStorage->deleteAll();
         try {
             $archiver = new ArchiveTar($path, 'gz');
             $files = array();
             foreach ($archiver->listContent() as $file) {
                 $files[] = $file['filename'];
             }
             $archiver->extractList($files, config_get_config_directory(CONFIG_STAGING_DIRECTORY));
             drupal_set_message($this->t('Your configuration files were successfully uploaded, ready for import.'));
             $form_state->setRedirect('config.sync');
         } catch (\Exception $e) {
             drupal_set_message($this->t('Could not extract the contents of the tar file. The error message is <em>@message</em>', array('@message' => $e->getMessage())), 'error');
         }
         drupal_unlink($path);
     }
 }
 /**
  * {@inheritdoc}
  */
 protected function setUpSyncForm()
 {
     // Create a new sync directory.
     drupal_mkdir($this->sync_dir);
     // Extract the tarball into the sync directory.
     $archiver = new ArchiveTar($this->tarball, 'gz');
     $files = array();
     foreach ($archiver->listContent() as $file) {
         $files[] = $file['filename'];
     }
     $archiver->extractList($files, $this->sync_dir);
     // Change the user.settings::register so that we can test that
     // standard_install() does not override it.
     $sync = new FileStorage($this->sync_dir);
     $user_settings = $sync->read('user.settings');
     $user_settings['register'] = USER_REGISTER_ADMINISTRATORS_ONLY;
     $sync->write('user.settings', $user_settings);
     $this->drupalPostForm(NULL, array('sync_directory' => drupal_realpath($this->sync_dir)), 'Save and continue');
 }
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $config_file = $input->getArgument('config-file');
     $copy_only = $input->getOption('copy-only');
     try {
         $files = array();
         $archiver = new ArchiveTar($config_file, 'gz');
         $output->writeln($this->trans('commands.config.import.messages.config_files_imported'));
         foreach ($archiver->listContent() as $file) {
             $pathinfo = pathinfo($file['filename']);
             $files[$pathinfo['filename']] = $file['filename'];
             $output->writeln('[-] <info>' . $file['filename'] . '</info>');
         }
         $config_staging_dir = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
         try {
             $archiver->extract($config_staging_dir . '/');
         } catch (\Exception $e) {
             $output->writeln('[+] <error>' . $e->getMessage() . '</error>');
             return;
         }
         if ($copy_only) {
             $output->writeln(sprintf($this->trans('commands.config.import.messages.copied'), CONFIG_SYNC_DIRECTORY));
         } else {
             foreach ($files as $cofig_name => $filename) {
                 $config = $this->getConfigFactory()->getEditable($cofig_name);
                 $parser = new Parser();
                 $config_value = $parser->parse(file_get_contents($config_staging_dir . '/' . $filename));
                 $config->setData($config_value);
                 try {
                     $config->save();
                 } catch (\Exception $e) {
                     $output->writeln('[+] <error>' . $e->getMessage() . '</error>');
                     return;
                 }
             }
             $output->writeln(sprintf($this->trans('commands.config.import.messages.imported'), CONFIG_SYNC_DIRECTORY));
         }
     } catch (\Exception $e) {
         $output->writeln('[+] <error>' . $e->getMessage() . '</error>');
         return;
     }
 }
Esempio n. 7
0
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $io = new DrupalStyle($input, $output);
     $configFile = $input->getOption('file');
     $removeFiles = $input->getOption('remove-files');
     $configSyncDir = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
     if ($configFile) {
         $archiveTar = new ArchiveTar($configFile, 'gz');
         $io->simple($this->trans('commands.config.import.messages.config_files_imported'));
         foreach ($archiveTar->listContent() as $file) {
             $io->info('[-] ' . $file['filename']);
         }
         try {
             $archiveTar->extract($configSyncDir . '/');
         } catch (\Exception $e) {
             $io->error($e->getMessage());
             return;
         }
     }
     $finder = new Finder();
     $finder->in($configSyncDir);
     $finder->name("*.yml");
     foreach ($finder as $configFile) {
         $configName = $configFile->getBasename('.yml');
         $configFilePath = sprintf('%s/%s', $configSyncDir, $configFile->getBasename());
         $config = $this->getConfigFactory()->getEditable($configName);
         $parser = new Parser();
         $configData = $parser->parse(file_get_contents($configFilePath));
         $config->setData($configData);
         if ($removeFiles) {
             file_unmanaged_delete($configFilePath);
         }
         try {
             $config->save();
         } catch (\Exception $e) {
             $io->error($e->getMessage());
             return;
         }
     }
     $io->success(sprintf($this->trans('commands.config.import.messages.imported'), CONFIG_SYNC_DIRECTORY));
 }
 /**
  * Tests an export and import of collections.
  */
 public function testExportImportCollections()
 {
     /** @var \Drupal\Core\Config\StorageInterface $active_storage */
     $active_storage = \Drupal::service('config.storage');
     $test1_storage = $active_storage->createCollection('collection.test1');
     $test1_storage->write('config_test.create', array('foo' => 'bar'));
     $test1_storage->write('config_test.update', array('foo' => 'bar'));
     $test2_storage = $active_storage->createCollection('collection.test2');
     $test2_storage->write('config_test.another_create', array('foo' => 'bar'));
     $test2_storage->write('config_test.another_update', array('foo' => 'bar'));
     // Export the configuration.
     $this->drupalPostForm('admin/config/development/configuration/full/export', array(), 'Export');
     $this->tarball = $this->drupalGetContent();
     $filename = file_directory_temp() . '/' . $this->randomName();
     file_put_contents($filename, $this->tarball);
     // Set up the active storage collections to test import.
     $test1_storage->delete('config_test.create');
     $test1_storage->write('config_test.update', array('foo' => 'baz'));
     $test1_storage->write('config_test.delete', array('foo' => 'bar'));
     $test2_storage->delete('config_test.another_create');
     $test2_storage->write('config_test.another_update', array('foo' => 'baz'));
     $test2_storage->write('config_test.another_delete', array('foo' => 'bar'));
     // Create a snapshot.
     $snapshot_storage = \Drupal::service('config.storage.snapshot');
     \Drupal::service('config.manager')->createSnapshot($active_storage, $snapshot_storage);
     // Ensure that the snapshot has the expected collection data before import.
     $test1_snapshot = $snapshot_storage->createCollection('collection.test1');
     $data = $test1_snapshot->read('config_test.delete');
     $this->assertEqual($data, array('foo' => 'bar'), 'The config_test.delete in collection.test1 exists in the snapshot storage.');
     $data = $test1_snapshot->read('config_test.update');
     $this->assertEqual($data, array('foo' => 'baz'), 'The config_test.update in collection.test1 exists in the snapshot storage.');
     $this->assertFalse($test1_snapshot->read('config_test.create'), 'The config_test.create in collection.test1 does not exist in the snapshot storage.');
     $test2_snapshot = $snapshot_storage->createCollection('collection.test2');
     $data = $test2_snapshot->read('config_test.another_delete');
     $this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_delete in collection.test2 exists in the snapshot storage.');
     $data = $test2_snapshot->read('config_test.another_update');
     $this->assertEqual($data, array('foo' => 'baz'), 'The config_test.another_update in collection.test2 exists in the snapshot storage.');
     $this->assertFalse($test2_snapshot->read('config_test.another_create'), 'The config_test.another_create in collection.test2 does not exist in the snapshot storage.');
     // Create the tar contains the expected contect for the collections.
     $tar = new ArchiveTar($filename, 'gz');
     $content_list = $tar->listContent();
     // Convert the list of files into something easy to search.
     $files = array();
     foreach ($content_list as $file) {
         $files[] = $file['filename'];
     }
     $this->assertTrue(in_array('collection/test1/config_test.create.yml', $files), 'Config export contains collection/test1/config_test.create.yml.');
     $this->assertTrue(in_array('collection/test2/config_test.another_create.yml', $files), 'Config export contains collection/test2/config_test.another_create.yml.');
     $this->assertTrue(in_array('collection/test1/config_test.update.yml', $files), 'Config export contains collection/test1/config_test.update.yml.');
     $this->assertTrue(in_array('collection/test2/config_test.another_update.yml', $files), 'Config export contains collection/test2/config_test.another_update.yml.');
     $this->assertFalse(in_array('collection/test1/config_test.delete.yml', $files), 'Config export does not contain collection/test1/config_test.delete.yml.');
     $this->assertFalse(in_array('collection/test2/config_test.another_delete.yml', $files), 'Config export does not contain collection/test2/config_test.another_delete.yml.');
     $this->drupalPostForm('admin/config/development/configuration/full/import', array('files[import_tarball]' => $filename), 'Upload');
     // Verify that there are configuration differences to import.
     $this->drupalGet('admin/config/development/configuration');
     $this->assertNoText(t('There are no configuration changes.'));
     $this->assertText(t('!collection configuration collection', array('!collection' => 'collection.test1')));
     $this->assertText(t('!collection configuration collection', array('!collection' => 'collection.test2')));
     $this->assertText('config_test.create');
     $this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test1/config_test.create');
     $this->assertText('config_test.update');
     $this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test1/config_test.update');
     $this->assertText('config_test.delete');
     $this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test1/config_test.delete');
     $this->assertText('config_test.another_create');
     $this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test2/config_test.another_create');
     $this->assertText('config_test.another_update');
     $this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test2/config_test.another_update');
     $this->assertText('config_test.another_delete');
     $this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test2/config_test.another_delete');
     $this->drupalPostForm(NULL, array(), 'Import all');
     $this->assertText(t('There are no configuration changes.'));
     // Test data in collections.
     $data = $test1_storage->read('config_test.create');
     $this->assertEqual($data, array('foo' => 'bar'), 'The config_test.create in collection.test1 has been created.');
     $data = $test1_storage->read('config_test.update');
     $this->assertEqual($data, array('foo' => 'bar'), 'The config_test.update in collection.test1 has been updated.');
     $this->assertFalse($test1_storage->read('config_test.delete'), 'The config_test.delete in collection.test1 has been deleted.');
     $data = $test2_storage->read('config_test.another_create');
     $this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_create in collection.test2 has been created.');
     $data = $test2_storage->read('config_test.another_update');
     $this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_update in collection.test2 has been updated.');
     $this->assertFalse($test2_storage->read('config_test.another_delete'), 'The config_test.another_delete in collection.test2 has been deleted.');
     // Ensure that the snapshot has been updated with the collection data.
     $snapshot_storage = \Drupal::service('config.storage.snapshot');
     $test1_snapshot = $snapshot_storage->createCollection('collection.test1');
     $data = $test1_snapshot->read('config_test.create');
     $this->assertEqual($data, array('foo' => 'bar'), 'The config_test.create in collection.test1 has been created in the snapshot storage.');
     $data = $test1_snapshot->read('config_test.update');
     $this->assertEqual($data, array('foo' => 'bar'), 'The config_test.update in collection.test1 has been updated in the snapshot storage.');
     $this->assertFalse($test1_snapshot->read('config_test.delete'), 'The config_test.delete in collection.test1 does not exist in the snapshot storage.');
     $test2_snapshot = $snapshot_storage->createCollection('collection.test2');
     $data = $test2_snapshot->read('config_test.another_create');
     $this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_create in collection.test2 has been created in the snapshot storage.');
     $data = $test2_snapshot->read('config_test.another_update');
     $this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_update in collection.test2 has been updated in the snapshot storage.');
     $this->assertFalse($test2_snapshot->read('config_test.another_delete'), 'The config_test.another_delete in collection.test2 does not exist in the snapshot storage.');
 }
 /**
  * Tests creating a feature via UI and download it.
  */
 public function testCreateFeaturesUI()
 {
     $feature_name = 'test_feature2';
     $admin_user = $this->createUser(['administer site configuration', 'export configuration', 'administer modules']);
     $this->drupalLogin($admin_user);
     $this->drupalPlaceBlock('local_actions_block');
     $this->drupalGet('admin/config/development/features');
     $this->clickLink('Create new feature');
     $this->assertResponse(200);
     $edit = ['name' => 'Test feature', 'machine_name' => $feature_name, 'description' => 'Test description: <strong>giraffe</strong>', 'version' => '8.x-1.0', 'system_simple[sources][selected][system.theme]' => TRUE, 'system_simple[sources][selected][user.settings]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Download Archive'));
     $this->assertResponse(200);
     $archive = $this->getRawContent();
     $filename = tempnam($this->tempFilesDirectory, 'feature');
     file_put_contents($filename, $archive);
     $archive = new ArchiveTar($filename);
     $files = $archive->listContent();
     $this->assertEqual(4, count($files));
     $this->assertEqual($feature_name . '/' . $feature_name . '.info.yml', $files[0]['filename']);
     $this->assertEqual($feature_name . '/' . $feature_name . '.features.yml', $files[1]['filename']);
     $this->assertEqual($feature_name . '/config/install/system.theme.yml', $files[2]['filename']);
     $this->assertEqual($feature_name . '/config/install/user.settings.yml', $files[3]['filename']);
     // Ensure that the archive contains the expected values.
     $info_filename = tempnam($this->tempFilesDirectory, 'feature');
     file_put_contents($info_filename, $archive->extractInString($feature_name . '/' . $feature_name . '.info.yml'));
     $features_info_filename = tempnam($this->tempFilesDirectory, 'feature');
     file_put_contents($features_info_filename, $archive->extractInString($feature_name . '/' . $feature_name . '.features.yml'));
     /** @var \Drupal\Core\Extension\InfoParser $info_parser */
     $info_parser = \Drupal::service('info_parser');
     $parsed_info = $info_parser->parse($info_filename);
     $this->assertEqual('Test feature', $parsed_info['name']);
     $parsed_features_info = Yaml::decode(file_get_contents($features_info_filename));
     $this->assertEqual(['required' => ['system.theme', 'user.settings']], $parsed_features_info);
     $archive->extract(\Drupal::service('kernel')->getSitePath() . '/modules');
     $module_path = \Drupal::service('kernel')->getSitePath() . '/modules/' . $feature_name;
     // Ensure that the features listing renders the right content.
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertLink('Test feature');
     $this->assertEqual($feature_name, (string) $tr->children()[2]);
     $description_column = (string) $tr->children()[3]->asXml();
     $this->assertTrue(strpos($description_column, 'system.theme') !== FALSE);
     $this->assertTrue(strpos($description_column, 'user.settings') !== FALSE);
     $this->assertRaw('Test description: <strong>giraffe</strong>');
     $this->assertEqual('Uninstalled', (string) $tr->children()[5]);
     $this->assertEqual('', (string) $tr->children()[6]);
     // Remove one and add new configuration.
     $this->clickLink('Test feature');
     $edit = ['system_simple[included][system.theme]' => FALSE, 'user_role[sources][selected][authenticated]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Write'));
     $info_filename = $module_path . '/' . $feature_name . '.info.yml';
     $parsed_info = $info_parser->parse($info_filename);
     $this->assertEqual('Test feature', $parsed_info['name']);
     $features_info_filename = $module_path . '/' . $feature_name . '.features.yml';
     $parsed_features_info = Yaml::decode(file_get_contents($features_info_filename));
     $this->assertEqual(['required' => ['user.settings', 'user.role.authenticated']], $parsed_features_info);
     $this->drupalGet('admin/modules');
     $edit = ['modules[Other][' . $feature_name . '][enable]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Install'));
     // Check that the feature is listed as installed.
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertEqual('Installed', (string) $tr->children()[5]);
     // Check that a config change results in a feature marked as changed.
     \Drupal::configFactory()->getEditable('user.settings')->set('anonymous', 'Anonymous giraffe')->save();
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
     // Uninstall module.
     $this->drupalPostForm('admin/modules/uninstall', ['uninstall[' . $feature_name . ']' => TRUE], $this->t('Uninstall'));
     $this->drupalPostForm(NULL, [], $this->t('Uninstall'));
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
     $this->clickLink($this->t('Changed'));
     $this->assertRaw('<td class="diff-context diff-deletedline">anonymous : Anonymous <span class="diffchange">giraffe</span></td>');
     $this->assertRaw('<td class="diff-context diff-addedline">anonymous : Anonymous</td>');
     $this->drupalGet('admin/modules');
     $edit = ['modules[Other][' . $feature_name . '][enable]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Install'));
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertEqual('Installed', (string) $tr->children()[5]);
     // Ensure that the changed config got overridden.
     $this->assertEqual('Anonymous', \Drupal::config('user.settings')->get('anonymous'));
     // Change the value, export and ensure that its not shown as changed.
     \Drupal::configFactory()->getEditable('user.settings')->set('anonymous', 'Anonymous giraffe')->save();
     // Ensure that exporting this change will result in an unchanged feature.
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
     $this->clickLink('Test feature');
     $this->drupalPostForm(NULL, [], $this->t('Write'));
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertEqual('Installed', (string) $tr->children()[5]);
 }
Esempio n. 10
0
 /**
  * @covers \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite
  */
 public function testExportWrite()
 {
     // Set a fake drupal root, so the testbot can also write into it.
     vfsStream::setup('drupal');
     \Drupal::getContainer()->set('app.root', 'vfs://drupal');
     $this->featuresManager->setRoot('vfs://drupal');
     $package = $this->featuresManager->getPackage(self::PACKAGE_NAME);
     // Find out where package will be exported
     list($full_name, $path) = $this->featuresManager->getExportInfo($package, $this->assigner->getBundle());
     $path = 'vfs://drupal/' . $path . '/' . $full_name;
     if (file_exists($path)) {
         file_unmanaged_delete_recursive($path);
     }
     $this->assertFalse(file_exists($path), 'Package directory already exists.');
     $this->generator->generatePackages('write', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
     $info_file_uri = $path . '/' . self::PACKAGE_NAME . '.info.yml';
     $this->assertTrue(file_exists($path), 'Package directory was not generated.');
     $this->assertTrue(file_exists($info_file_uri), 'Package info.yml not generated.');
     $this->assertTrue(file_exists($path . '/config/install'), 'Package config/install not generated.');
     $this->assertTrue(file_exists($path . '/config/install/system.site.yml'), 'Config.yml not exported.');
     $expected_info = ["name" => "My test package", "type" => "module", "core" => "8.x"];
     $info = Yaml::decode(file_get_contents($info_file_uri));
     $this->assertEquals($expected_info, $info, 'Incorrect info file generated');
     // Now, add stuff to the feature and re-export to ensure it is preserved
     // Add a dependency to the package itself to see that it gets exported.
     $package->setDependencies(['user']);
     $this->featuresManager->setPackage($package);
     // Add dependency and custom key to the info file to simulate manual edit.
     $info['dependencies'] = ['node'];
     $info['mykey'] = "test value";
     $info_contents = Yaml::encode($info);
     file_put_contents($info_file_uri, $info_contents);
     // Add an extra file that should be retained.
     $css_file = $path . '/' . self::PACKAGE_NAME . '.css';
     $file_contents = "This is a dummy file";
     file_put_contents($css_file, $file_contents);
     // Add a config file that should be removed since it's not part of the
     // feature.
     $config_file = $path . '/config/install/node.type.mytype.yml';
     file_put_contents($config_file, $file_contents);
     $this->generator->generatePackages('write', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
     $this->assertTrue(file_exists($info_file_uri), 'Package info.yml not generated.');
     $expected_info = ["name" => "My test package", "type" => "module", "core" => "8.x", "dependencies" => ["node", "user"], "mykey" => "test value"];
     $info = Yaml::decode(file_get_contents($info_file_uri));
     $this->assertEquals($expected_info, $info, 'Incorrect info file generated');
     $this->assertTrue(file_exists($css_file), 'Extra file was not retained.');
     $this->assertFalse(file_exists($config_file), 'Config directory was not cleaned.');
     $this->assertEquals($file_contents, file_get_contents($css_file), 'Extra file contents not retained');
     // Next, test that generating an Archive picks up the extra files.
     $filename = file_directory_temp() . '/' . self::PACKAGE_NAME . '.tar.gz';
     if (file_exists($filename)) {
         unlink($filename);
     }
     $this->assertFalse(file_exists($filename), 'Archive file already exists.');
     $this->generator->generatePackages('archive', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
     $this->assertTrue(file_exists($filename), 'Archive file was not generated.');
     $archive = new ArchiveTar($filename);
     $files = $archive->listContent();
     $this->assertEquals(4, count($files));
     $this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.info.yml', $files[0]['filename']);
     $this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.features.yml', $files[1]['filename']);
     $this->assertEquals(self::PACKAGE_NAME . '/config/install/system.site.yml', $files[2]['filename']);
     $this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.css', $files[3]['filename']);
     $expected_info = ["name" => "My test package", "type" => "module", "core" => "8.x", "dependencies" => ["node", "user"], "mykey" => "test value"];
     $info = Yaml::decode($archive->extractInString(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.info.yml'));
     $this->assertEquals($expected_info, $info, 'Incorrect info file generated');
     $this->assertEquals($file_contents, $archive->extractInString(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.css'), 'Extra file contents not retained');
 }