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 folder based starter kit modules #11104

Draft
wants to merge 31 commits into
base: 5.x
Choose a base branch
from
Draft
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e2183db
Use dot notation in module keys so we can use `Arr::dot()` etc.
jesseleite Nov 9, 2024
d69a43b
Allow `default` param on `config()` helper.
jesseleite Nov 9, 2024
9588e0b
Add `setChildModules()` helper, so that we can override children afte…
jesseleite Nov 9, 2024
8a15e7b
Separate out instantiation and prompting logic.
jesseleite Nov 9, 2024
6a6d86a
Flatten modules before installing.
jesseleite Nov 9, 2024
833e463
Rename method.
jesseleite Nov 10, 2024
04e80f6
Test `import` syntax recursively imports from `/modules` folders.
jesseleite Nov 10, 2024
3fa6bcf
Module import implementation.
jesseleite Nov 10, 2024
ee5f969
Add test coverage for error handling.
jesseleite Nov 10, 2024
b9d96ea
Extract module instantiation and flattening to helper class for John.
jesseleite Nov 10, 2024
bb52694
Make into a more generic config setter.
jesseleite Nov 10, 2024
d8641c5
Use helper method.
jesseleite Nov 10, 2024
3e71b18
Update to match docs PR.
jesseleite Nov 11, 2024
889d6db
Import merging.
jesseleite Nov 11, 2024
a0b42b1
Fix and properly implement module folder scoping.
jesseleite Nov 11, 2024
cc89b46
Make this public for John.
jesseleite Nov 11, 2024
37b10c2
Export whole modules folder.
jesseleite Nov 11, 2024
5ded91e
Remove `import: ‘@config’` syntax, and implicitly import instead.
jesseleite Nov 11, 2024
5f8e663
Merge branch '5.x' of https://github.com/statamic/cms into starter-ki…
jesseleite Dec 19, 2024
92d939f
Fix issues from last merge.
jesseleite Dec 19, 2024
03741d6
Extract to helper method.
jesseleite Dec 19, 2024
f4e122c
Merge branch '5.x' of https://github.com/statamic/cms into starter-ki…
jesseleite Dec 19, 2024
758da1b
Extract to parent `Modules` class for reuse.
jesseleite Dec 20, 2024
6752dd0
Extract to parent `Module` class for reuse.
jesseleite Dec 20, 2024
032b304
Implement `ExportableModule` instantiation.
jesseleite Dec 20, 2024
d3b8ad9
Pass tests again using new `ExportableModules` helper.
jesseleite Dec 20, 2024
3bcd7c9
Remove rogue `ray()` calls.
jesseleite Dec 20, 2024
af377ae
Pass test around double `/modules/modules` reference.
jesseleite Dec 20, 2024
8c93901
Use `export` folder by default, and test non-export folder for backwa…
jesseleite Dec 22, 2024
f2413e1
Tweak install test for newer `modules` + `export` folder conventions.
jesseleite Dec 24, 2024
62bd6a9
Pass tests again.
jesseleite Dec 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/StarterKits/ExportableModules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Statamic\StarterKits;

use Illuminate\Support\Collection;

class ExportableModules extends Modules
{
/**
* Instantiate individual ExportableModule.
*/
protected function instantiateIndividualModule(array|Collection $config, string $key): Module
{
return new ExportableModule($config, $key);
}
}
70 changes: 12 additions & 58 deletions src/StarterKits/Exporter.php
Original file line number Diff line number Diff line change
@@ -83,67 +83,19 @@ protected function validateConfig(): self
}

/**
* Instantiate and validate modules that are to be installed.
* Instantiate and prepare flattened modules that are to be exported.
*/
protected function instantiateModules(): self
{
$this->modules = collect(['top_level' => $this->config()->all()])
->map(fn ($config, $key) => $this->instantiateModuleRecursively($config, $key))
->flatten()
->filter()
$this->modules = (new ExportableModules($this->config(), $this->exportPath))
->instantiate()
->all()
->pipe(fn ($module) => ExportableModules::flattenModules($module))
->each(fn ($module) => $module->validate());

return $this;
}

/**
* Instantiate module and check if nested modules should be recursively instantiated.
*/
protected function instantiateModuleRecursively(array $config, string $key): ExportableModule|array
{
$instantiated = new ExportableModule($config, $key);

if ($modules = Arr::get($config, 'modules')) {
$instantiated = collect($modules)
->map(fn ($config, $childKey) => $this->instantiateModule($config, $this->normalizeModuleKey($key, $childKey)))
->prepend($instantiated, $key)
->filter()
->all();
}

return $instantiated;
}

/**
* Instantiate individual module.
*/
protected function instantiateModule(array $config, string $key): ExportableModule|array
{
if (Arr::has($config, 'options') && $key !== 'top_level') {
return $this->instantiateSelectModule($config, $key);
}

return $this->instantiateModuleRecursively($config, $key);
}

