Compare commits

..

No commits in common. "1" and "1.3.0" have entirely different histories.
1 ... 1.3.0

9 changed files with 58 additions and 110 deletions

View File

@ -1,11 +0,0 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
jobs:
ci:
name: CI
uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1

View File

@ -1,16 +0,0 @@
name: Dispatch CI
on:
# At 12:00 PM UTC, only on Friday and Saturday
schedule:
- cron: '0 12 * * 5,6'
jobs:
dispatch-ci:
name: Dispatch CI
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Dispatch CI
uses: silverstripe/gha-dispatch-ci@v1

View File

@ -1,17 +0,0 @@
name: Keepalive
on:
workflow_dispatch:
# The 4th of every month at 10:50am UTC
schedule:
- cron: '50 10 4 * *'
jobs:
keepalive:
name: Keepalive
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Keepalive
uses: silverstripe/gha-keepalive@v1

View File

@ -1,7 +1,4 @@
# Silverstripe recipe-plugin # SilverStripe recipe-plugin
[![CI](https://github.com/silverstripe/recipe-plugin/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/recipe-plugin/actions/workflows/ci.yml)
[![Silverstripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
## Introduction ## Introduction

View File

@ -22,11 +22,10 @@
"lint-clean": "phpcbf src/" "lint-clean": "phpcbf src/"
}, },
"require": { "require": {
"composer-plugin-api": "^1.1 || ^2" "composer-plugin-api": "^1.1"
}, },
"require-dev": { "require-dev": {
"composer/composer": "^1.2 || 2", "composer/composer": "^1.2"
"squizlabs/php_codesniffer": "^3.5"
}, },
"minimum-stability": "dev" "minimum-stability": "dev"
} }

View File

@ -1,12 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ruleset name="SilverStripe"> <ruleset><rule ref="PSR2" /></ruleset>
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
<file>src</file>
<!-- base rules are PSR-12 -->
<rule ref="PSR12" >
<exclude name="PSR1.Methods.CamelCapsMethodName.NotCamelCaps" />
<exclude name="PSR1.Files.SideEffects.FoundWithSymbols" />
</rule>
</ruleset>

View File

