### Creating and Using Custom Facts Source: https://context7.com/vjik/scaffolder/llms.txt Illustrates how to create a custom Fact class, `DatabaseDriver`, which can be configured via CLI options or user prompts. It demonstrates registering the custom fact with the Runner and using it to dynamically generate a `.env` file. Includes an example of how to run the scaffolder with a custom argument. ```php */ final class DatabaseDriver extends Fact { public const string VALUE_OPTION = 'db-driver'; private const array VALID_DRIVERS = ['mysql', 'pgsql', 'sqlite']; public static function configureCommand(SymfonyCommand $command, Params $params): void { $command->addOption( self::VALUE_OPTION, mode: InputOption::VALUE_OPTIONAL, description: 'Database driver (mysql, pgsql, sqlite)', default: $params->get(self::VALUE_OPTION), ); } public static function resolve(Cli $cli, Context $context): string { // Check CLI option first $value = $cli->getOption(self::VALUE_OPTION); if ($value !== null && $value !== '') { return self::normalize($value); } // Prompt user with validation return $cli->ask( question: 'Database driver', default: 'mysql', normalizer: self::normalize(...), ); } private static function normalize(string $input): string { $driver = strtolower(trim($input)); if (!in_array($driver, self::VALID_DRIVERS, true)) { throw new NormalizeInputException( 'Invalid driver. Choose: ' . implode(', ', self::VALID_DRIVERS) ); } return $driver; } } // Usage with custom fact new Runner( changes: [ new WriteFile('.env', fn(Context $ctx) => "DB_DRIVER=" . $ctx->getFact(DatabaseDriver::class) ), ], factClasses: [DatabaseDriver::class], // Register custom fact )->run(); // Run: php scaffolder.php --db-driver=pgsql ``` -------------------------------- ### Execute Scaffolding Process with Runner in PHP Source: https://context7.com/vjik/scaffolder/llms.txt The `Runner` class is the main entry point for executing the scaffolding process. It accepts an array of `Change` objects, optional custom fact classes, and default parameters to manage automated project modifications. The example demonstrates initializing the runner with file writing, composer preparation, file copying, and directory ensuring changes, along with parameters for namespace and license. ```php 'MyVendor\\MyProject', 'license' => 'MIT', ], )->run(); // Run with CLI options: // php my-scaffolder.php --directory=/path/to/project --namespace=Acme\\App ``` -------------------------------- ### Copy Files with CopyFile and CopyFileIfNotExists in PHP Source: https://context7.com/vjik/scaffolder/llms.txt The `CopyFile` and `CopyFileIfNotExists` change types facilitate copying files from a template directory to the target project. `CopyFile` will always overwrite the target file if it exists and differs, while `CopyFileIfNotExists` will only copy the file if the target does not already exist, preserving existing customizations. The example demonstrates copying `rector.php` unconditionally and `.php-cs-fixer.dist.php` only if it doesn't exist. ```php run(); ``` -------------------------------- ### Dynamically Write File Content with WriteFile Change in PHP Source: https://context7.com/vjik/scaffolder/llms.txt The `WriteFile` change type writes content to a specified file, performing intelligent change detection to avoid unnecessary writes. It supports static content, Stringable objects, or closures for dynamic content generation based on contextual information like package name and description. The example shows writing a `.gitignore` file with static content and a `README.md` file with dynamically generated content using a closure and context facts. ```php getFact(PackageName::class); $description = $context->getFact(PackageDescription::class); return <<run(); // Output: // Write `.gitignore`... Done. // Write `README.md`... Done. // Success! 2 changes applied. ``` -------------------------------- ### Conditionally Write File Content with WriteFileIfNotExists in PHP Source: https://context7.com/vjik/scaffolder/llms.txt The `WriteFileIfNotExists` change type ensures a file is created with specified content only if the file does not already exist. This is particularly useful for generating default configuration files that should not overwrite existing user customizations. The example demonstrates creating `.env` and `phpunit.xml` files if they are not present. ```php tests XML), ], )->run(); // If files already exist, no changes will be applied ``` -------------------------------- ### Run Scaffolder with Command Line Options (Bash) Source: https://github.com/vjik/scaffolder/blob/master/README.md Shows how to execute the scaffolder using the command line, specifying the target directory and a custom configuration file name. This allows for dynamic configuration at runtime. ```bash php my-scaffolder.php --directory=/path/to/project --scaffolder-file=my-config.php ``` -------------------------------- ### Runner Initialization with Changes and Parameters Source: https://context7.com/vjik/scaffolder/llms.txt Initializes the `Runner` with various changes like creating README and composer files, and sets default parameters. It demonstrates how to use named changes and how CLI arguments can override default configurations. ```php new Change\WriteFile('README.md', '# Project'), 'composer' => new Change\PrepareComposerJson(), 'phpstan' => new Change\WriteFile('phpstan.neon', 'level: 10'), ], params: [ // Default values (lowest priority) 'namespace' => 'DefaultVendor\\DefaultProject', 'license' => 'BSD-3-Clause', ], )->run(); // Run with CLI overrides (highest priority): // php my-scaffolder.php --directory=/path/to/project --namespace=Acme\\App // php my-scaffolder.php --scaffolder-file=custom-config.php ``` -------------------------------- ### Interactive Prompts with Validation and Normalization Source: https://context7.com/vjik/scaffolder/llms.txt Demonstrates using the `Cli` class for interactive prompts, including simple questions and more complex ones with validation and normalization. It shows how to handle user input and enforce specific formats. ```php */ final class ProjectType extends Fact { public static function resolve(Cli $cli, Context $context): string { // Simple prompt $type = $cli->ask('Project type', 'library'); // Prompt with validation/normalization $type = $cli->ask( question: 'Project type (library/project)', default: 'library', normalizer: function (string $input): string { $normalized = strtolower(trim($input)); if (!in_array($normalized, ['library', 'project'], true)) { throw new NormalizeInputException( 'Invalid type. Choose "library" or "project".' ); } return $normalized; }, ); return $type; } } // Cli is also available in applier callables: // fn(Cli $cli) => $cli->step('Processing', fn() => doSomething()) ``` -------------------------------- ### Configure Scaffolder with scaffolder.php File (PHP) Source: https://github.com/vjik/scaffolder/blob/master/README.md Demonstrates how to set project-specific configuration parameters for the scaffolder by creating a `scaffolder.php` file. This file should return an array of key-value pairs for parameters like 'namespace' and 'license'. ```php 'MyVendor\\MyProject', 'license' => 'MIT', ]; ``` -------------------------------- ### File Operations and Fact Resolution with Context Source: https://context7.com/vjik/scaffolder/llms.txt Demonstrates how to use the Context object to read and write files, check file existence, resolve facts like package name and namespace, access parameters, and execute shell commands within a Change closure. It shows basic usage for generating a bootstrap file. ```php tryReadFile('composer.json'); // Returns null if not exists // Check file existence if ($context->fileExists('config/app.php')) { $config = $context->readFile('config/app.php'); // Throws if not exists } // Resolve facts (cached after first resolution) $packageName = $context->getFact(PackageName::class); // e.g., "vendor/package" $namespace = $context->getFact(NamespaceX::class); // e.g., "Vendor\\Package" // Access parameters $debug = $context->getParam('debug', false); // Execute shell commands $result = $context->execute('composer --version', throwExceptionOnError: false); if ($result->isSuccess()) { $composerVersion = implode("\n", $result->lines); } return " true], )->run(); ``` -------------------------------- ### Running PHP Scaffolder Script Source: https://github.com/vjik/scaffolder/blob/master/README.md Shows the command-line execution of a PHP Scaffolder script. This command initiates the process defined in the PHP script. ```bash php my-scaffolder.php ``` -------------------------------- ### PHP CLI Wrapper for User Interaction Source: https://github.com/vjik/scaffolder/blob/master/AGENTS.md The `Cli` class in PHP acts as a wrapper around Symfony Console I/O, simplifying user interactions. It provides methods for executing operations with progress output (`step`), handling interactive prompts (`ask`), displaying success messages (`success`), and accessing command options (`getOption`). ```php getFact(NamespaceX::class); $filePath = 'src/' . $this->className . '.php'; $content = <<className} { public function run(): void { // Application entry point } } PHP; // Skip if file exists with same content if ($context->tryReadFile($filePath) === $content) { return null; } // Return applier callable return fn(Cli $cli) => $cli->step( "Create `{$filePath}`", fn() => $context->writeTextFile($filePath, $content), ); } } new Runner( changes: [ new CreateEntryPointClass('App'), ], )->run(); // Output: // Create `src/App.php`... Done. // Success! 1 change applied. ``` -------------------------------- ### General Usage of PHP Scaffolder Source: https://github.com/vjik/scaffolder/blob/master/README.md Demonstrates how to use the PHP Scaffolder library to perform automated file modifications. It shows how to instantiate the Runner with a list of changes and then execute them. ```php use Vjik\Scaffolder\Change; use Vjik\Scaffolder\Runner; require_once __DIR__ . '/vendor/autoload.php'; new Runner( changes: [ new Change\WriteFile('README.md', 'Hello World'), new Change\PrepareComposerJson(), new Change\CopyFile( from: __DIR__ . '/templates/LICENSE', to: 'LICENSE', ), ], )->run(); ``` -------------------------------- ### Write License File Source: https://context7.com/vjik/scaffolder/llms.txt Writes a LICENSE file using predefined license templates like MIT or BSD-3-Clause. It automatically substitutes copyright holder and year information gathered from facts. Custom filenames can also be specified. ```php run(); // Output: // Write `LICENSE`... Done. // Success! 1 change applied. ``` -------------------------------- ### PHP Runner Entry Point Source: https://github.com/vjik/scaffolder/blob/master/AGENTS.md The `Runner` class in PHP serves as the entry point for the scaffolding process. It accepts `Change` objects, optional `Fact` classes, and parameters, then orchestrates the creation and execution of a Symfony Console application. ```php run(); } // ... ``` -------------------------------- ### Set Default Scaffolder Parameters via Runner (PHP) Source: https://github.com/vjik/scaffolder/blob/master/README.md Illustrates setting default configuration parameters for the scaffolder when initializing the `Runner` class. These defaults can be overridden by parameters defined in a `scaffolder.php` file. ```php new Runner( // ... params: [ 'namespace' => 'DefaultVendor\\DefaultProject', 'license' => 'BSD-3-Clause', ], )->run(); ``` -------------------------------- ### Write Binary or Exact Content File with writeFile Source: https://github.com/vjik/scaffolder/blob/master/AGENTS.md Use writeFile for writing binary files (e.g., images) or when precise control over the file's content is necessary, without any automatic modifications like adding a trailing newline. This function is suitable for non-textual data or when the exact byte sequence must be preserved. It accepts the filename and the raw data. ```php $context->writeFile('image.png', $binaryData); ``` -------------------------------- ### Using Facts in PHP Scaffolder Source: https://github.com/vjik/scaffolder/blob/master/README.md Illustrates how to access contextual information using Facts within the PHP Scaffolder library. Facts are resolved on-demand and can retrieve data from files, user input, or other facts. ```php use Vjik\Scaffolder\Context; // Assuming $context is available, e.g., within a Change's decide method $context->getFact(PackageName::class); // Returns "vendor/package" $context->getFact(NamespaceX::class); // Returns "Vendor\\Package" ``` -------------------------------- ### Project Scaffolder Configuration Source: https://context7.com/vjik/scaffolder/llms.txt Defines project-specific configuration for the scaffolder, including namespace, license, and options to disable certain changes. This file serves as a central configuration point for project scaffolding. ```php 'MyCompany\\MyProject', 'license' => 'MIT', 'prepare-autoload-dev' => true, 'disable' => ['phpstan'], // Disable specific named changes ]; ``` -------------------------------- ### PHP Params Class for Configuration Source: https://github.com/vjik/scaffolder/blob/master/AGENTS.md The `Params` class in PHP provides a readonly, immutable container for arbitrary parameters. It is passed to the `Runner` and accessible via the `Context`, facilitating the passing of configuration to `Change` and `Fact` objects. ```php 'value']); // $context = new Context($cli, $params); // $paramValue = $context->getParam('key'); ``` -------------------------------- ### PHP Fact Normalization Guideline Source: https://github.com/vjik/scaffolder/blob/master/AGENTS.md When a `Fact` accepts a value from a command-line option and potentially prompts the user, both paths must use the same normalizer for consistent validation. This involves extracting the normalizer into a private static method and using it for both option values and interactive prompts. ```php getOption('user-email'); if ($optionValue !== null) { return self::normalize($optionValue); } return $cli->ask('Enter your email:', normalizer: self::normalize(...)); } private static function normalize(string $value): string { // Validation logic here, e.g., using filter_var or a regex if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email format.'); } return $value; } // ... ``` -------------------------------- ### Ensure Directory with .gitkeep Source: https://context7.com/vjik/scaffolder/llms.txt Creates a directory and adds a .gitkeep file to ensure it is tracked by Git. This change skips creation if the directory already exists and contains files. It can be used with static directory names or by referencing Fact classes. ```php run(); // Output: // Create `.gitkeep` in `src`... Done. // Create `.gitkeep` in `tests`... Done. // Create `.gitkeep` in `config`... Done. ``` -------------------------------- ### PHP Context for File Operations and Facts Source: https://github.com/vjik/scaffolder/blob/master/AGENTS.md The `Context` class in PHP provides essential utilities for the scaffolding process. It manages file operations (read, write, copy), fact resolution with memoization, parameter access, and command execution, all within the target directory. ```php getIO(), $input->getArgument('params')); $appliers = []; foreach ($this->changes as $change) { $result = $change->decide($context); if (is_callable($result)) { $appliers[] = $result; } elseif (is_array($result)) { $appliers = array_merge($appliers, $result); } } foreach ($appliers as $applier) { $applier($context->getIO()); } return count($appliers); } // ... ``` -------------------------------- ### Write Text File with writeTextFile Source: https://github.com/vjik/scaffolder/blob/master/AGENTS.md Use writeTextFile to write text-based files like LICENSE, README.md, or composer.json. This function automatically adds a trailing newline if one is missing, adhering to standard text file conventions. It takes the filename and the text content as arguments. ```php $context->writeTextFile('LICENSE', $licenseText); $context->writeTextFile('composer.json', $json); $context->writeTextFile('README.md', $readme); ``` -------------------------------- ### Update composer.json with Package Information Source: https://context7.com/vjik/scaffolder/llms.txt Updates the composer.json file with package details like name, description, license, authors, PHP version constraints, and autoloading configuration. It automatically gathers information from facts and allows for custom modifications. ```php run(); // Output: // Write `composer.json`... Done. // Normalize `composer.json`... Done. // Success! 2 changes applied. ``` -------------------------------- ### Ensure Facts for User Input Source: https://context7.com/vjik/scaffolder/llms.txt Forces the resolution of one or more Facts, which can trigger user prompts for input without directly creating files. This is useful for gathering necessary information early in the scaffolding process. ```php "# " . $ctx->getFact(PackageName::class)), ], )->run(); ``` -------------------------------- ### Conditionally Apply Changes with ChangeIf Source: https://context7.com/vjik/scaffolder/llms.txt Applies a list of changes only if a specified boolean Fact evaluates to true. This is useful for implementing optional features or platform-specific configurations within the scaffolding process. ```php tests XML), ], fact: PrepareComposerAutoloadDev::class, ), ], )->run(); ``` -------------------------------- ### PHP Value Objects for Domain Data Source: https://github.com/vjik/scaffolder/blob/master/AGENTS.md Value objects within the `src/Value/` directory provide immutable, type-safe, and validated representations of domain-specific data. They are used by Facts and Changes to ensure consistent and reliable handling of business concepts. ```php value = $value; } public function getValue(): string { return $this->value; } } // Facts and Changes would use these objects for structured data. ``` -------------------------------- ### PHP Fact Abstract Class for Contextual Data Source: https://github.com/vjik/scaffolder/blob/master/AGENTS.md The `Fact` abstract class in PHP serves as a template for resolving contextual information. Its `resolve` method computes or prompts for a fact's value, and `configureCommand` can optionally add CLI options. Facts are lazily resolved and cached within the `Context`. ```php { abstract public function resolve(Cli $cli, Context $context): T; public function configureCommand(Command $command): void { // Optional: add CLI options for this fact } } // ... // Example Usage in Runner::BUILT_IN_FACT_CLASSES ``` -------------------------------- ### PHP Change Interface for Scaffolding Decisions Source: https://github.com/vjik/scaffolder/blob/master/AGENTS.md The `Change` interface in PHP defines the contract for scaffolding modifications. Implementations must provide a `decide` method that takes a `Context` and returns either an applier callable, an array of callables, or null. The applier signature is `callable(Cli): void`. ```php