diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a8e3b7..57b9beb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +### Removed + +## [0.4.1] - 2026-05-17 + +### Fixed + +- Refactored `ActivatePromptCommand` and `PromptManager` to support prompt version activation in formats `1` or `v1`. + +## [0.4.0] - 2026-05-17 + +### Added + +### Changed + - Renamed the package from `veeqtoh/prompt-deck` to `promptphp/deck`. - Renamed the PHP namespace from `Veeqtoh\PromptDeck` to `PromptPHP\Deck`. - Renamed the public package identity from Prompt Deck to Deck by PromptPHP. diff --git a/README.md b/README.md index 8844866..2c89bb5 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,10 @@ Activate a specific version ```bash php artisan prompt:activate order-summary v2 + +# or + +php artisan prompt:activate order-summary 2 ``` Or load a specific version programmatically diff --git a/docs/advanced/api-reference.mdx b/docs/advanced/api-reference.mdx index eb884dd..da57f2b 100644 --- a/docs/advanced/api-reference.mdx +++ b/docs/advanced/api-reference.mdx @@ -225,7 +225,7 @@ Static proxy to the `PromptManager` singleton. | `Deck::get(string $name, ?int $version = null)` | `PromptTemplate` | Load a prompt by name and optional version. | | `Deck::active(string $name)` | `PromptTemplate` | Load the active version of a prompt. | | `Deck::versions(string $name)` | `array` | List all versions for a prompt. | -| `Deck::activate(string $name, int $version)` | `bool` | Activate a specific version. | +| `Deck::activate(string $name, string $version)` | `bool` | Activate a specific version. | | `Deck::track(string $name, int $version, array $data)` | `void` | Record a prompt execution. | --- diff --git a/docs/core/commands.mdx b/docs/core/commands.mdx index f647116..89e0797 100644 --- a/docs/core/commands.mdx +++ b/docs/core/commands.mdx @@ -154,20 +154,25 @@ prompt:activate {name : The prompt name} {version : The version number to activa ### Arguments -| Argument | Required | Description | -| --------- | -------- | ------------------------------------------ | -| `name` | Yes | The prompt name (e.g. `order-summary`). | -| `version` | Yes | The version number to activate (e.g. `2`). | +| Argument | Required | Description | +| --------- | -------- | -----------------------------------------------| +| `name` | Yes | The prompt name (e.g. `order-summary`). | +| `version` | Yes | The version number to activate (e.g. `2, v2`). | ### Examples ```bash php artisan prompt:activate order-summary 2 + +# or + +php artisan prompt:activate order-summary v2 ``` Output: -``` + +```bash Version 2 of prompt [order-summary] activated. ``` diff --git a/docs/core/prompts.mdx b/docs/core/prompts.mdx index d4c1699..a4d9951 100644 --- a/docs/core/prompts.mdx +++ b/docs/core/prompts.mdx @@ -335,6 +335,10 @@ Or use the Artisan command: ```bash php artisan prompt:activate order-summary 2 + +# or + +php artisan prompt:activate order-summary v2 ``` See [Artisan Commands — prompt:activate](/core/commands#promptactivate) for details. diff --git a/docs/getting-started/introduction.mdx b/docs/getting-started/introduction.mdx index 2e94b80..27dac10 100644 --- a/docs/getting-started/introduction.mdx +++ b/docs/getting-started/introduction.mdx @@ -77,6 +77,10 @@ Activate a specific version: ```bash php artisan prompt:activate order-summary v2 + +# or + +php artisan prompt:activate order-summary 2 ``` Or load a specific version programmatically: diff --git a/src/Console/Commands/ActivatePromptCommand.php b/src/Console/Commands/ActivatePromptCommand.php index d0cedce..b8903e5 100644 --- a/src/Console/Commands/ActivatePromptCommand.php +++ b/src/Console/Commands/ActivatePromptCommand.php @@ -10,7 +10,7 @@ class ActivatePromptCommand extends Command { protected $signature = 'prompt:activate {name : The prompt name} - {version : The version number to activate}'; + {version : The version number to activate, e.g. 1 or v1}'; protected $description = 'Activate a specific version of a prompt'; @@ -24,12 +24,21 @@ public function __construct(PromptManager $manager) public function handle(): int { - $name = $this->argument('name'); - $version = (int) $this->argument('version'); + $name = (string) $this->argument('name'); + $versionInput = (string) $this->argument('version'); + + $version = $this->parseVersion($versionInput); + + if ($version === null) { + $this->error("Invalid version [{$versionInput}] provided. Use a positive number like [1] or [v1]."); + + return Command::FAILURE; + } try { $this->manager->activate($name, $version); - $this->info("Version {$version} of prompt [{$name}] activated."); + + $this->info("Version {$versionInput} of prompt [{$name}] activated."); return Command::SUCCESS; } catch (\Exception $e) { @@ -38,4 +47,22 @@ public function handle(): int return Command::FAILURE; } } + + /** + * Parse the version input and return the version number as an integer. + * + * @param string $value The version input, e.g. "1" or "v1". + * + * @return int|null The parsed version number, or null if invalid. + */ + protected function parseVersion(string $value): ?int + { + $value = trim($value); + + if (! preg_match('/^v?([1-9]\d*)$/i', $value, $matches)) { + return null; + } + + return (int) $matches[1]; + } } diff --git a/src/PromptManager.php b/src/PromptManager.php index 65dabd1..6e548ed 100644 --- a/src/PromptManager.php +++ b/src/PromptManager.php @@ -119,34 +119,44 @@ public function versions(string $name): array /** * Activate a specific version. + * + * For simplicity, we'll store in a JSON file or use the database if tracking is enabled. + * We'll assume we have a "prompt_versions" table with an "is_active" column. */ public function activate(string $name, int $version): bool { - // Store active version in database or config file? - // For simplicity, we'll store in a JSON file or use the database if tracking is enabled. - // We'll assume we have a "prompt_versions" table with an "is_active" column. + $this->ensureVersionExists($name, $version); + if ($this->trackingConfig['enabled'] ?? false) { - // Update database. - DB::connection($this->trackingConfig['connection'] ?? config('database.default')) - ->table('prompt_versions') - ->where('name', $name) - ->update(['is_active' => false]); + $connection = DB::connection( + $this->trackingConfig['connection'] ?? config('database.default') + ); - return DB::table('prompt_versions') - ->where('name', $name) - ->where('version', $version) - ->update(['is_active' => true]) > 0; + // Update database. + $connection->transaction(function () use ($connection, $name, $version): void { + $connection + ->table('prompt_versions') + ->where('name', $name) + ->update(['is_active' => false]); + + $connection + ->table('prompt_versions') + ->where('name', $name) + ->where('version', $version) + ->update(['is_active' => true]); + }); } // Fallback: store in a JSON file in the prompt directory. $metadataFile = "{$this->basePath}/{$name}/metadata.json"; + $metadata = $this->files->exists($metadataFile) - ? json_decode($this->files->get($metadataFile), true) + ? json_decode($this->files->get($metadataFile), true) ?? [] : []; $metadata['active_version'] = $version; - $this->files->put($metadataFile, json_encode($metadata, JSON_PRETTY_PRINT)); + $this->files->put($metadataFile, json_encode($metadata, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); return true; } @@ -267,4 +277,28 @@ protected function loadMetadata(string $name, int $version): array return []; } + + /** + * Ensure the specified prompt version exists before activation. + * + * @throws \InvalidArgumentException if the version is invalid or does not exist. + */ + protected function ensureVersionExists(string $name, int $version): void + { + if ($version < 1) { + throw new \InvalidArgumentException('The prompt version must be a positive integer.'); + } + + $promptPath = "{$this->basePath}/{$name}"; + + if (! $this->files->isDirectory($promptPath)) { + throw new \InvalidArgumentException("Prompt [{$name}] does not exist."); + } + + $versionPath = "{$promptPath}/v{$version}"; + + if (! $this->files->isDirectory($versionPath)) { + throw new \InvalidArgumentException("Version [{$version}] does not exist for prompt [{$name}]. Create it first before activating."); + } + } } diff --git a/tests/Unit/PromptManagerTest.php b/tests/Unit/PromptManagerTest.php index 7a40104..2ac4b2e 100644 --- a/tests/Unit/PromptManagerTest.php +++ b/tests/Unit/PromptManagerTest.php @@ -274,6 +274,8 @@ function freshManager(?array $configOverrides = []): PromptManager expect($meta['active_version'])->toBe(1); }); +use InvalidArgumentException; + test('activate() preserves existing metadata keys when updating active_version', function () { $this->createPromptFixture('preserve-meta', 1, 'sys', 'usr', null, [ 'name' => 'preserve-meta', @@ -281,23 +283,35 @@ function freshManager(?array $configOverrides = []): PromptManager 'active_version' => 1, ]); + // Version 2 must actually exist before it can be activated. + mkdir("{$this->tempDir}/preserve-meta/v2", 0777, true); + freshManager()->activate('preserve-meta', 2); $meta = json_decode(file_get_contents("{$this->tempDir}/preserve-meta/metadata.json"), true); + expect($meta['active_version'])->toBe(2) ->and($meta['name'])->toBe('preserve-meta') ->and($meta['description'])->toBe('My prompt'); }); -test('activate() always returns true in filesystem mode', function () { +test('activate() returns true in filesystem mode when version exists', function () { $this->createPromptFixture('fs-activate', 1, 'sys', 'usr'); - // Even for a non-existent version number, filesystem mode always returns true - $result = freshManager()->activate('fs-activate', 999); + $result = freshManager()->activate('fs-activate', 1); expect($result)->toBeTrue(); }); +test('activate() throws in filesystem mode when version does not exist', function () { + $this->createPromptFixture('fs-activate-missing', 1, 'sys', 'usr'); + + freshManager()->activate('fs-activate-missing', 999); +})->throws( + InvalidArgumentException::class, + 'Version [999] does not exist for prompt [fs-activate-missing].' +); + // ===================================================================== // activate() — database mode (tracking enabled) // ===================================================================== @@ -328,16 +342,17 @@ function freshManager(?array $configOverrides = []): PromptManager ->and((bool) $v2->is_active)->toBeTrue(); }); -test('activate() with tracking enabled returns false when version not in DB', function () { +test('activate() with tracking enabled throws when prompt does not exist', function () { $this->setUpTrackingTables(); $this->app['config']->set('deck.tracking.enabled', true); $this->app['config']->set('deck.tracking.connection', 'testing'); - $result = freshManager()->activate('nonexistent', 99); - - expect($result)->toBeFalse(); -}); + freshManager()->activate('nonexistent', 99); +})->throws( + InvalidArgumentException::class, + 'Prompt [nonexistent] does not exist.' +); // ===================================================================== // getActiveVersion() — database mode