Skip to content

Instantly share code, notes, and snippets.

@cfaria
Last active October 6, 2018 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cfaria/46284212707f7fa1dcebd7edcfe01e21 to your computer and use it in GitHub Desktop.
Save cfaria/46284212707f7fa1dcebd7edcfe01e21 to your computer and use it in GitHub Desktop.
---
- name: Check for composer.json in project root or project_subtree_path
stat:
path: "{{ deploy_helper.new_release_path }}/composer.json"
register: composer_json
- name: Fail if composer.json not found
fail:
msg: "Unable to find a `composer.json` file in the root of '{{ deploy_helper.new_release_path }}'. Make sure your repo has a `composer.json` file in its root or edit `repo_subtree_path` for '{{ site }}' in `wordpress_sites.yml` so it points to the directory with a `composer.json` file."
when: not composer_json.stat.exists
- name: Setup packagist.com authentication
composer:
command: config
arguments: --auth http-basic.repo.packagist.com token {{ project.packagist_token }}
working_dir: "{{ deploy_helper.new_release_path }}"
no_log: true
when: project.packagist_token is defined
- name: Install Composer dependencies with scripts
composer:
working_dir: "{{ deploy_helper.new_release_path }}"
# # command: composer install --no-ansi --no-dev --no-interaction --optimize-autoloader
# command: composer install --no-dev
# args:
# chdir: "{{ deploy_helper.new_release_path }}"
{
"name": "roots/bedrock",
"type": "project",
"license": "MIT",
"description": "WordPress boilerplate with modern development tools, easier configuration, and an improved folder structure",
"homepage": "https://roots.io/bedrock/",
"authors": [
{
"name": "Scott Walkinshaw",
"email": "scott.walkinshaw@gmail.com",
"homepage": "https://github.com/swalkinshaw"
},
{
"name": "Ben Word",
"email": "ben@benword.com",
"homepage": "https://github.com/retlehs"
}
],
"keywords": [
"bedrock", "roots", "wordpress", "stack", "composer", "vagrant", "wp"
],
"support": {
"issues": "https://github.com/roots/bedrock/issues",
"forum": "https://discourse.roots.io/category/bedrock"
},
"config": {
"preferred-install": "dist"
},
"repositories": [
{
"type": "composer",
"url": "https://wpackagist.org"
},
{
"type": "composer",
"url": "https://wp-languages.github.io"
}
],
"require": {
"php": ">=5.6",
"composer/installers": "^1.4",
"koodimonni/composer-dropin-installer": "*",
"vlucas/phpdotenv": "^2.0.1",
"johnpbloch/wordpress": "4.9.8",
"oscarotero/env": "^1.1.0",
"koodimonni-language/es_es": "*",
"wpackagist-plugin/html-forms": "^1.3",
"wpackagist-plugin/wp-migrate-db": "^1.0",
"wpackagist-plugin/shortcode-ui": "0.7.3",
"wpackagist-plugin/pods": "^2.6",
"wpackagist-plugin/wordpress-seo": "~8.3.0",
"wpackagist-plugin/wp-super-cache": "^1.5.9",
"wpackagist-plugin/sucuri-scanner": "^1.8.18",
"wpackagist-plugin/duplicate-post": "3.2.2",
"wpackagist-plugin/post-types-order": "^1.9.3.9",
"wpackagist-plugin/antispam-bee": "^2.8.1"
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.0.2"
},
"extra": {
"installer-paths": {
"web/app/mu-plugins/{$name}/": ["type:wordpress-muplugin"],
"web/app/plugins/{$name}/": ["type:wordpress-plugin"],
"web/app/themes/{$name}/": ["type:wordpress-theme"]
},
"dropin-paths": {
"web/app/languages/": ["vendor:koodimonni-language"],
"web/app/languages/plugins/": ["vendor:koodimonni-plugin-language"],
"web/app/languages/themes/": ["vendor:koodimonni-theme-language"]
},
"wordpress-install-dir": "web/wp"
},
"scripts": {
"post-root-package-install": [
"php -r \"copy('.env.example', '.env');\""
],
"test": [
"phpcs"
]
}
}
<?php
namespace Koodimonni\Composer;
use Composer\Script\Event;
use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
#Subscribe to Package events
use Composer\Installer\PackageEvent;
#For asking package about it's details
use Composer\Package\PackageInterface;
#Default installer
use Composer\Installer\LibraryInstaller;
#Hook in composer/installers for asking custom paths
use Composer\Installers\Installer;
class Dropin implements PluginInterface, EventSubscriberInterface {
/**
* List of files which will not be moved no matter what
* This might be useless, but it gives me peace of mind.
* I intended this plugin for moving translations and dropins for wordpress.
* Put a pull request if you find some other use cases for this.
* these filenames are in lowcase intentionally!!
*/
public static $ignoreList = array(
".ds_store",
".git",
".gitattributes",
".gitignore",
"composer.json",
"composer.lock",
"readme.md",
"changelog",
"changelog.md",
"readme.txt",
"license",
"license.md",
"phpunit.xml",
".travis.yml"
);
// Cache results of dropin-paths into here and use it only from getter function getPaths()
protected $paths;
protected $composer;
protected $io;
/**
* Composer plugin default behaviour
*/
public function activate(Composer $composer, IOInterface $io)
{
$this->composer = $composer;
$this->io = $io;
}
/**
* Subscribe to package changed events
* TODO: It might be good idea to gather all changes into static variable
* and then do all of them after install/update finishes
* This way some extra ordinary dropins would behave more predictable
*/
public static function getSubscribedEvents()
{
return array(
"post-package-install" => array(
array('onPackageInstall', 0)
),
"post-package-update" => array(
array('onPackageUpdate', 0)
),
);
}
/**
* Hook up this function to package install to move files defined in composer.json -> extra -> dropin-paths
* Run this command as post-install-package
* @param Composer\Installer\PackageEvent $event - Composer automatically tells information about itself for custom scripts
*/
public function onPackageInstall(PackageEvent $event){
//Get information about the package that was just installed
$package = $event->getOperation()->getPackage();
$this->dropNewFiles($package);
}
/**
* Hook up this function to package install to move files defined in composer.json -> extra -> dropin-paths
* Run this command as post-install-package
* @param Composer\Installer\PackageEvent $event - Composer automatically tells information about itself for custom scripts
*/
public function onPackageUpdate(PackageEvent $event){
//TODO: Keep record of moved files and delete them on updates and in package deletion
//$package = $event->getOperation()->getInitialPackage(); //Do something for these.
//Maybe symlinking/copying files would be better than moving.
$package = $event->getOperation()->getTargetPackage();
//For now just Ignore what happend earlier and assume that new files will replace earlier
$this->dropNewFiles($package);
}
/**
* TODO: Keep track of files so you could also delete them!!
* Run this command as post-delete-package
* @param Composer\Script\Event $event - Composer automatically tells information about itself for custom scripts
*/
public static function onPackageDelete(PackageEvent $event){
}
/*
* Call this function with the installed/updated package
* @param Composer\Package\PackageInterface $package - Composer Package which we are handling
*/
public function dropNewFiles(PackageInterface $package){
//Gather all information for dropin directives
$info = array();
//Composer doesn't care about uppercase and so shouldn't we
$info['package'] = strtolower($package->getName());
$info['vendor'] = substr($info['package'], 0, strpos($info['package'], '/'));
$info['type'] = $package->getType();
#Locate absolute urls
$projectDir = getcwd();
#Get directives from composer.json
$extra = $this->composer->getPackage()->getExtra();
if (isset($extra['dropin-paths'])) {
$paths = self::getPaths($extra['dropin-paths']);
} else {
//Stop here if dropin-paths is not defined.
return;
}
$dest = self::installPath($info);
//If dropin has nothing to do with this package just end it now
if (!$dest) {
return;
}else {
$dest = "{$projectDir}/{$dest}"; //Update to full path
}
//Compatibility with composer/installers
if (class_exists('\\Composer\\Installers\\Installer')) {
$installer = new Installer($this->io,$this->composer);
} else {
//System default
$installer = new LibraryInstaller($this->io,$this->composer);
}
try {
$src = realpath($installer->getInstallPath($package));
} catch (\InvalidArgumentException $e) {
// We will end up here if composer/installers doesn't recognise the type
// In this case it's the default installation folder in vendor
$vendorDir = $this->composer->getConfig()->get('vendor-dir');
$vendorDir = realpath($vendorDir);
$src = "{$vendorDir}/{$info['package']}";
}
$config = $this->composer->getPackage()->getConfig();
$shouldCopy = isset($config['dropin-installer']) && $config['dropin-installer'] === 'copy';
$installFiles = self::getFilesToInstall($info);
if ($shouldCopy) {
$this->io->write(" Copying dropin files...\n");
if ($installFiles == "*") {
self::rcopy($src, $dest);
} else {
foreach ($installFiles as $file) {
self::copy("{$src}/{$file}", $dest);
}
}
} else {
$this->io->write(" Moving dropin files...\n");
if ($installFiles == "*") {
self::rmove($src, $dest);
} else {
foreach ($installFiles as $file) {
self::move("{$src}/{$file}", $dest);
}
}
}
}
/**
* Form nice associative array from extra['dropin-paths']
* So we can easily decide what to do from the directive eg. 'type:', 'vendor:', 'package:'
* Cache it for the rest of the runs
*/
private function getPaths($dropinPaths) {
if(!$this->paths){
$dropinDirectives = array();
foreach($dropinPaths as $path => $directives) {
//if directive is string, fixit to use the array logic
if (is_string($directives)) {
$directives = array($directives);
}
foreach($directives as $directive) {
$result = self::parseDirective($directive);
if ($result) {
$dropinDirectives[$result['type']][$result['target']]['path'] = $path;
$dropinDirectives[$result['type']][$result['target']]['files'] = $result['files'];
} else {
throw new \InvalidArgumentException(
"Sorry your dropin directive has problems: $directive.\nIt should be like 'htdocs/wp-content/languages': ['type:wordpress-language']"
);
}
}
}
//Cache the results
$this->paths = $dropinDirectives;
return $this->paths;
}else {
//This has already been done earlier, return the results
return $this->paths;
}
}
/**
* If dropin path for package is defined use it and return relative installation path
* @param Array $package - Associative array containing all supported types
*/
private function installPath($package) {
if (isset($this->paths['package'][$package['package']]['path'])){
return $this->paths['package'][$package['package']]['path'];
} elseif (isset($this->paths['vendor'][$package['vendor']]['path'])){
return $this->paths['vendor'][$package['vendor']]['path'];
} elseif (isset($this->paths['type'][$package['type']]['path'])){
return $this->paths['type'][$package['type']]['path'];
} else {
return false;
}
}
/**
* Sometimes not all is wanted to be moved. You can include only the files you want to get moved
* This is useful for this this kinds of plugins: wp-packagist/wordpress-mu-domain-mapping
* @param Array $package - Associative array containing all supported types
*/
private function getFilesToInstall($package) {
if (isset($this->paths['package'][$package['package']]['files'])){
return $this->paths['package'][$package['package']]['files'];
} else {
return "*"; //Install all
}
}
/**
* Recursively move files from one directory to another
*
* @param String $src - Source of files being moved
* @param String $dest - Destination of files being moved
*/
private static function rmove($src, $dest){
// If source is not a directory stop processing
if(!is_dir($src)) {
echo "Source is not a directory";
return false;
}
// If the destination directory does not exist create it
if(!is_dir($dest)) {
if(!mkdir($dest,0777,true)) {
// If the destination directory could not be created stop processing
echo "Can't create destination path: {$dest}\n";
return false;
}
}
// Open the source directory to read in files
$i = new \DirectoryIterator($src);
foreach($i as $f) {
#Skip useless files&folders
if (self::isFileIgnored($f->getFilename())) continue;
if($f->isFile()) {
rename($f->getRealPath(), "$dest/" . $f->getFilename());
} else if(!$f->isDot() && $f->isDir()) {
self::rmove($f->getRealPath(), "$dest/$f");
#unlink($f->getRealPath());
}
}
#We could Remove original directories but don't do it
#unlink($src);
}
private static function move($src, $dest){
// If the destination directory does not exist create it
if(!is_dir($dest)) {
if(!mkdir($dest,0777,true)) {
// If the destination directory could not be created stop processing
echo "Can't create destination path: {$dest}\n";
return false;
}
}
rename($src, "$dest/" . basename($src));
}
/**
* Recursively copy files from one directory to another
*
* @param string $src - Source of files being copied
* @param string $dest - Destination of files being copied
*/
private static function rcopy($src, $dest){
// If source is not a directory stop processing
if(!is_dir($src)) {
echo "Source is not a directory";
return false;
}
// If the destination directory does not exist create it
if(!is_dir($dest)) {
if(!mkdir($dest,0777,true)) {
// If the destination directory could not be created stop processing
echo "Can't create destination path: {$dest}\n";
return false;
}
}
// Open the source directory to read in files
$i = new \DirectoryIterator($src);
foreach($i as $f) {
#Skip useless files&folders
if (self::isFileIgnored($f->getFilename())) continue;
if($f->isFile()) {
copy($f->getRealPath(), "$dest/" . $f->getFilename());
} else if(!$f->isDot() && $f->isDir()) {
self::rcopy($f->getRealPath(), "$dest/$f");
#unlink($f->getRealPath());
}
}
#We could Remove original directories but don't do it
#unlink($src);
}
/**
* Copy a file from one location to another.
*
* @param $src - File being copied
* @param $dest - Destinatin directory
*/
private static function copy($src, $dest)
{
// If the destination directory does not exist create it
if(!is_dir($dest)) {
if(!mkdir($dest,0777,true)) {
// If the destination directory could not be created stop processing
echo "Can't create destination path: {$dest}\n";
return false;
}
}
copy($src, "$dest/" . basename($src));
}
/**
* Returns type and information of dropin directive
*/
private static function parseDirective($directive) {
#directive example => vendor:koodimonni-language:file1,file2,file3...
#so type would be 'vendor';
$parsed_directive = explode(':', $directive);
$type = $parsed_directive[0];
$target = $parsed_directive[1];
if (isset($parsed_directive[2])) {
$files = explode(',', $parsed_directive[2]);
} else {
$files = NULL;
}
if(!$type || !$target) return false;
return array( "type" => $type, "target" => $target, "files" => $files);
}
/**
* Returns true if file is in ignored files list
*/
private static function isFileIgnored($filename){
return in_array(strtolower($filename),self::$ignoreList);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment