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 updatable and package starter kit conventions #11119

Merged
merged 26 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0c456ca
Test that it can export `package` directory to target export path root.
jesseleite Nov 12, 2024
ee450c4
Implement `package` folder export.
jesseleite Nov 12, 2024
b3a973f
Test that it validates `package/composer.json` before exporting any f…
jesseleite Nov 12, 2024
5f6de45
Implement `package/composer.json` validation.
jesseleite Nov 12, 2024
70d828b
Test that we scope all export_paths to `export` folder when `package`…
jesseleite Nov 12, 2024
3774706
Implement scoping to `export` folder.
jesseleite Nov 12, 2024
9078149
Flesh out assertions a bit more.
jesseleite Nov 12, 2024
2b25032
Add test for `export` folder handling in installer.
jesseleite Nov 12, 2024
a2491d6
Implement `export` handling in installer.
jesseleite Nov 12, 2024
b37c380
Cleanup.
jesseleite Nov 12, 2024
f7c676b
Tidy path.
jesseleite Nov 13, 2024
c481e7b
Tidy path.
jesseleite Nov 13, 2024
4579dfd
More cleanup.
jesseleite Nov 13, 2024
34de160
Windows you too.
jesseleite Nov 13, 2024
14e5a94
Deprecate `export_as` (but we'll still support it quietly for backwar…
jesseleite Nov 14, 2024
9cec292
Update exporter.
jesseleite Nov 25, 2024
47a84ee
Help migrate starter kit and package config to new `package` folder c…
jesseleite Nov 25, 2024
46a8ed1
Merge branch '5.x' of https://github.com/statamic/cms into starter-ki…
jesseleite Nov 26, 2024
be31121
Tweak error output to use `$this->components`, like we do in exporter.
jesseleite Dec 2, 2024
3c4ce12
Less `!`.
jesseleite Dec 2, 2024
3bccefd
Fix assertions.
jesseleite Dec 2, 2024
0cc5cba
[5.x] Give starter kit devs an opt-in config to help make parts of th…
jesseleite Dec 2, 2024
2f918a5
Flesh out test coverage to include `StarterKitPostInstall.php`.
jesseleite Dec 3, 2024
61a1216
This isn’t needed anymore.
jesseleite Dec 3, 2024
a7ae78f
Pass `StarterKitPostInstall.php` export tests.
jesseleite Dec 3, 2024
599f70e
Note on backwards compatibility for future us.
jesseleite Dec 3, 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
63 changes: 41 additions & 22 deletions src/Console/Commands/StarterKitExport.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ class StarterKitExport extends Command
*/
public function handle()
{
if (! File::exists(base_path('starter-kit.yaml'))) {
return $this->askToStubStarterKitConfig();
if ($this->isUsingLegacyExporterConventions()) {
$this->askToMigrateToPackageFolder();
}

if (! File::exists($path = $this->getAbsolutePath())) {
Expand All @@ -58,26 +58,6 @@ public function handle()
$this->components->info("Starter kit was successfully exported to [$path].");
}

/**
* Ask to stub out starter kit config.
*/
protected function askToStubStarterKitConfig(): void
{
$stubPath = __DIR__.'/stubs/starter-kits/starter-kit.yaml.stub';
$newPath = base_path($config = 'starter-kit.yaml');

if ($this->input->isInteractive()) {
if (! confirm("Config [{$config}] does not exist. Would you like to create it now?", true)) {
return;
}
}

File::copy($stubPath, $newPath);

$this->comment("A new config has been created at [{$config}].");
$this->comment('Please configure your `export_paths` and re-run to begin your export!');
}

/**
* Get absolute path.
*/
Expand Down Expand Up @@ -105,4 +85,43 @@ protected function askToCreateExportPath(string $path): void

$this->components->info("A new directory has been created at [{$path}].");
}

/**
* Determine if dev sandbox has starter-kit.yaml at root and/or customized composer.json at target path.
*/
protected function isUsingLegacyExporterConventions(): bool
{
return File::exists(base_path('starter-kit.yaml'));
}

/**
* Determine if dev sandbox has starter-kit.yaml at root and/or customized composer.json at target path.
*/
protected function askToMigrateToPackageFolder(): void
{
if ($this->input->isInteractive()) {
if (! confirm('Config should now live in the [package] folder. Would you like Statamic to move it for you?', true)) {
return;
}
}

if (! File::exists($dir = base_path('package'))) {
File::makeDirectory($dir, 0755, true);
}

if (File::exists($starterKitConfig = base_path('starter-kit.yaml'))) {
File::move($starterKitConfig, base_path('package/starter-kit.yaml'));
$this->components->info('Starter kit config moved to [package/starter-kit.yaml].');
}

if (File::exists($postInstallHook = base_path('StarterKitPostInstall.php'))) {
File::move($postInstallHook, base_path('package/StarterKitPostInstall.php'));
$this->components->info('Starter kit post-install hook moved to [package/StarterKitPostInstall.php].');
}

if (File::exists($packageComposerJson = $this->getAbsolutePath().'/composer.json')) {
File::move($packageComposerJson, base_path('package/composer.json'));
$this->components->info('Composer package config moved to [package/composer.json].');
}
}
}
2 changes: 1 addition & 1 deletion src/Console/Commands/StarterKitInstall.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function handle()
try {
$installer->install();
} catch (StarterKitException $exception) {
$this->error($exception->getMessage());
$this->components->error($exception->getMessage());

return 1;
}
Expand Down
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
14 changes: 9 additions & 5 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 Expand Up @@ -112,8 +112,12 @@ protected function ensureNotExportingComposerJson(): self
->merge($this->exportAsPaths())
->merge($this->exportAsPaths()->keys());

if ($flattenedExportPaths->contains('starter-kit.yaml')) {
throw new StarterKitException('Cannot export [starter-kit.yaml] config.');
}

if ($flattenedExportPaths->contains('composer.json')) {
throw new StarterKitException('Cannot export [composer.json]. Please use `dependencies` array!');
throw new StarterKitException('Cannot export [composer.json]. Please use `dependencies` array.');
}

return $this;
Expand All @@ -131,7 +135,7 @@ protected function ensureExportablePathsExist(): self
->merge($this->exportAsPaths()->keys())
->reject(fn ($path) => $this->files->exists(base_path($path)))
->each(function ($path) {
throw new StarterKitException("Cannot export [{$path}], because it does not exist in your app!");
throw new StarterKitException("Cannot export [{$path}], because it does not exist in your app.");
});

return $this;
Expand All @@ -153,7 +157,7 @@ protected function ensureExportableDependenciesExist(): self
->exportableDependencies()
->reject(fn ($dependency) => $installedDependencies->contains($dependency))
->each(function ($dependency) {
throw new StarterKitException("Cannot export [{$dependency}], because it does not exist in your composer.json!");
throw new StarterKitException("Cannot export [{$dependency}], because it does not exist in your composer.json.");
});

return $this;
Expand Down
98 changes: 18 additions & 80 deletions src/StarterKits/Exporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Statamic\StarterKits\Concerns\InteractsWithFilesystem;
use Statamic\StarterKits\Exceptions\StarterKitException;
use Statamic\Support\Arr;
use Statamic\Support\Str;
use Statamic\Support\Traits\FluentlyGetsAndSets;

class Exporter
Expand Down Expand Up @@ -52,9 +51,7 @@ public function export(): void
->instantiateModules()
->clearExportPath()
->exportModules()
->exportConfig()
->exportHooks()
->exportComposerJson();
->exportPackage();
}

