Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.x] Add new package folder convention for starter kits #11119

Open
wants to merge 14 commits into
base: 5.x
Choose a base branch
from
Open
16 changes: 14 additions & 2 deletions src/StarterKits/Concerns/InteractsWithFilesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ protected function installFile(string $fromPath, string $toPath, Command|NullCon
}

/**
* Export starter kit path.
* Export relative path to starter kit.
*/
protected function exportPath(string $starterKitPath, string $from, ?string $to = null): void
protected function exportRelativePath(string $starterKitPath, string $from, ?string $to = null): void
{
$to = $to
? "{$starterKitPath}/{$to}"
Expand All @@ -43,6 +43,18 @@ protected function exportPath(string $starterKitPath, string $from, ?string $to
: $files->copy($from, $to);
}

/**
* Copy directory contents into, file by file so that it does not stomp the whole target directory.
*/
protected function copyDirectoryContentsInto(string $from, string $to): void
{
$files = app(Filesystem::class);

collect($files->allFiles($from))
->mapWithKeys(fn ($file) => [$from.'/'.$file->getRelativePathname() => $to.'/'.$file->getRelativePathname()])
->each(fn ($to, $from) => $files->copy(Path::tidy($from), $this->preparePath($to)));
}

/**
* Prepare path directory.
*/
Expand Down
4 changes: 2 additions & 2 deletions src/StarterKits/ExportableModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ public function export(string $starterKitPath): void
{
$this
->exportPaths()
->each(fn ($path) => $this->exportPath(
->each(fn ($path) => $this->exportRelativePath(
from: $path,
starterKitPath: $starterKitPath,
));

$this
->exportAsPaths()
->each(fn ($to, $from) => $this->exportPath(
->each(fn ($to, $from) => $this->exportRelativePath(
from: $from,
to: $to,
starterKitPath: $starterKitPath,
Expand Down
42 changes: 39 additions & 3 deletions src/StarterKits/Exporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Collection;
use Statamic\Facades\Path;
use Statamic\Facades\YAML;
use Statamic\StarterKits\Concerns\InteractsWithFilesystem;
use Statamic\StarterKits\Exceptions\StarterKitException;
Expand Down Expand Up @@ -49,12 +50,13 @@ public function export(): void
$this
->validateExportPath()
->validateConfig()
->validatePackage()
->instantiateModules()
->clearExportPath()
->exportModules()
->exportConfig()
->exportHooks()
->exportComposerJson();
->exportPackage();
}

/**
Expand All @@ -81,6 +83,22 @@ protected function validateConfig(): self
return $this;
}

/**
* Validate package folder, if it exists.
*/
protected function validatePackage(): self
{
if (! $this->files->exists(base_path('package'))) {
return $this;
}

if (! $this->files->exists(base_path('package/composer.json'))) {
throw new StarterKitException('Package config [package/composer.json] does not exist.');
}

return $this;
}

/**
* Instantiate and validate modules that are to be installed.
*/
Expand Down Expand Up @@ -162,7 +180,11 @@ protected function clearExportPath()
*/
protected function exportModules(): self
{
$this->modules->each(fn ($module) => $module->export($this->exportPath));
$exportPath = $this->files->exists(base_path('package'))
? $this->exportPath.'/export'
: $this->exportPath;

$this->modules->each(fn ($module) => $module->export($exportPath));

return $this;
}
Expand Down Expand Up @@ -262,14 +284,28 @@ protected function exportHooks(): self

collect($hooks)
->filter(fn ($hook) => $this->files->exists(base_path($hook)))
->each(fn ($hook) => $this->exportPath(
->each(fn ($hook) => $this->exportRelativePath(
from: $hook,
starterKitPath: $this->exportPath,
));

return $this;
}

/**
* Export package config & other misc vendor files.
*/
protected function exportPackage(): self
{
if (! $this->files->exists($packageFolder = base_path('package'))) {
return $this->exportComposerJson();
}

$this->copyDirectoryContentsInto($packageFolder, $this->exportPath);

return $this;
}

/**
* Export composer.json.
*/
Expand Down
33 changes: 24 additions & 9 deletions src/StarterKits/InstallableModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ protected function installableFiles(): Collection
*/
protected function expandExportDirectoriesToFiles(string $to, ?string $from = null): Collection
{
$to = Path::tidy($this->starterKitPath($to));
$from = Path::tidy($from ? $this->starterKitPath($from) : $to);
$to = Path::tidy($this->installableFilesPath($to));
$from = Path::tidy($from ? $this->installableFilesPath($from) : $to);

$paths = collect([$from => $to]);

Expand All @@ -139,13 +139,24 @@ protected function expandExportDirectoriesToFiles(string $to, ?string $from = nu
]);
}

$package = $this->installer->package();

return $paths->mapWithKeys(fn ($to, $from) => [
Path::tidy($from) => Path::tidy(str_replace("/vendor/{$package}", '', $to)),
Path::tidy($from) => Path::tidy($this->convertInstallableToDestinationPath($to)),
]);
}

/**
* Convert installable vendor file path to destination path.
*/
protected function convertInstallableToDestinationPath(string $path): string
{
$package = $this->installer->package();

$path = str_replace("/vendor/{$package}/export", '', $path);
$path = str_replace("/vendor/{$package}", '', $path);

return $path;
}

/**
* Install dependency permanently into app.
*/
Expand Down Expand Up @@ -186,7 +197,7 @@ protected function ensureInstallableFilesExist(): self
$this
->exportPaths()
->merge($this->exportAsPaths())
->reject(fn ($path) => $this->files->exists($this->starterKitPath($path)))
->reject(fn ($path) => $this->files->exists($this->installableFilesPath($path)))
->each(function ($path) {
throw new StarterKitException("Starter kit path [{$path}] does not exist.");
});
Expand Down Expand Up @@ -229,13 +240,17 @@ protected function ensureCanRequireDependencies(array $packages, bool $dev = fal
}

/**
* Get starter kit vendor path.
* Get starter kit installable files path.
*/
protected function starterKitPath(?string $path = null): string
protected function installableFilesPath(?string $path = null): string
{
$package = $this->installer->package();

return collect([base_path("vendor/{$package}"), $path])->filter()->implode('/');
$scope = $this->files->exists(base_path("vendor/{$package}/export"))
? 'export'
: null;

return collect([base_path("vendor/{$package}"), $scope, $path])->filter()->implode('/');
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/StarterKits/Installer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Statamic\Console\Please\Application as PleaseApplication;
use Statamic\Console\Processes\Exceptions\ProcessException;
use Statamic\Facades\Blink;
use Statamic\Facades\Path;
use Statamic\Facades\YAML;
use Statamic\StarterKits\Concerns\InteractsWithFilesystem;
use Statamic\StarterKits\Exceptions\StarterKitException;
Expand Down Expand Up @@ -654,7 +655,7 @@ protected function tidyComposerErrorOutput(string $output): string
*/
protected function starterKitPath(?string $path = null): string
{
return collect([base_path("vendor/{$this->package}"), $path])->filter()->implode('/');
return Path::tidy(collect([base_path("vendor/{$this->package}"), $path])->filter()->implode('/'));
}

/**
Expand Down
96 changes: 84 additions & 12 deletions tests/StarterKits/ExportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class ExportTest extends TestCase

protected $files;
protected $configPath;
protected $packagePath;
protected $exportPath;
protected $postInstallHookPath;

Expand All @@ -24,34 +25,40 @@ public function setUp(): void

$this->files = app(Filesystem::class);
$this->configPath = base_path('starter-kit.yaml');
$this->packagePath = base_path('package');
$this->postInstallHookPath = base_path('StarterKitPostInstall.php');
$this->exportPath = base_path('../cool-runnings');

if ($this->files->exists($this->configPath)) {
$this->files->delete($this->configPath);
}

if ($this->files->exists($this->exportPath)) {
$this->files->deleteDirectory($this->exportPath);
}

$this->cleanUp();
$this->restoreComposerJson();
$this->backupComposerJson();
}

public function tearDown(): void
{
$this->cleanUp();
$this->restoreComposerJson();

parent::tearDown();
}

private function cleanUp()
{
if ($this->files->exists($this->configPath)) {
$this->files->delete($this->configPath);
}

if ($this->files->exists($this->postInstallHookPath)) {
$this->files->delete($this->postInstallHookPath);
if ($this->files->exists($this->packagePath)) {
$this->files->deleteDirectory($this->packagePath);
}

$this->restoreComposerJson();
if ($this->files->exists($this->exportPath)) {
$this->files->deleteDirectory($this->exportPath);
}

parent::tearDown();
if ($this->files->exists($this->postInstallHookPath)) {
$this->files->delete($this->postInstallHookPath);
}
}

#[Test]
Expand Down Expand Up @@ -631,6 +638,71 @@ public function it_uses_existing_composer_json_file()
, $this->files->get($this->exportPath('composer.json')));
}

#[Test]
public function it_requires_composer_json_file_in_package_folder_if_it_exists()
{
$this->files->makeDirectory(base_path('package/src'), 0777, true, true);
$this->files->put(base_path('package/src/ServiceProvider.php'), 'I am a service provider!');
// $this->files->put(base_path('package/composer.json'), 'I am a composer.json!'); // Say we forget to create a composer.json file

$this->setExportPaths([
'config',
]);

$this->assertFileDoesNotExist($this->exportPath('config'));
$this->assertFileDoesNotExist($this->exportPath('src'));

$this
->exportCoolRunnings()
// ->expectsOutput('Package config [package/composer.json] does not exist.') // TODO: Why does this work in InstallTest?
->assertFailed();

$this->assertFileDoesNotExist($this->exportPath('config'));
$this->assertFileDoesNotExist($this->exportPath('src'));
}

#[Test]
public function it_exports_package_and_export_folders_instead_of_composer_json_stub_when_package_folder_exists()
{
$this->files->makeDirectory(base_path('package/src'), 0777, true, true);
$this->files->put(base_path('package/src/ServiceProvider.php'), 'I am a service provider!');

$this->files->makeDirectory(base_path('package/resources/views'), 0777, true, true);
$this->files->put(base_path('package/resources/views/widget.blade.php'), 'I am a vendor view!');

$this->files->put(base_path('package/composer.json'), 'I am a composer.json!');

$this->setExportPaths([
'config',
'resources/views',
]);

$this->assertFileDoesNotExist($this->exportPath('package'));
$this->assertFileDoesNotExist($this->exportPath('src'));
$this->assertFileDoesNotExist($this->exportPath('composer.json'));
$this->assertFileDoesNotExist($this->exportPath('config'));
$this->assertFileDoesNotExist($this->exportPath('resources/views'));

$this->exportCoolRunnings();

$this->assertFileDoesNotExist($this->exportPath('package'));
$this->assertFileExists($this->exportPath('src'));
$this->assertFileExists($this->exportPath('composer.json'));

// Notice `export_paths` should not get exported to starter kit root anymore...
$this->assertFileDoesNotExist($this->exportPath('config/filesystems.php'));
$this->assertFileDoesNotExist($this->exportPath('resources/views/welcome.blade.php'));

// Rather, they should go to the `export` folder now...
$this->assertFileExists($this->exportPath('export/config/filesystems.php'));
$this->assertFileExists($this->exportPath('export/resources/views/welcome.blade.php'));

// And all vendor stuff in `package` should get exported to target starter kit root...
$this->assertEquals('I am a service provider!', $this->files->get($this->exportPath('src/ServiceProvider.php')));
$this->assertEquals('I am a vendor view!', $this->files->get($this->exportPath('resources/views/widget.blade.php')));
$this->assertEquals('I am a composer.json!', $this->files->get($this->exportPath('composer.json')));
}

#[Test]
public function it_can_export_module_files()
{
Expand Down
Loading
Loading