Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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. |

---
Expand Down
15 changes: 10 additions & 5 deletions docs/core/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
```

Expand Down
4 changes: 4 additions & 0 deletions docs/core/prompts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions docs/getting-started/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
35 changes: 31 additions & 4 deletions src/Console/Commands/ActivatePromptCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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) {
Expand All @@ -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];
}
}
62 changes: 48 additions & 14 deletions src/PromptManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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.");
}
}
}
31 changes: 23 additions & 8 deletions tests/Unit/PromptManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,30 +274,44 @@ 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',
'description' => 'My prompt',
'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)
// =====================================================================
Expand Down Expand Up @@ -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
Expand Down