/**
* Instantiate select module.
*/
protected function instantiateSelectModule(array $config, string $key): ExportableModule|array
{
return collect($config['options'])
->map(fn ($option, $optionKey) => $this->instantiateModuleRecursively($option, "{$key}.options.{$optionKey}"))
->all();
}

/**
* Normalize module key, as dotted array key for location in starter-kit.yaml.
*/
protected function normalizeModuleKey(string $key, string $childKey): string
{
return $key !== 'top_level' ? "{$key}.modules.{$childKey}" : $childKey;
}

/**
* Optionally clear out everything at target export path before exporting.
*/
@@ -159,13 +111,11 @@ protected function clearExportPath()
}

/**
* Export all the modules.
* Export all inline modules.
*/
protected function exportModules(): self
{
$exportPath = $this->exportPath.'/export';

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

return $this;
}
@@ -202,6 +152,10 @@ protected function syncConfigWithModules(): Collection
$config = $this->config()->all();

$normalizedModuleKeyOrder = [
'prompt',
'label',
'skip_option',
'options',
'export_paths',
'export_as',
'dependencies',
@@ -239,7 +193,7 @@ protected function dottedModulePath(ExportableModule $module, string $key): stri
return $key;
}

return 'modules.'.$module->key().'.'.$key;
return $module->key().'.'.$key;
}

/**
37 changes: 27 additions & 10 deletions src/StarterKits/InstallableModule.php
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ final class InstallableModule extends Module
*
* @throws Exception|StarterKitException
*/
public function installer($installer): self
public function installer(?Installer $installer): self
{
$this->installer = $installer;

@@ -102,7 +102,7 @@ protected function installDependencies(): self
/**
* Get installable files.
*/
protected function installableFiles(): Collection
public function installableFiles(): Collection
{
$installableFromExportPaths = $this
->exportPaths()
@@ -125,8 +125,10 @@ protected function installableFiles(): Collection
*/
protected function expandExportDirectoriesToFiles(string $to, ?string $from = null): Collection
{
$from = $this->relativePath($from ?? $to);

$from = Path::tidy($this->installableFilesPath($from));
$to = Path::tidy($this->installableFilesPath($to));
$from = Path::tidy($from ? $this->installableFilesPath($from) : $to);

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

@@ -151,7 +153,10 @@ protected function convertInstallableToDestinationPath(string $path): string
{
$package = $this->installer->package();

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

// Older kits may not be using new `export` folder convention, so
// we'll convert from the kit root for backwards compatibility
$path = str_replace("/vendor/{$package}", '', $path);

return $path;
@@ -195,9 +200,8 @@ protected function installableDependencies(string $configKey): array
protected function ensureInstallableFilesExist(): self
{
$this
->exportPaths()
->merge($this->exportAsPaths())
->reject(fn ($path) => $this->files->exists($this->installableFilesPath($path)))
->installableFiles()
->reject(fn ($to, $from) => $this->files->exists($from))
->each(function ($path) {
throw new StarterKitException("Starter kit path [{$path}] does not exist.");
});
@@ -246,15 +250,28 @@ protected function installableFilesPath(?string $path = null): string
{
$package = $this->installer->package();

// Scope to new `export` folder if it exists, otherwise we'll
// look in starter kit root for backwards compatibility
$scope = $this->files->exists(base_path("vendor/{$package}/export"))
// Older kits may not be using new `export` folder convention at the top level,
// so for backwards compatibility we'll dynamically scope to `export` folder,
// but we don't need to worry about this with newer folder based modules.
$scope = $this->files->exists(base_path("vendor/{$package}/export")) && ! $this->isFolderBasedModule()
? 'export'
: null;

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

/**
* Get relative module path.
*/
protected function relativePath(string $path): string
{
if (! $this->relativePath) {
return $path;
}

return Str::ensureRight($this->relativePath, '/export/').$path;
}

/**
* Normalize packages array to require args, with version handling if `package => version` array structure is passed.
*/
44 changes: 44 additions & 0 deletions src/StarterKits/InstallableModules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Statamic\StarterKits;

use Illuminate\Support\Collection;

final class InstallableModules extends Modules
{
protected $installer;

/**
* Set installer instance.
*/
public function installer(?Installer $installer): self
{
$this->installer = $installer;

return $this;
}

/**
* Instantiate individual InstallableModule.
*/
protected function instantiateIndividualModule(array|Collection $config, string $key): Module
{
return (new InstallableModule($config, $key))->installer($this->installer);
}

/**
* Override so that we do not prefix option key for installable modules.
*/
protected function prefixOptionsKey(string $key): ?string
{
return $key;
}

/**
* Override so that we do not prefix modules key for installable modules.
*/
protected function prefixModulesKey(string $key): ?string
{
return $key;
}
}
Loading