Skip to content

Commit

Permalink
[5.x] Give starter kit devs an opt-in config to help make parts of th…
Browse files Browse the repository at this point in the history
…eir kits composer updatable (#11064)

* Spec out `addon: true` for starter kits that should live on as composer updatable addons.

* Blink config, no reason to read it twice.

* Pass tests.

* Refactor to `updatable: true`.

* Add test coverage to ensure package remains in `require` (not `require-dev`).

* Pass test. (We don’t really have reason to `requireDev` here anymore.)

* Adjust these tests too.
  • Loading branch information
jesseleite authored Dec 2, 2024
1 parent 3bccefd commit 0cc5cba
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 9 deletions.
20 changes: 15 additions & 5 deletions src/StarterKits/Installer.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ function () {
: $this->package;

try {
Composer::withoutQueue()->throwOnFailure()->requireDev($package);
Composer::withoutQueue()->throwOnFailure()->require($package);
} catch (ProcessException $exception) {
$this->rollbackWithError("Error installing starter kit [{$package}].", $exception->getMessage());
}
Expand Down Expand Up @@ -549,14 +549,14 @@ function () {
*/
public function removeStarterKit(): self
{
if ($this->disableCleanup) {
if ($this->isUpdatable() || $this->disableCleanup) {
return $this;
}

spin(
function () {
if (Composer::isInstalled($this->package)) {
Composer::withoutQueue()->throwOnFailure(false)->removeDev($this->package);
Composer::withoutQueue()->throwOnFailure(false)->remove($this->package);
}
},
'Cleaning up temporary files...'
Expand Down Expand Up @@ -590,7 +590,7 @@ protected function completeInstall(): self
*/
protected function removeRepository(): self
{
if ($this->fromLocalRepo || ! $this->url) {
if ($this->isUpdatable() || $this->fromLocalRepo || ! $this->url) {
return $this;
}

Expand Down Expand Up @@ -673,12 +673,22 @@ protected function starterKitPath(?string $path = null): string
*/
protected function config(?string $key = null): mixed
{
$config = collect(YAML::parse($this->files->get($this->starterKitPath('starter-kit.yaml'))));
$config = Blink::once('starter-kit-config', function () {
return collect(YAML::parse($this->files->get($this->starterKitPath('starter-kit.yaml'))));
});

if ($key) {
return $config->get($key);
}

return $config;
}

/**
* Should starter kit be treated as an updatable package, and live on for future composer updates, etc?
*/
protected function isUpdatable(): bool
{
return (bool) $this->config('updatable');
}
}
89 changes: 85 additions & 4 deletions tests/StarterKits/InstallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Statamic\Facades\Config;
use Statamic\Facades\Path;
use Statamic\Facades\YAML;
use Statamic\Support\Arr;
use Statamic\Support\Str;
use Tests\Fakes\Composer\FakeComposer;
use Tests\TestCase;
Expand Down Expand Up @@ -61,6 +62,7 @@ public function it_installs_starter_kit()
$this->assertFalse(Blink::has('starter-kit-repository-added'));
$this->assertFileDoesNotExist($this->kitVendorPath());
$this->assertFileDoesNotExist(base_path('composer.json.bak'));
$this->assertComposerJsonDoesntHavePackage('statamic/cool-runnings');
$this->assertComposerJsonDoesntHave('repositories');
$this->assertFileExists(base_path('copied.md'));
}
Expand Down Expand Up @@ -258,6 +260,70 @@ public function it_restores_existing_repositories_after_successful_install()
$this->assertEquals($expectedRepositories, $composerJson['repositories']);
}

#[Test]
public function it_installs_as_living_package_with_custom_config()
{
$this->setConfig([
'updatable' => true, // With `updatable: true`, kit should live on as composer updatable package
'export_paths' => [
'copied.md',
],
]);

$this->assertFileDoesNotExist(base_path('copied.md'));
$this->assertFileDoesNotExist($this->kitVendorPath());

$this->installCoolRunnings();

$this->assertFileExists(base_path('copied.md'));
$this->assertComposerJsonDoesntHave('repositories');

// Keep package around
$this->assertFileExists($this->kitVendorPath());
$this->assertComposerJsonHasPackage('require', 'statamic/cool-runnings');

// But ensure we still delete backup composer.json, which is only used for error handling purposes
$this->assertFileDoesNotExist(base_path('composer.json.bak'));
}

#[Test]
public function it_leaves_custom_repository_for_living_packages_that_need_it()
{
$this->setConfig([
'updatable' => true, // With `updatable: true`, kit should live on as composer updatable package
'export_paths' => [
'copied.md',
],
]);

$this->assertFileDoesNotExist(base_path('copied.md'));
$this->assertFileDoesNotExist($this->kitVendorPath());
$this->assertComposerJsonDoesntHave('repositories');

$this->installCoolRunnings([], [
'outpost.*' => Http::response(['data' => ['price' => null]], 200),
'github.com/*' => Http::response('', 200),
'*' => Http::response('', 404),
]);

$this->assertFileExists(base_path('copied.md'));

// Keep package around
$this->assertFileExists($this->kitVendorPath());
$this->assertComposerJsonHasPackage('require', 'statamic/cool-runnings');

// As well as custom repository, which will be needed for composer updates, if it was needed for install
$composerJson = json_decode($this->files->get(base_path('composer.json')), true);
$this->assertCount(1, $composerJson['repositories']);
$this->assertEquals([[
'type' => 'vcs',
'url' => 'https://github.com/statamic/cool-runnings',
]], $composerJson['repositories']);

// But delete backup composer.json, which is only used for error handling purposes
$this->assertFileDoesNotExist(base_path('composer.json.bak'));
}

#[Test]
public function it_fails_if_starter_kit_config_does_not_exist()
{
Expand Down Expand Up @@ -737,8 +803,8 @@ public function it_parses_branch_from_package_param_when_installing()
]);

// Ensure `Composer::requireDev()` gets called with `package:branch`
$this->assertEquals(Blink::get('composer-require-dev-package'), 'statamic/cool-runnings');
$this->assertEquals(Blink::get('composer-require-dev-branch'), 'dev-custom-branch');
$this->assertEquals(Blink::get('composer-require-package'), 'statamic/cool-runnings');
$this->assertEquals(Blink::get('composer-require-branch'), 'dev-custom-branch');

// But ensure the rest of the installer handles parsed `package` without branch messing things up
$this->assertFalse(Blink::has('starter-kit-repository-added'));
Expand All @@ -760,8 +826,8 @@ public function it_installs_branch_with_slash_without_failing_package_validation
]);

// Ensure `Composer::requireDev()` gets called with `package:branch`
$this->assertEquals(Blink::get('composer-require-dev-package'), 'statamic/cool-runnings');
$this->assertEquals(Blink::get('composer-require-dev-branch'), 'dev-feature/custom-branch');
$this->assertEquals(Blink::get('composer-require-package'), 'statamic/cool-runnings');
$this->assertEquals(Blink::get('composer-require-branch'), 'dev-feature/custom-branch');

// But ensure the rest of the installer handles parsed `package` without branch messing things up
$this->assertFalse(Blink::has('starter-kit-repository-added'));
Expand Down Expand Up @@ -1652,6 +1718,21 @@ private function assertFileDoesntHaveContent($expected, $path)
$this->assertStringNotContainsString($expected, $this->files->get($path));
}

private function assertComposerJsonHasPackage($requireKey, $package)
{
$composerJson = json_decode($this->files->get(base_path('composer.json')), true);

$this->assertTrue(Arr::has($composerJson, "{$requireKey}.{$package}"));
}

private function assertComposerJsonDoesntHavePackage($package)
{
$composerJson = json_decode($this->files->get(base_path('composer.json')), true);

$this->assertFalse(Arr::has($composerJson, "require.{$package}"));
$this->assertFalse(Arr::has($composerJson, "require-dev.{$package}"));
}

private function assertComposerJsonHasPackageVersion($requireKey, $package, $version)
{
$composerJson = json_decode($this->files->get(base_path('composer.json')), true);
Expand Down

0 comments on commit 0cc5cba

Please sign in to comment.