/**
Expand All @@ -74,8 +71,12 @@ protected function validateExportPath(): self
*/
protected function validateConfig(): self
{
if (! $this->files->exists(base_path('starter-kit.yaml'))) {
throw new StarterKitException('Export config [starter-kit.yaml] does not exist.');
if (! $this->files->exists(base_path('package/starter-kit.yaml'))) {
throw new StarterKitException('Starter kit config [package/starter-kit.yaml] does not exist.');
}

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

return $this;
Expand Down Expand Up @@ -162,7 +163,9 @@ protected function clearExportPath()
*/
protected function exportModules(): self
{
$this->modules->each(fn ($module) => $module->export($this->exportPath));
$exportPath = $this->exportPath.'/export';

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

return $this;
}
Expand All @@ -172,7 +175,7 @@ protected function exportModules(): self
*/
protected function config(?string $key = null): mixed
{
$config = collect(YAML::parse($this->files->get(base_path('starter-kit.yaml'))));
$config = collect(YAML::parse($this->files->get(base_path('package/starter-kit.yaml'))));

if ($key) {
return $config->get($key);
Expand All @@ -181,20 +184,6 @@ protected function config(?string $key = null): mixed
return $config;
}

/**
* Export starter kit config.
*/
protected function exportConfig(): self
{
$config = $this
->versionModuleDependencies()
->syncConfigWithModules();

$this->files->put("{$this->exportPath}/starter-kit.yaml", YAML::dump($config->all()));

return $this;
}

/**
* Version module dependencies from composer.json.
*/
Expand Down Expand Up @@ -254,69 +243,18 @@ protected function dottedModulePath(ExportableModule $module, string $key): stri
}

/**
* Export starter kit hooks.
* Export package config & other misc vendor files.
*/
protected function exportHooks(): self
protected function exportPackage(): self
{
$hooks = ['StarterKitPostInstall.php'];

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

return $this;
}

/**
* Export composer.json.
*/
protected function exportComposerJson(): self
{
$composerJson = $this->prepareComposerJsonFromStub()->all();
$config = $this
->versionModuleDependencies()
->syncConfigWithModules();

$this->files->put(
"{$this->exportPath}/composer.json",
json_encode($composerJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n"
);
$this->files->put("{$this->exportPath}/starter-kit.yaml", YAML::dump($config->all()));

return $this;
}

/**
* Prepare composer.json from stub.
*/
protected function prepareComposerJsonFromStub(): Collection
{
$stub = $this->getComposerJsonStub();

$directory = preg_replace('/.*\/([^\/]*)/', '$1', $this->exportPath);
$vendorName = $this->vendorName ?? 'my-vendor-name';
$repoName = Str::slug($directory);
$package = "{$vendorName}/{$repoName}";
$title = Str::slugToTitle($repoName);

$stub = str_replace('dummy/package', $package, $stub);
$stub = str_replace('DummyTitle', $title, $stub);

return collect(json_decode($stub, true));
}

/**
* Get composer.json stub.
*/
protected function getComposerJsonStub(): string
{
$stubPath = __DIR__.'/../Console/Commands/stubs/starter-kits/composer.json.stub';

$existingComposerJsonPath = "{$this->exportPath}/composer.json";

if ($this->files->exists($existingComposerJsonPath)) {
return $this->files->get($existingComposerJsonPath);
}

return $this->files->get($stubPath);
}
}
35 changes: 26 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,19 @@ 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 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"))
? 'export'
: null;

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

/**
Expand Down
Loading
Loading