@ -14,6 +14,31 @@ use Symfony\Component\Console\Output\OutputInterface;
trait RecipeCommandBehaviour trait RecipeCommandBehaviour
{ {
/**
* Gets the application instance for this command.
*
* @return Application An Application instance
*/
public abstract function getApplication();
/**
* @param bool $required
* @param bool|null $disablePlugins
* @throws \RuntimeException
* @return Composer
*/
public abstract function getComposer($required = true, $disablePlugins = null);
/**
* Removes the cached composer instance
*/
public abstract function resetComposer();
/**
* @return IOInterface
*/
abstract public function getIO();
/** /**
* @param OutputInterface $output * @param OutputInterface $output
* @param string $recipe * @param string $recipe
@ -103,17 +128,17 @@ trait RecipeCommandBehaviour
} }
// Existing version is already a ^1.0.0 or ~1.0.0 constraint // Existing version is already a ^1.0.0 or ~1.0.0 constraint
if (preg_match('#^[~^]#', $existingVersion ?? '')) { if (preg_match('#^[~^]#', $existingVersion)) {
return $existingVersion; return $existingVersion;
} }
// Existing version is already a dev constraint // Existing version is already a dev constraint
if (stristr($existingVersion ?? '', 'dev') !== false) { if (stristr($existingVersion, 'dev') !== false) {
return $existingVersion; return $existingVersion;
} }
// Numeric-only version maps to semver constraint // Numeric-only version maps to semver constraint
if (preg_match('#^([\d.]+)$#', $existingVersion ?? '')) { if (preg_match('#^([\d.]+)$#', $existingVersion)) {
return "^{$existingVersion}"; return "^{$existingVersion}";
} }

View File

@ -16,10 +16,6 @@ use RegexIterator;
class RecipeInstaller extends LibraryInstaller class RecipeInstaller extends LibraryInstaller
{ {
/**
* @var bool
*/
private $hasWrittenFiles = false;
public function __construct(IOInterface $io, Composer $composer) public function __construct(IOInterface $io, Composer $composer)
{ {
@ -65,21 +61,19 @@ class RecipeInstaller extends LibraryInstaller
$relativePath = $this->installProjectFile($sourceRoot, $destinationRoot, $path, $installedFiles); $relativePath = $this->installProjectFile($sourceRoot, $destinationRoot, $path, $installedFiles);
// Add file to installed (even if already exists) // Add file to installed (even if already exists)
if (!in_array($relativePath, $installedFiles ?? [])) { if (!in_array($relativePath, $installedFiles)) {
$installedFiles[] = $relativePath; $installedFiles[] = $relativePath;
} }
} }
// If any files are written, modify composer.json with newly installed files // If any files are written, modify composer.json with newly installed files
if ($this->hasWrittenFiles) { if ($installedFiles) {
sort($installedFiles); sort($installedFiles);
if (!isset($composerData['extra'])) { if (!isset($composerData['extra'])) {
$composerData['extra'] = []; $composerData['extra'] = [];
} }
$composerData['extra'][$registrationKey] = $installedFiles; $composerData['extra'][$registrationKey] = $installedFiles;
$composerFile->write($composerData); $composerFile->write($composerData);
// Reset the variable so that we can try this trick again later
$this->hasWrittenFiles = false;
} }
} }
@ -93,15 +87,15 @@ class RecipeInstaller extends LibraryInstaller
protected function installProjectFile($sourceRoot, $destinationRoot, $sourcePath, $installedFiles) protected function installProjectFile($sourceRoot, $destinationRoot, $sourcePath, $installedFiles)
{ {
// Relative path // Relative path
$relativePath = substr($sourcePath ?? '', strlen($sourceRoot ?? '') + 1); // Name path without leading '/' $relativePath = substr($sourcePath, strlen($sourceRoot) + 1); // Name path without leading '/'
// Get destination path // Get destination path
$relativeDestination = $this->rewriteFilePath($destinationRoot, $relativePath); $relativeDestination = $this->rewriteFilePath($destinationRoot, $relativePath);
$destination = $destinationRoot . DIRECTORY_SEPARATOR . $relativeDestination; $destination = $destinationRoot . DIRECTORY_SEPARATOR . $relativeDestination;
// Check if file exists // Check if file exists
if (file_exists($destination ?? '')) { if (file_exists($destination)) {
if (file_get_contents($destination ?? '') === file_get_contents($sourcePath ?? '')) { if (file_get_contents($destination) === file_get_contents($sourcePath)) {
$this->io->write( $this->io->write(
" - Skipping <info>$relativePath</info> (<comment>existing, but unchanged</comment>)" " - Skipping <info>$relativePath</info> (<comment>existing, but unchanged</comment>)"
); );
@ -110,19 +104,15 @@ class RecipeInstaller extends LibraryInstaller
" - Skipping <info>$relativePath</info> (<comment>existing and modified in project</comment>)" " - Skipping <info>$relativePath</info> (<comment>existing and modified in project</comment>)"
); );
} }
} elseif ( } elseif (in_array($relativePath, $installedFiles) || in_array($relativeDestination, $installedFiles)) {
in_array($relativePath, $installedFiles ?? []) ||
in_array($relativeDestination, $installedFiles ?? [])
) {
// Don't re-install previously installed files that have been deleted // Don't re-install previously installed files that have been deleted
$this->io->write( $this->io->write(
" - Skipping <info>$relativePath</info> (<comment>previously installed</comment>)" " - Skipping <info>$relativePath</info> (<comment>previously installed</comment>)"
); );
} else { } else {
$this->io->write(" - Copying <info>$relativePath</info>"); $this->io->write(" - Copying <info>$relativePath</info>");
$this->filesystem->ensureDirectoryExists(dirname($destination ?? '')); $this->filesystem->ensureDirectoryExists(dirname($destination));
copy($sourcePath ?? '', $destination ?? ''); copy($sourcePath, $destination);
$this->hasWrittenFiles = true;
} }
return $relativePath; return $relativePath;
} }
@ -141,7 +131,7 @@ class RecipeInstaller extends LibraryInstaller
foreach ($patterns as $pattern) { foreach ($patterns as $pattern) {
$expressions[] = $this->globToRegexp($pattern); $expressions[] = $this->globToRegexp($pattern);
} }
$regExp = '#^' . $this->globToRegexp($sourceRoot . '/') . '((' . implode(')|(', $expressions) . '))$#'; $regExp = '#^' . $this->globToRegexp($sourceRoot . '/').'(('.implode(')|(', $expressions).'))$#';
// Build directory iterator // Build directory iterator
$directoryIterator = new RecursiveDirectoryIterator( $directoryIterator = new RecursiveDirectoryIterator(
@ -165,10 +155,10 @@ class RecipeInstaller extends LibraryInstaller
*/ */
protected function globToRegexp($glob) protected function globToRegexp($glob)
{ {
$sourceParts = explode('*', $glob ?? ''); $sourceParts = explode('*', $glob);
$regexParts = array_map(function ($part) { $regexParts = array_map(function ($part) {
return preg_quote($part ?? '', '#'); return preg_quote($part, '#');
}, $sourceParts ?? []); }, $sourceParts);
return implode('(.+)', $regexParts); return implode('(.+)', $regexParts);
} }
@ -186,11 +176,11 @@ class RecipeInstaller extends LibraryInstaller
$recipePath = $this->getInstallPath($package); $recipePath = $this->getInstallPath($package);
// Find project path // Find project path
$projectPath = dirname(realpath(Factory::getComposerFile() ?? '') ?? ''); $projectPath = dirname(realpath(Factory::getComposerFile()));
// Find public path // Find public path
$candidatePublicPath = $projectPath . DIRECTORY_SEPARATOR . RecipePlugin::PUBLIC_PATH; $candidatePublicPath = $projectPath . DIRECTORY_SEPARATOR . RecipePlugin::PUBLIC_PATH;
$publicPath = is_dir($candidatePublicPath ?? '') ? $candidatePublicPath : $projectPath; $publicPath = is_dir($candidatePublicPath) ? $candidatePublicPath : $projectPath;
// Copy project files to root // Copy project files to root
$name = $package->getName(); $name = $package->getName();
@ -225,9 +215,8 @@ class RecipeInstaller extends LibraryInstaller
* Perform any file rewrites necessary to a relative path of a file being installed. * Perform any file rewrites necessary to a relative path of a file being installed.
* E.g. if 'mysite' folder exists, rewrite 'mysite' to 'app' and 'mysite/code' to 'app/src' * E.g. if 'mysite' folder exists, rewrite 'mysite' to 'app' and 'mysite/code' to 'app/src'
* *
* This will be removed in 2.0 as the app folder will be hard coded and no rewrites supported. * @deprecated 1.2..2.0 Will be removed in 2.0; app folder will be hard coded and no
* * rewrites supported.
* @deprecated 1.2.0 Will be removed without equivalent functionality to replace it
* @param string $destinationRoot Project root * @param string $destinationRoot Project root
* @param string $relativePath Relative path to the resource being installed * @param string $relativePath Relative path to the resource being installed
* @return string Relative path we should write to * @return string Relative path we should write to
@ -249,8 +238,8 @@ class RecipeInstaller extends LibraryInstaller
'app' => 'mysite', 'app' => 'mysite',
]; ];
foreach ($rewrites as $from => $to) { foreach ($rewrites as $from => $to) {
if (stripos($relativePath ?? '', $from ?? '') === 0) { if (stripos($relativePath, $from) === 0) {
return $to . substr($relativePath ?? '', strlen($from ?? '')); return $to . substr($relativePath, strlen($from));
} }
} }
return $relativePath; return $relativePath;

