Skip to content

Commit

Permalink
[5.x] Add new updatable and package starter kit conventions (#11119)
Browse files Browse the repository at this point in the history
  • Loading branch information
jesseleite authored Dec 4, 2024
1 parent 9493598 commit 67463fe
Show file tree
Hide file tree
Showing 11 changed files with 558 additions and 273 deletions.
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

0 comments on commit 67463fe

Please sign in to comment.