Skip to content

Commit 6384dc7

Browse files
committed
Add missing project command
1 parent 8f69838 commit 6384dc7

File tree

1 file changed

+375
-0
lines changed

1 file changed

+375
-0
lines changed

src/Command/Misc/Project.php

Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
<?php
2+
3+
namespace DrupalCodeGenerator\Command\Misc;
4+
5+
use DrupalCodeGenerator\Command\Generator;
6+
use Symfony\Component\Console\Question\Question;
7+
8+
/**
9+
* Implements misc:project command.
10+
*
11+
* Inspired by drupal-composer/drupal-project.
12+
*/
13+
class Project extends Generator {
14+
15+
protected $name = 'misc:project';
16+
protected $description = 'Generates a composer project';
17+
protected $alias = 'project';
18+
19+
const DRUPAL_DEFAULT_VERSION = '~8.7.0';
20+
21+
/**
22+
* {@inheritdoc}
23+
*/
24+
protected function generate() :void {
25+
26+
$vars = &$this->vars;
27+
28+
$name_validator = function (?string $value) :?string {
29+
$value = self::validateRequired($value);
30+
if (!preg_match('#[^/]+/[^/]+$#i', $value)) {
31+
throw new \UnexpectedValueException('The value is not correct project name.');
32+
}
33+
return $value;
34+
};
35+
$vars['name'] = $this->ask('Project name (vendor/name)', NULL, $name_validator);
36+
37+
$vars['description'] = $this->ask('Description');
38+
39+
$license_question = new Question('License', 'GPL-2.0-or-later');
40+
// @see https://getcomposer.org/doc/04-schema.md#license
41+
$licenses = [
42+
'Apache-2.0',
43+
'BSD-2-Clause',
44+
'BSD-3-Clause',
45+
'BSD-4-Clause',
46+
'GPL-2.0-only',
47+
'GPL-2.0-or-later',
48+
'GPL-3.0-only',
49+
'GPL-3.0-or-later',
50+
'LGPL-2.1-onl',
51+
'LGPL-2.1-or-later',
52+
'LGPL-3.0-only',
53+
'LGPL-3.0-or-later',
54+
'MIT',
55+
'proprietary',
56+
];
57+
$license_question->setAutocompleterValues($licenses);
58+
$vars['license'] = $this->io->askQuestion($license_question);
59+
60+
// Suggest most typical document roots.
61+
$document_roots = [
62+
'docroot',
63+
'web',
64+
'www',
65+
'public_html',
66+
'public',
67+
'htdocs',
68+
'httpdocs',
69+
'html',
70+
];
71+
$document_root_question = new Question('Document root directory, type single dot to use Composer root', 'docroot');
72+
$document_root_normalizer = function (string $value) :string {
73+
return $value == '.' ? '' : rtrim($value, '/');
74+
};
75+
$document_root_question->setNormalizer($document_root_normalizer);
76+
$document_root_question->setAutocompleterValues($document_roots);
77+
$vars['document_root'] = $this->io->askQuestion($document_root_question);
78+
79+
$vars['php'] = $this->ask('PHP version', '>=' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION);
80+
$vars['drupal'] = $this->ask('Drupal version', self::DRUPAL_DEFAULT_VERSION);
81+
$vars['drupal_core_strict'] = $this->confirm('Would you like to get the same versions of Drupal core\'s dependencies as in Drupal core\'s composer.lock file?', FALSE);
82+
83+
$sections = ['require', 'require-dev'];
84+
85+
$vars['drush'] = NULL;
86+
if ($vars['drush'] = $this->confirm('Would you like to install Drush?')) {
87+
$drush_question = new Question('Drush installation (require|require-dev)', 'require');
88+
$drush_question->setValidator([__CLASS__, 'validateSection']);
89+
$drush_question->setAutocompleterValues($sections);
90+
$vars['drush'] = $this->io->askQuestion($drush_question);
91+
}
92+
93+
$vars['drupal_console'] = NULL;
94+
if ($this->confirm('Would you like to install Drupal Console?', !$vars['drush'])) {
95+
$dc_question = new Question('Drupal Console installation (require|require-dev)', 'require-dev');
96+
$dc_question->setValidator([__CLASS__, 'validateSection']);
97+
$dc_question->setAutocompleterValues($sections);
98+
$vars['drupal_console'] = $this->io->askQuestion($dc_question);
99+
}
100+
101+
$vars['composer_patches'] = $this->confirm('Would you like to install Composer patches plugin?');
102+
$vars['composer_merge'] = $this->confirm('Would you like to install Composer merge plugin?', FALSE);
103+
$vars['behat'] = $this->confirm('Would you like to create Behat tests?', FALSE);
104+
$vars['env'] = $this->confirm('Would you like to load environment variables from .env files?', FALSE);
105+
$vars['asset_packagist'] = $this->confirm('Would you like to add asset-packagist repository?', FALSE);
106+
107+
// Add ending slash if the path is not empty.
108+
if ($vars['document_root_path'] = $vars['document_root']) {
109+
$vars['document_root_path'] .= '/';
110+
}
111+
112+
$this->addFile('composer.json')->content(self::buildComposerJson($vars));
113+
114+
$this->addFile('.gitignore', 'misc/_project/gitignore');
115+
$this->addFile('phpcs.xml', 'misc/_project/phpcs.xml');
116+
$this->addFile('scripts/composer/create_required_files.php', 'misc/_project/scripts/composer/create_required_files.php');
117+
118+
if ($vars['behat']) {
119+
$this->addFile('tests/behat/behat.yml', 'misc/_project/tests/behat/behat.yml');
120+
$this->addFile('tests/behat/local.behat.yml', 'misc/_project/tests/behat/local.behat.yml');
121+
$this->addFile('tests/behat/bootstrap/BaseContext.php', 'misc/_project/tests/behat/bootstrap/BaseContext.php');
122+
$this->addFile('tests/behat/bootstrap/ExampleContext.php', 'misc/_project/tests/behat/bootstrap/ExampleContext.php');
123+
$this->addFile('tests/behat/features/example/user_forms.feature', 'misc/_project/tests/behat/features/example/user_forms.feature');
124+
}
125+
126+
if ($vars['env']) {
127+
$this->addFile('.env.example', 'misc/_project/env.example');
128+
$this->addFile('load.environment.php', 'misc/_project/load.environment.php');
129+
}
130+
131+
if ($vars['document_root']) {
132+
$this->addDirectory('config/sync');
133+
}
134+
135+
if ($vars['drush']) {
136+
$this->addFile('drush/drush.yml', 'misc/_project/drush/drush.yml');
137+
$this->addFile('drush/Commands/PolicyCommands.php', 'misc/_project/drush/Commands/PolicyCommands.php');
138+
$this->addFile('drush/sites/self.site.yml', 'misc/_project/drush/sites/self.site.yml');
139+
$this->addFile('scripts/sync-site.sh', 'misc/_project/scripts/sync-site.sh')->mode(0544);
140+
}
141+
142+
$this->addFile('patches/.keep')->content('');
143+
$this->addDirectory('{document_root_path}modules/contrib');
144+
$this->addDirectory('{document_root_path}modules/custom');
145+
$this->addDirectory('{document_root_path}modules/custom');
146+
$this->addDirectory('{document_root_path}libraries');
147+
}
148+
149+
/**
150+
* {@inheritdoc}
151+
*/
152+
protected function printSummary(array $dumped_assets): void {
153+
parent::printSummary($dumped_assets);
154+
155+
$message = [
156+
'Next steps:',
157+
'–––––––––––',
158+
'1. Review generated files',
159+
'2. Run <comment>composer install</comment> command',
160+
'3. Install Drupal',
161+
];
162+
$this->io->text($message);
163+
$this->io->newLine();
164+
}
165+
166+
/**
167+
* Builds composer.json file.
168+
*
169+
* @param array $vars
170+
* Collected variables.
171+
*
172+
* @return string
173+
* Encoded JSON content.
174+
*/
175+
protected static function buildComposerJson(array $vars) :string {
176+
177+
$document_root_path = $vars['document_root_path'];
178+
179+
$composer_json = [];
180+
181+
$composer_json['name'] = $vars['name'];
182+
$composer_json['description'] = (string) $vars['description'];
183+
$composer_json['type'] = 'project';
184+
$composer_json['license'] = $vars['license'];
185+
186+
$composer_json['repositories'][] = [
187+
'type' => 'composer',
188+
'url' => 'https://packages.drupal.org/8',
189+
];
190+
if ($vars['asset_packagist']) {
191+
$composer_json['repositories'][] = [
192+
'type' => 'composer',
193+
'url' => 'https://asset-packagist.org',
194+
];
195+
}
196+
197+
$require = [];
198+
$require_dev = [];
199+
200+
self::addPackage($require, 'drupal/core');
201+
$require['drupal/core'] = $vars['drupal'];
202+
self::addPackage($require, 'composer/installers');
203+
self::addPackage($require, 'drupal-composer/drupal-scaffold');
204+
self::addPackage($require, 'zaporylie/composer-drupal-optimizations');
205+
$require_dev['webflo/drupal-core-require-dev'] = $vars['drupal'];
206+
207+
if ($vars['asset_packagist']) {
208+
self::addPackage($require, 'oomphinc/composer-installers-extender');
209+
}
210+
211+
if ($vars['drupal_core_strict']) {
212+
$require['webflo/drupal-core-strict'] = $vars['drupal'];
213+
}
214+
215+
if ($vars['drush']) {
216+
$vars['drush'] == 'require'
217+
? self::addPackage($require, 'drush/drush')
218+
: self::addPackage($require_dev, 'drush/drush');
219+
}
220+
221+
if ($vars['drupal_console']) {
222+
$vars['drupal_console'] == 'require'
223+
? self::addPackage($require, 'drupal/console')
224+
: self::addPackage($require_dev, 'drupal/console');
225+
}
226+
227+
if ($vars['composer_patches']) {
228+
self::addPackage($require, 'cweagans/composer-patches');
229+
}
230+
231+
if ($vars['composer_merge']) {
232+
self::addPackage($require, 'wikimedia/composer-merge-plugin');
233+
}
234+
235+
if ($vars['behat']) {
236+
// Behat and Mink drivers are Drupal core dev dependencies.
237+
self::addPackage($require_dev, 'drupal/drupal-extension');
238+
}
239+
240+
if ($vars['env']) {
241+
self::addPackage($require, 'symfony/dotenv');
242+
}
243+
244+
$composer_json['require'] = [
245+
'php' => $vars['php'],
246+
'ext-curl' => '*',
247+
'ext-gd' => '*',
248+
'ext-json' => '*',
249+
];
250+
ksort($require);
251+
$composer_json['require'] += $require;
252+
253+
ksort($require_dev);
254+
$composer_json['require-dev'] = $require_dev;
255+
256+
// PHPUnit is core dev dependency.
257+
$composer_json['scripts']['phpunit'] = 'phpunit --colors=always --configuration ' . $document_root_path . 'core ' . $document_root_path . 'modules/custom';
258+
if ($vars['behat']) {
259+
$composer_json['scripts']['behat'] = 'behat --colors --config=tests/behat/local.behat.yml';
260+
}
261+
$composer_json['scripts']['phpcs'] = 'phpcs --standard=phpcs.xml';
262+
$composer_json['scripts']['post-install-cmd'][] = '@php ./scripts/composer/create_required_files.php';
263+
$composer_json['scripts']['post-update-cmd'][] = '@php ./scripts/composer/create_required_files.php';
264+
265+
$composer_json['minimum-stability'] = 'dev';
266+
$composer_json['prefer-stable'] = TRUE;
267+
268+
$composer_json['config'] = [
269+
'sort-packages' => TRUE,
270+
'bin-dir' => 'bin',
271+
];
272+
273+
if ($vars['env']) {
274+
$composer_json['autoload']['files'][] = 'load.environment.php';
275+
}
276+
277+
if ($vars['composer_patches']) {
278+
$composer_json['extra']['composer-exit-on-patch-failure'] = TRUE;
279+
}
280+
281+
if ($vars['asset_packagist']) {
282+
$composer_json['extra']['installer-types'] = [
283+
'bower-asset',
284+
'npm-asset',
285+
];
286+
}
287+
$composer_json['extra']['installer-paths'] = [
288+
$document_root_path . 'core' => ['type:drupal-core'],
289+
$document_root_path . 'libraries/{$name}' => ['type:drupal-library'],
290+
$document_root_path . 'modules/contrib/{$name}' => ['type:drupal-module'],
291+
$document_root_path . 'themes/{$name}' => ['type:drupal-theme'],
292+
'drush/{$name}' => ['type:drupal-drush'],
293+
];
294+
if ($vars['asset_packagist']) {
295+
$composer_json['extra']['installer-paths'][$document_root_path . 'libraries/{$name}'][] = 'type:bower-asset';
296+
$composer_json['extra']['installer-paths'][$document_root_path . 'libraries/{$name}'][] = 'type:npm-asset';
297+
}
298+
299+
$composer_json['extra']['drupal-scaffold']['excludes'] = [
300+
'.csslintrc',
301+
'.editorconfig',
302+
'.eslintignore',
303+
'.eslintrc.json',
304+
'.gitattributes',
305+
'.ht.router.php',
306+
'.htaccess',
307+
'robots.txt',
308+
'update.php',
309+
'web.config',
310+
];
311+
// Initial files are created but never updated.
312+
$composer_json['extra']['drupal-scaffold']['initial'] = [
313+
'.htaccess' => '.htaccess',
314+
'robots.txt' => 'robots.txt',
315+
];
316+
317+
// Move these files to Composer root.
318+
if ($vars['document_root']) {
319+
$composer_json['extra']['drupal-scaffold']['initial']['.editorconfig'] = '../.editorconfig';
320+
$composer_json['extra']['drupal-scaffold']['initial']['.gitattributes'] = '../.gitattributes';
321+
}
322+
ksort($composer_json['extra']['drupal-scaffold']['initial']);
323+
324+
if ($vars['composer_merge']) {
325+
$composer_json['extra']['merge-plugin'] = [
326+
'include' => [
327+
$document_root_path . 'modules/custom/*/composer.json',
328+
],
329+
'recurse' => TRUE,
330+
];
331+
}
332+
333+
return json_encode($composer_json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
334+
}
335+
336+
/**
337+
* Requires a given package.
338+
*
339+
* @param array $section
340+
* Section for the package (require|require-dev)
341+
* @param string $package
342+
* A package to be added.
343+
*
344+
* @todo Find a way to track versions automatically.
345+
*/
346+
protected static function addPackage(array &$section, $package) :void {
347+
$versions = [
348+
'composer/installers' => '^1.4',
349+
'cweagans/composer-patches' => '^1.6',
350+
'drupal-composer/drupal-scaffold' => '^2.5',
351+
'drupal/console' => '^1.0',
352+
'drupal/core' => self::DRUPAL_DEFAULT_VERSION,
353+
'drupal/drupal-extension' => '^3.4',
354+
'drush/drush' => '^9.6',
355+
'oomphinc/composer-installers-extender' => '^1.1',
356+
'symfony/dotenv' => '^3.4',
357+
'webflo/drupal-core-require-dev' => self::DRUPAL_DEFAULT_VERSION,
358+
'webflo/drupal-core-strict' => self::DRUPAL_DEFAULT_VERSION,
359+
'wikimedia/composer-merge-plugin' => '^1.4',
360+
'zaporylie/composer-drupal-optimizations' => '^1.1',
361+
];
362+
$section[$package] = $versions[$package];
363+
}
364+
365+
/**
366+
* Validates require/require-dev answer.
367+
*/
368+
public static function validateSection(?string $value) :?string {
369+
if ($value != 'require' && $value != 'require-dev') {
370+
throw new \UnexpectedValueException('The value should be either "require" or "require-dev".');
371+
}
372+
return $value;
373+
}
374+
375+
}

0 commit comments

Comments
 (0)