View File

@ -1,5 +1,6 @@
<?php <?php
namespace SilverStripe\RecipePlugin; namespace SilverStripe\RecipePlugin;
use Composer\Composer; use Composer\Composer;
@ -26,37 +27,37 @@ class RecipePlugin implements PluginInterface, EventSubscriberInterface, Capable
/** /**
* Type of recipe to check for * Type of recipe to check for
*/ */
public const RECIPE_TYPE = 'silverstripe-recipe'; const RECIPE_TYPE = 'silverstripe-recipe';
/** /**
* 'extra' key for project files * 'extra' key for project files
*/ */
public const PROJECT_FILES = 'project-files'; const PROJECT_FILES = 'project-files';
/** /**
* 'extra' key for public files * 'extra' key for public files
*/ */
public const PUBLIC_FILES = 'public-files'; const PUBLIC_FILES = 'public-files';
/** /**
* Hard-coded 'public' web-root folder * Hard-coded 'public' web-root folder
*/ */
public const PUBLIC_PATH = 'public'; const PUBLIC_PATH = 'public';
/** /**
* 'extra' key for list of project files installed * 'extra' key for list of project files installed
*/ */
public const PROJECT_FILES_INSTALLED = 'project-files-installed'; const PROJECT_FILES_INSTALLED = 'project-files-installed';
/** /**
* 'extra' key for list of public files installed * 'extra' key for list of public files installed
*/ */
public const PUBLIC_FILES_INSTALLED = 'public-files-installed'; const PUBLIC_FILES_INSTALLED = 'public-files-installed';
/** /**
* 'extra' key for project dependencies installed * 'extra' key for project dependencies installed
*/ */
public const PROJECT_DEPENDENCIES_INSTALLED = 'project-dependencies-installed'; const PROJECT_DEPENDENCIES_INSTALLED = 'project-dependencies-installed';
public function activate(Composer $composer, IOInterface $io) public function activate(Composer $composer, IOInterface $io)
{ {
@ -132,13 +133,4 @@ class RecipePlugin implements PluginInterface, EventSubscriberInterface, Capable
CommandProvider::class => RecipeCommandProvider::class CommandProvider::class => RecipeCommandProvider::class
]; ];
} }
public function deactivate(Composer $composer, IOInterface $io)
{
}
public function uninstall(Composer $composer, IOInterface $io)
{
}
} }