diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e6ff0e8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## 0.1.1 — 2026-10-06 + +- Use stable Starter 1.1.8 for new projects instead of the removed development branch. +- Keep the requested version and verified commit fixed while loading repository catalogs. A catalog's default version is metadata; its repository and package identity must still match. +- Cover default and explicitly selected tagged templates without creating files during dry runs. diff --git a/README.md b/README.md index 1fdd76e..211f9ff 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ sympress project:create [directory] [options] - `--dev-package=vendor/name[:constraint]` - `--no-suggested-packages` - `--repository=https://github.com/SymPress/starter` -- `--template-version=1.0.x-dev` +- `--template-version=1.1.8` - `--manifest=./sympress-cli.json` - `--manifest=https://github.com/SymPress/starter` - `--manifest-ref=<40-character-Git-SHA>` @@ -183,7 +183,7 @@ Update options: - `--type=website|app|microservice|commerce` - `--template=sympress-starter` - `--repository=https://github.com/SymPress/starter` -- `--template-version=1.0.x-dev` +- `--template-version=1.1.8` - `--manifest=./sympress-cli.json` - `--manifest-ref=<40-character-Git-SHA>` - `--no-remote-manifest` @@ -202,6 +202,11 @@ When the starter changes, update this manifest in the starter repository. The CL will pick it up on the next run, so setup commands, project types and package suggestions can evolve with the repository instead of being hardcoded in the CLI. +New projects default to the stable Starter 1.1.8 tag. An explicitly selected +version and its verified commit remain fixed even when the catalog at that commit +advertises a different default. Repository and Composer package identities must +still match before any project is materialized. + Example: ```json @@ -215,7 +220,7 @@ Example: "packageName": "sympress/starter", "repositoryUrl": "https://github.com/SymPress/starter", "description": "DDEV-ready SymPress WordPress starter.", - "defaultVersion": "1.0.x-dev", + "defaultVersion": "1.1.8", "setupCommand": ["bin/console", "setup", "{project_slug}"] } ], diff --git a/src/Command/UpdateProjectCommand.php b/src/Command/UpdateProjectCommand.php index 0bb1dd1..07896e2 100644 --- a/src/Command/UpdateProjectCommand.php +++ b/src/Command/UpdateProjectCommand.php @@ -175,13 +175,14 @@ private function catalogs(InputInterface $input, ProjectMetadata $metadata): arr foreach ($remoteManifest->templates as $candidate) { if ( $candidate->id === $selected->id && ($candidate->repositoryUrl !== $selected->repositoryUrl - || $candidate->packageName !== $selected->packageName - || $candidate->defaultVersion !== $selected->defaultVersion) + || $candidate->packageName !== $selected->packageName) ) { throw new RuntimeException('Snapshot manifest cannot redirect the selected template.'); } } - return [...$this->applyManifest($remoteManifest, $templates, $profiles, $packages), $revision]; + [$templates, $profiles, $packages] = $this->applyManifest($remoteManifest, $templates, $profiles, $packages); + $templates = $templates->withTemplates([$templates->get($selected->id)->withVersion($version)]); + return [$templates, $profiles, $packages, $revision]; } private function level(InputInterface $input, SymfonyStyle $io): string diff --git a/src/Generator/ProjectGenerator.php b/src/Generator/ProjectGenerator.php index e1f96a4..0de3091 100644 --- a/src/Generator/ProjectGenerator.php +++ b/src/Generator/ProjectGenerator.php @@ -88,11 +88,10 @@ public function generate(ProjectConfiguration $configuration, SymfonyStyle $io): if ( $candidate->repositoryUrl !== $setupTemplate->repositoryUrl || $candidate->packageName !== $setupTemplate->packageName - || $candidate->defaultVersion !== $setupTemplate->defaultVersion ) { throw new RuntimeException('Snapshot manifest cannot redirect the selected template.'); } - $setupTemplate = $candidate; + $setupTemplate = $candidate->withVersion($configuration->templateVersion ?: $setupTemplate->defaultVersion); } $this->filesystem->remove($configuration->directory . '/.git'); $io->section('Apply initial configuration'); diff --git a/src/IO/ProjectQuestionnaire.php b/src/IO/ProjectQuestionnaire.php index e8e6954..aa6b9d1 100644 --- a/src/IO/ProjectQuestionnaire.php +++ b/src/IO/ProjectQuestionnaire.php @@ -82,12 +82,13 @@ public function ask(InputInterface $input, OutputInterface $output, string $cwd) $updatedTemplate = $templates->get($template->id); if ( $updatedTemplate->repositoryUrl !== $template->repositoryUrl - || $updatedTemplate->defaultVersion !== $template->defaultVersion || $updatedTemplate->packageName !== $template->packageName ) { throw new \RuntimeException('A manifest cannot redirect the selected template repository or version.'); } - $template = $updatedTemplate; + // Catalog metadata may advertise a different default. The version + // already resolved to a SHA remains the user's selected version. + $template = $updatedTemplate->withVersion($version); if ($this->emptyToNull($input->getOption('repository')) === null) { $templateRepository = $template->repositoryUrl; @@ -125,7 +126,7 @@ public function ask(InputInterface $input, OutputInterface $output, string $cwd) runSetup: $this->runSetup($input, $io, $interactive), dryRun: (bool) $input->getOption('dry-run'), composerBinary: (string) $input->getOption('composer-bin'), - templateVersion: $this->emptyToNull($input->getOption('template-version')), + templateVersion: $version, templateRepository: $templateRepository, templateRevision: $revision, allowTemplateExecution: (bool) $input->getOption('allow-template-execution'), diff --git a/src/Model/TemplateDefinition.php b/src/Model/TemplateDefinition.php index de0b734..c3b2a34 100644 --- a/src/Model/TemplateDefinition.php +++ b/src/Model/TemplateDefinition.php @@ -15,11 +15,24 @@ public function __construct( public string $packageName, public string $repositoryUrl, public string $description, - public string $defaultVersion = '1.0.x-dev', + public string $defaultVersion = '1.1.8', public array $setupCommand = ['bin/console', 'setup', '{project_slug}'], ) { } + public function withVersion(string $version): self + { + return new self( + $this->id, + $this->label, + $this->packageName, + $this->repositoryUrl, + $this->description, + $version, + $this->setupCommand, + ); + } + public function packageSpec(?string $version = null): string { $selectedVersion = $version ?: $this->defaultVersion; diff --git a/src/Repository/RepositoryManifestParser.php b/src/Repository/RepositoryManifestParser.php index 92c6452..dab517b 100644 --- a/src/Repository/RepositoryManifestParser.php +++ b/src/Repository/RepositoryManifestParser.php @@ -117,7 +117,7 @@ private function template(mixed $template, string $path): TemplateDefinition repositoryUrl: $repositoryUrl, description: $this->string($template, 'description', $path) ?? 'Repository-provided starter template.', - defaultVersion: $this->string($template, 'defaultVersion', $path) ?? '1.0.x-dev', + defaultVersion: $this->string($template, 'defaultVersion', $path) ?? '1.1.8', setupCommand: $setupCommand, ); } diff --git a/tests/Command/CreateProjectCommandTest.php b/tests/Command/CreateProjectCommandTest.php index 226813e..0567ed1 100644 --- a/tests/Command/CreateProjectCommandTest.php +++ b/tests/Command/CreateProjectCommandTest.php @@ -7,9 +7,55 @@ use PHPUnit\Framework\TestCase; use SymPress\Cli\Command\CreateProjectCommand; use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Process\Process; final class CreateProjectCommandTest extends TestCase { + public function testDefaultAndExplicitReleaseKeepTheirResolvedRevisionDespiteManifestDefault(): void + { + $root = $this->temporaryDirectory(); + $repository = $root . '/repository'; + mkdir($repository . '/.sympress', 0700, true); + $git = static function (array $arguments) use ($repository): string { + $process = new Process(['git', '-C', $repository, ...$arguments]); + $process->setEnv(['GIT_DIR' => false, 'GIT_WORK_TREE' => false, 'GIT_INDEX_FILE' => false]); + $process->mustRun(); + return trim($process->getOutput()); + }; + try { + $git(['init', '-b', 'main']); + $git(['config', 'user.name', 'Fixture']); + $git(['config', 'user.email', 'fixture@example.test']); + file_put_contents($repository . '/composer.json', '{"name":"sympress/starter"}'); + file_put_contents($repository . '/.sympress/cli.json', json_encode([ + 'schemaVersion' => 1, 'templates' => [[ + 'id' => 'sympress-starter', 'label' => 'Starter', 'packageName' => 'sympress/starter', + 'repositoryUrl' => 'https://github.com/SymPress/starter', 'description' => 'Fixture', + 'defaultVersion' => '1.0.0', + ]], + ], JSON_THROW_ON_ERROR)); + $git(['add', '.']); + $git(['commit', '-m', 'Release with older manifest default']); + $sha = $git(['rev-parse', 'HEAD']); + $git(['tag', 'v1.1.8']); + $git(['tag', 'v1.1.6']); + foreach ([null, '1.1.6'] as $version) { + $options = ['directory' => $root . '/project', '--repository' => $repository, + '--dry-run' => true, '--no-setup' => true, '--name' => 'Fixture']; + if ($version !== null) { + $options['--template-version'] = $version; + } + $tester = new CommandTester(new CreateProjectCommand()); + self::assertSame(0, $tester->execute($options, ['interactive' => false]), $tester->getDisplay()); + self::assertStringContainsString('sympress/starter:' . ($version ?? '1.1.8'), $tester->getDisplay()); + self::assertStringContainsString($sha, $tester->getDisplay()); + self::assertDirectoryDoesNotExist($root . '/project'); + } + } finally { + new Filesystem()->remove($root); + } + } public function testDryRunUsesManifestProvidedCatalogEntries(): void { $directory = $this->temporaryDirectory();