├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── env-keys-checker.php ├── pint.json ├── rector.php ├── src ├── Actions │ ├── AddKeys.php │ ├── CheckKeys.php │ ├── FilterFiles.php │ └── GetKeys.php ├── Commands │ ├── EnvInGitIgnoreCommand.php │ ├── EnvKeysSyncCommand.php │ └── KeysCheckerCommand.php ├── Concerns │ └── HelperFunctions.php └── LaravelEnvKeysCheckerServiceProvider.php └── tlint.json /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-env-keys-checker` will be documented in this file. 4 | 5 | ## v1.8.0 - 2025-05-30 6 | 7 | ### What's Changed 8 | 9 | * Bump dependabot/fetch-metadata from 2.3.0 to 2.4.0 by @dependabot in https://github.com/msamgan/laravel-env-keys-checker/pull/23 10 | * Ractored. by @msamgan in https://github.com/msamgan/laravel-env-keys-checker/pull/24 11 | 12 | **Full Changelog**: https://github.com/msamgan/laravel-env-keys-checker/compare/v1.7.0...v1.8.0 13 | 14 | ## v1.7.0 - 2025-04-02 15 | 16 | ### What's Changed 17 | 18 | * L12 support by @msamgan in https://github.com/msamgan/laravel-env-keys-checker/pull/22 19 | 20 | **Full Changelog**: https://github.com/msamgan/laravel-env-keys-checker/compare/v1.6.0...v1.7.0 21 | 22 | ## v1.6.0 - 2025-02-24 23 | 24 | ### What's Changed 25 | 26 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/msamgan/laravel-env-keys-checker/pull/16 27 | 28 | ### New Contributors 29 | 30 | * @dependabot made their first contribution in https://github.com/msamgan/laravel-env-keys-checker/pull/16 31 | 32 | **Full Changelog**: https://github.com/msamgan/laravel-env-keys-checker/compare/v1.5.0...v1.6.0 33 | 34 | ## v1.5.0 - 2024-10-18 35 | 36 | ### What's Changed 37 | 38 | * Env key sync by @msamgan in https://github.com/msamgan/laravel-env-keys-checker/pull/15 39 | 40 | **Full Changelog**: https://github.com/msamgan/laravel-env-keys-checker/compare/v1.4.1...v1.5.0 41 | 42 | ## v1.4.1 - 2024-10-13 43 | 44 | ### What's Changed 45 | 46 | * Added no progress option by @fkrzski in https://github.com/msamgan/laravel-env-keys-checker/pull/14 47 | 48 | ### New Contributors 49 | 50 | * @fkrzski made their first contribution in https://github.com/msamgan/laravel-env-keys-checker/pull/14 51 | 52 | **Full Changelog**: https://github.com/msamgan/laravel-env-keys-checker/compare/v1.4.0...v1.4.1 53 | 54 | ## v1.4.0 - 2024-10-08 55 | 56 | ### What's Changed 57 | 58 | * V1.4.0 by @msamgan in https://github.com/msamgan/laravel-env-keys-checker/pull/13 59 | * Add environment vars in config file by @pelmered in https://github.com/msamgan/laravel-env-keys-checker/pull/10 60 | 61 | ### New Contributors 62 | 63 | * @pelmered made their first contribution in https://github.com/msamgan/laravel-env-keys-checker/pull/10 64 | 65 | **Full Changelog**: https://github.com/msamgan/laravel-env-keys-checker/compare/v1.3.0...v1.4.0 66 | 67 | ## v1.3.0 - 2024-10-07 68 | 69 | ### What's Changed 70 | 71 | * Space line preservation by @msamgan in https://github.com/msamgan/laravel-env-keys-checker/pull/4 72 | 73 | **Full Changelog**: https://github.com/msamgan/laravel-env-keys-checker/compare/v1.2.0...v1.3.0 74 | 75 | ## v1.2.0 - 2024-10-06 76 | 77 | ### What's Changed 78 | 79 | * added config file. by @msamgan in https://github.com/msamgan/laravel-env-keys-checker/pull/2 80 | * Auto Add by @msamgan in https://github.com/msamgan/laravel-env-keys-checker/pull/3 81 | 82 | ### New Contributors 83 | 84 | * @msamgan made their first contribution in https://github.com/msamgan/laravel-env-keys-checker/pull/2 85 | 86 | **Full Changelog**: https://github.com/msamgan/laravel-env-keys-checker/compare/v1.1.0...v1.2.0 87 | 88 | ## v1.1.0 - 2024-10-06 89 | 90 | ### What's Changed 91 | 92 | * Add Line Number Feature and Performance Improvements to Env Keys Checker by @erhanurgun in https://github.com/msamgan/laravel-env-keys-checker/pull/1 93 | 94 | ### New Contributors 95 | 96 | * @erhanurgun made their first contribution in https://github.com/msamgan/laravel-env-keys-checker/pull/1 97 | 98 | **Full Changelog**: https://github.com/msamgan/laravel-env-keys-checker/compare/v1.0.0...v1.1.0 99 | 100 | ## v1.0.0 - 2024-10-06 101 | 102 | Stable. 103 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | Thank you for your interest in contributing to **msamgan/laravel-env-keys-checker**! We welcome contributions of all kinds—whether you're fixing bugs, adding features, improving documentation, or just reporting an issue. This guide will help you get started and ensure that your contributions are aligned with our goals and standards. 4 | 5 | ## How to Contribute 6 | 7 | ### 1. Reporting Issues 8 | If you've found a bug, have a feature request, or want to report a security vulnerability, we encourage you to submit an issue: 9 | 10 | - **Bug Reports**: Provide a detailed description of the problem, including the steps to reproduce, the expected behavior, and screenshots/logs if possible. 11 | - **Feature Requests**: Explain the use case for the feature, and how it will benefit users. If possible, include ideas for implementation. 12 | - **Security Issues**: For sensitive security vulnerabilities, please email us directly at `[mail@msamgan.com]`. 13 | 14 | ### 2. Fork the Repository 15 | To contribute code, follow these steps: 16 | 17 | 1. **Fork the repository** by clicking the "Fork" button in the top-right corner of the GitHub page. 18 | 2. **Clone the fork** to your local machine: 19 | ```bash 20 | git clone https://github.com/your-username/laravel-env-keys-checker.git 21 | cd laravel-env-keys-checker 22 | ``` 23 | 3. **Create a new branch** for your changes: 24 | ```bash 25 | git checkout -b my-branch-name 26 | ``` 27 | 28 | ### 3. Making Changes 29 | - Follow the [Coding Standards](#coding-standards) when writing code. 30 | - Write clear and concise **commit messages**. 31 | - Include **unit tests** for any new features or bug fixes if you can. 32 | - **Run tests locally** before pushing changes: 33 | ```bash 34 | ./vendor/bin/pest 35 | ``` 36 | 37 | ### 4. Submitting a Pull Request 38 | Once your changes are ready, submit a pull request (PR): 39 | 40 | 1. Push your branch to your fork: 41 | ```bash 42 | git push origin my-branch-name 43 | ``` 44 | 2. Open a pull request to the `next release version` branch of the original repository. 45 | 3. Provide a **clear description** of your changes, explaining why the change is needed and what it does. 46 | 4. The project maintainers will review your PR, suggest any necessary changes, and merge it when it's ready. 47 | 48 | ### 5. Code Review 49 | Please be prepared to make changes based on the review. We want to ensure that all contributions meet the project's standards for quality and maintainability. 50 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) msamgan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Check if all the keys are available across all the .env files. 2 | 3 | ![image](https://github.com/user-attachments/assets/8f80ef4a-a777-46ed-bc49-e70e3c1bec60) 4 | 5 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/msamgan/laravel-env-keys-checker.svg?style=flat-square)](https://packagist.org/packages/msamgan/laravel-env-keys-checker) 6 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/msamgan/laravel-env-keys-checker/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/msamgan/laravel-env-keys-checker/actions?query=workflow%3Arun-tests+branch%3Amain) 7 | [![Total Downloads](https://img.shields.io/packagist/dt/msamgan/laravel-env-keys-checker.svg?style=flat-square)](https://packagist.org/packages/msamgan/laravel-env-keys-checker) 8 | 9 | # Documentation 10 | 11 | You can find a the complete documentation [here](https://msamgan.com/docs/env-checker) 12 | 13 | ## Changelog 14 | 15 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 16 | 17 | ## Contributing 18 | 19 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 20 | 21 | ## Security Vulnerabilities 22 | 23 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 24 | 25 | ## Credits 26 | 27 | - [msamgan](https://github.com/msamgan) 28 | - [All Contributors](../../contributors) 29 | 30 | ## License 31 | 32 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "msamgan/laravel-env-keys-checker", 3 | "description": "check if all the keys are available in all the .env files.", 4 | "keywords": [ 5 | "msamgan", 6 | "laravel", 7 | "laravel-env-keys-checker" 8 | ], 9 | "homepage": "https://msamgan.com/docs/env-checker", 10 | "support": { 11 | "email": "mohdsamgankhan@gmail.com", 12 | "url": "https://msamgan.com/contact" 13 | }, 14 | "source": { 15 | "type": "git", 16 | "url": "https://github.com/msamgan/laravel-env-keys-checker", 17 | "reference": "v1.7.0" 18 | }, 19 | "license": "MIT", 20 | "authors": [ 21 | { 22 | "name": "msamgan", 23 | "email": "mohdsamgankhan@gmail.com", 24 | "role": "Developer" 25 | } 26 | ], 27 | "require": { 28 | "php": "^8.3 || ^8.2", 29 | "illuminate/contracts": "^10.0||^11.0||^12.0", 30 | "spatie/laravel-package-tools": "^1.16" 31 | }, 32 | "require-dev": { 33 | "tightenco/duster": "^3.2", 34 | "driftingly/rector-laravel": "^1.2", 35 | "larastan/larastan": "^2.9", 36 | "laravel/pint": "^1.14", 37 | "nunomaduro/collision": "^8.1.1||^7.10.0", 38 | "orchestra/testbench": "^9.0.0||^8.22.0", 39 | "pestphp/pest": "^2.34", 40 | "pestphp/pest-plugin-arch": "^2.7", 41 | "pestphp/pest-plugin-laravel": "^2.3", 42 | "phpstan/extension-installer": "^1.3", 43 | "phpstan/phpstan-deprecation-rules": "^1.1", 44 | "phpstan/phpstan-phpunit": "^1.3", 45 | "rector/rector": "^1.2" 46 | }, 47 | "autoload": { 48 | "psr-4": { 49 | "Msamgan\\LaravelEnvKeysChecker\\": "src/" 50 | } 51 | }, 52 | "autoload-dev": { 53 | "psr-4": { 54 | "Msamgan\\LaravelEnvKeysChecker\\Tests\\": "tests/" 55 | } 56 | }, 57 | "scripts": { 58 | "post-autoload-dump": "@composer run prepare", 59 | "clear": "@php vendor/bin/testbench package:purge-laravel-env-keys-checker --ansi", 60 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 61 | "build": [ 62 | "@composer run prepare", 63 | "@php vendor/bin/testbench workbench:build --ansi" 64 | ], 65 | "start": [ 66 | "Composer\\Config::disableProcessTimeout", 67 | "@composer run build", 68 | "@php vendor/bin/testbench serve" 69 | ], 70 | "analyse": "vendor/bin/phpstan analyse", 71 | "test": "vendor/bin/pest", 72 | "test-coverage": "vendor/bin/pest --coverage", 73 | "format": "./vendor/bin/rector process && vendor/bin/pint && ./vendor/bin/duster fix" 74 | }, 75 | "config": { 76 | "sort-packages": true, 77 | "allow-plugins": { 78 | "pestphp/pest-plugin": true, 79 | "phpstan/extension-installer": true 80 | } 81 | }, 82 | "extra": { 83 | "laravel": { 84 | "providers": [ 85 | "Msamgan\\LaravelEnvKeysChecker\\LaravelEnvKeysCheckerServiceProvider" 86 | ], 87 | "aliases": { 88 | "LaravelEnvKeysChecker": "Msamgan\\LaravelEnvKeysChecker\\Facades\\LaravelEnvKeysChecker" 89 | } 90 | } 91 | }, 92 | "minimum-stability": "dev", 93 | "prefer-stable": true 94 | } 95 | -------------------------------------------------------------------------------- /config/env-keys-checker.php: -------------------------------------------------------------------------------- 1 | explode(',', (string) env('KEYS_CHECKER_IGNORE_FILES', '')), 8 | 9 | // List of all the env keys to ignore while checking the env keys 10 | 'ignore_keys' => explode(',', (string) env('KEYS_CHECKER_IGNORE_KEYS', '')), 11 | 12 | // strategy to add the missing keys to the .env file 13 | // ask: will ask the user to add the missing keys 14 | // auto: will add the missing keys automatically 15 | // none: will not add the missing keys 16 | 'auto_add' => env('KEYS_CHECKER_AUTO_ADD', 'ask'), 17 | 18 | // List of all the .env.* files to be checked if they 19 | // are present in the .gitignore file 20 | 'gitignore_files' => explode(',', (string) env('KEYS_CHECKER_GITIGNORE_FILES', '.env')), 21 | 22 | // Master .env file to be used for syncing the keys 23 | 'master_env' => env('MASTER_ENV', '.env'), 24 | ]; 25 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "notPath": [ 4 | "tests/TestCase.php" 5 | ], 6 | "rules": { 7 | "array_push": true, 8 | "backtick_to_shell_exec": true, 9 | "date_time_immutable": true, 10 | "declare_strict_types": true, 11 | "lowercase_keywords": true, 12 | "lowercase_static_reference": true, 13 | "final_class": true, 14 | "final_internal_class": true, 15 | "final_public_method_for_abstract_class": true, 16 | "fully_qualified_strict_types": true, 17 | "global_namespace_import": { 18 | "import_classes": true, 19 | "import_constants": true, 20 | "import_functions": true 21 | }, 22 | "mb_str_functions": true, 23 | "modernize_types_casting": true, 24 | "new_with_parentheses": false, 25 | "no_superfluous_elseif": true, 26 | "no_useless_else": true, 27 | "no_multiple_statements_per_line": true, 28 | "ordered_class_elements": { 29 | "order": [ 30 | "use_trait", 31 | "case", 32 | "constant", 33 | "constant_public", 34 | "constant_protected", 35 | "constant_private", 36 | "property_public", 37 | "property_protected", 38 | "property_private", 39 | "construct", 40 | "destruct", 41 | "magic", 42 | "phpunit", 43 | "method_abstract", 44 | "method_public_static", 45 | "method_public", 46 | "method_protected_static", 47 | "method_protected", 48 | "method_private_static", 49 | "method_private" 50 | ], 51 | "sort_algorithm": "none" 52 | }, 53 | "ordered_interfaces": true, 54 | "ordered_traits": true, 55 | "protected_to_private": true, 56 | "self_accessor": true, 57 | "self_static_accessor": true, 58 | "strict_comparison": true, 59 | "visibility_required": true, 60 | "concat_space": { 61 | "spacing": "one" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 11 | __DIR__ . '/src', 12 | __DIR__ . '/config', 13 | __DIR__ . '/tests', 14 | ])->withPhpSets(php83: true) 15 | ->withPhpVersion(PhpVersion::PHP_83) 16 | ->withPreparedSets( 17 | deadCode: true, 18 | codeQuality: true, 19 | typeDeclarations: true, 20 | privatization: true, 21 | instanceOf: true, 22 | earlyReturn: true, 23 | carbon: true, 24 | )->withSets([ 25 | LaravelSetList::LARAVEL_110, 26 | LaravelSetList::LARAVEL_CODE_QUALITY, 27 | LaravelSetList::LARAVEL_IF_HELPERS, 28 | LaravelSetList::LARAVEL_ARRAY_STR_FUNCTION_TO_STATIC_CALL, 29 | LaravelSetList::LARAVEL_FACADE_ALIASES_TO_FULL_NAMES, 30 | LaravelSetList::LARAVEL_ELOQUENT_MAGIC_METHOD_TO_QUERY_BUILDER, 31 | LaravelSetList::LARAVEL_CONTAINER_STRING_TO_FULLY_QUALIFIED_NAME, 32 | LaravelSetList::LARAVEL_ARRAYACCESS_TO_METHOD_CALL, 33 | LaravelSetList::LARAVEL_COLLECTION, 34 | ])->withImportNames(importDocBlockNames: false); 35 | -------------------------------------------------------------------------------- /src/Actions/AddKeys.php: -------------------------------------------------------------------------------- 1 | each(function (array $missingKey): void { 14 | $filePath = base_path($missingKey['envFile']); 15 | $envContent = file($filePath); 16 | 17 | $lineDiff = count($envContent) - $missingKey['line']; 18 | if ($lineDiff < 0) { 19 | $envContent = $this->appendEmptyLines(file: $filePath, numberOfLines: abs($lineDiff)); 20 | } 21 | 22 | array_splice( 23 | $envContent, $missingKey['line'] - 1, 0, $missingKey['key'] . '=""' . PHP_EOL 24 | ); 25 | 26 | if ($missingKey['is_next_line_empty']) { 27 | array_splice($envContent, $missingKey['line'], 0, PHP_EOL); 28 | } 29 | 30 | file_put_contents($filePath, $envContent); 31 | }); 32 | } 33 | 34 | private function appendEmptyLines(string $file, int $numberOfLines = 1): array 35 | { 36 | $envContent = file($file); 37 | $lastLine = count($envContent); 38 | 39 | for ($i = 0; $i < $numberOfLines; $i++) { 40 | array_splice($envContent, $lastLine, 0, PHP_EOL); 41 | } 42 | 43 | return $envContent; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Actions/CheckKeys.php: -------------------------------------------------------------------------------- 1 | each(function ($envFile) use ($keyData, $missingKeys): void { 14 | $envContent = file($envFile); 15 | $keyExists = false; 16 | 17 | foreach ($envContent as $line) { 18 | if (str_starts_with($line, (string) $keyData['key'])) { 19 | $keyExists = true; 20 | break; 21 | } 22 | } 23 | 24 | if (! $keyExists) { 25 | $missingKeys->push([ 26 | 'line' => $keyData['line'], 27 | 'key' => $keyData['key'], 28 | 'is_next_line_empty' => $keyData['is_next_line_empty'], 29 | 'envFile' => basename($envFile), 30 | ]); 31 | } 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Actions/FilterFiles.php: -------------------------------------------------------------------------------- 1 | reject(callback: fn ($file): bool => in_array(needle: basename((string) $file), haystack: $ignoredFiles)) 13 | ->reject(callback: fn ($file): bool => str_ends_with(haystack: basename((string) $file), needle: '.encrypted')) 14 | ->toArray(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Actions/GetKeys.php: -------------------------------------------------------------------------------- 1 | map(function ($file) use ($ignoredKeys, $withComments) { 21 | $collection = collect(file($file))->map(function ($line, $index) use ($file): array { 22 | [$key] = explode('=', $line); 23 | 24 | return [ 25 | 'key' => $key, 26 | 'line' => $index + 1, 27 | 'is_next_line_empty' => isset(file($file)[$index + 1]) && file($file)[$index + 1] === "\n", 28 | ]; 29 | }); 30 | 31 | if (! $withComments) { 32 | $collection = $collection->filter(fn ($item): bool => $item['key'] !== "\n" && ! str_starts_with($item['key'], '#')); 33 | } 34 | 35 | return $collection->reject(fn ($keyData): bool => in_array($keyData['key'], $ignoredKeys)); 36 | }) 37 | ->flatten(1) 38 | ->unique('key'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Commands/EnvInGitIgnoreCommand.php: -------------------------------------------------------------------------------- 1 | showFailureInfo(message: '.gitignore file not found.'); 24 | 25 | return self::FAILURE; 26 | } 27 | 28 | $gitIgnoreContent = array_map(callback: 'trim', array: file($gitIgnoreFile)); 29 | 30 | $filesToCheck = config(key: 'env-keys-checker.gitignore_files', default: ['.env']); 31 | 32 | $missingFiles = collect(); 33 | collect(value: $filesToCheck)->each(callback: function ($file) use ($gitIgnoreContent, $missingFiles): void { 34 | if (! in_array(needle: $file, haystack: $gitIgnoreContent)) { 35 | $missingFiles->push(values: $file); 36 | } 37 | }); 38 | 39 | if ($missingFiles->isEmpty()) { 40 | $this->showSuccessInfo(message: 'All files are present in .gitignore file.'); 41 | 42 | return self::SUCCESS; 43 | } 44 | 45 | $this->showFailureInfo(message: $missingFiles->implode(value: ', ') . ' file(s) not found in .gitignore file.'); 46 | 47 | return self::FAILURE; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Commands/EnvKeysSyncCommand.php: -------------------------------------------------------------------------------- 1 | call('env:keys-check', [ 22 | '--auto-add' => 'none', 23 | '--no-progress' => true, 24 | '--no-display' => true, 25 | ]); 26 | 27 | if ($allKeysCheck === self::FAILURE) { 28 | $this->showFailureInfo(message: 'keys mismatch found. Syncing keys is not possible. Please fix the keys mismatch first.'); 29 | $this->showFailureInfo(message: 'Run `php artisan env:keys-check --auto-add=auto` to add missing keys automatically.'); 30 | 31 | return self::FAILURE; 32 | } 33 | 34 | $envFiles = $this->getEnvs(); 35 | $ignoredFiles = $this->getFilesToIgnore(); 36 | 37 | if ($envFiles === []) { 38 | $this->showFailureInfo(message: 'No .env files found.'); 39 | 40 | return self::FAILURE; 41 | } 42 | 43 | $envFiles = $filterFiles->handle(envFiles: $envFiles, ignoredFiles: $ignoredFiles); 44 | 45 | if ($envFiles === []) { 46 | $this->showFailureInfo(message: 'No .env files found.'); 47 | 48 | return self::FAILURE; 49 | } 50 | 51 | $envFiles = collect(value: $envFiles)->filter(callback: fn ($file): bool => basename(path: (string) $file) !== $this->getMasterEnv()); 52 | 53 | $envFiles->each(callback: function ($envFile): void { 54 | $totalKeysFromMaster = count(value: file(filename: $this->getMasterEnv())); 55 | for ($line = 1; $line <= $totalKeysFromMaster; $line++) { 56 | $keyMaster = $this->getKeyFromFileOnLine(file: $this->getMasterEnv(), line: $line); 57 | $keyEnvFile = $this->getKeyFromFileOnLine(file: $envFile, line: $line); 58 | 59 | if ($keyMaster === $keyEnvFile) { 60 | continue; 61 | } 62 | 63 | $keyMasterKey = explode(separator: '=', string: $keyMaster)[0]; 64 | $keyEnvFileKey = explode(separator: '=', string: $keyEnvFile)[0]; 65 | 66 | if ($keyMasterKey === $keyEnvFileKey) { 67 | continue; 68 | } 69 | 70 | if ($this->checkIfComment(line: $keyMaster)) { 71 | $this->pushKeyOnLine(file: $envFile, line: $line, key: $keyMaster); 72 | 73 | continue; 74 | } 75 | 76 | if ($this->checkIfEmptyLine(line: $keyMaster)) { 77 | $this->pushKeyOnLine(file: $envFile, line: $line, key: $keyMaster); 78 | 79 | continue; 80 | } 81 | 82 | $this->moveKeyToLine(file: $envFile, key: $keyMasterKey, toLine: $line); 83 | } 84 | 85 | $this->removeAllLinesAfter(lineNumber: $totalKeysFromMaster, file: $envFile); 86 | }); 87 | 88 | $this->showSuccessInfo(message: 'Keys synced successfully.'); 89 | 90 | return self::SUCCESS; 91 | } 92 | 93 | private function getMasterEnv(): string 94 | { 95 | return (string) config(key: 'env-keys-checker.master_env', default: '.env'); 96 | } 97 | 98 | private function getKeyFromFileOnLine(string $file, int $line): string 99 | { 100 | return file(filename: $file)[$line - 1] ?? ''; 101 | } 102 | 103 | private function checkIfComment(string $line): bool 104 | { 105 | return str_starts_with($line, '#'); 106 | } 107 | 108 | private function pushKeyOnLine(string $file, int $line, string $key): void 109 | { 110 | $lines = file(filename: $file); 111 | array_splice(array: $lines, offset: $line - 1, length: 0, replacement: $key); 112 | 113 | file_put_contents(filename: $file, data: implode(separator: '', array: $lines)); 114 | } 115 | 116 | private function checkIfEmptyLine(string $line): bool 117 | { 118 | return $line === "\n"; 119 | } 120 | 121 | private function moveKeyToLine(string $file, string $key, int $toLine): void 122 | { 123 | $lines = file(filename: $file); 124 | $keyLine = array_filter(array: $lines, callback: fn ($line): bool => str_starts_with($line, $key)); 125 | 126 | if ($keyLine === []) { 127 | return; 128 | } 129 | 130 | $keyLine = array_keys(array: $keyLine)[0]; 131 | $keyData = $lines[$keyLine]; 132 | 133 | unset($lines[$keyLine]); 134 | 135 | array_splice(array: $lines, offset: $toLine - 1, length: 0, replacement: $keyData); 136 | 137 | file_put_contents(filename: $file, data: implode(separator: '', array: $lines)); 138 | } 139 | 140 | private function removeAllLinesAfter(int $lineNumber, string $file): void 141 | { 142 | $lines = file(filename: $file); 143 | $lines = array_slice(array: $lines, offset: 0, length: $lineNumber); 144 | 145 | file_put_contents(filename: $file, data: implode(separator: '', array: $lines)); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Commands/KeysCheckerCommand.php: -------------------------------------------------------------------------------- 1 | getEnvs(); 30 | 31 | $ignoredFiles = $this->getFilesToIgnore(); 32 | $autoAddOption = $this->option(key: 'auto-add'); 33 | $autoAddAvailableOptions = ['ask', 'auto', 'none']; 34 | 35 | $autoAddStrategy = $autoAddOption ?: config(key: 'env-keys-checker.auto_add', default: 'ask'); 36 | 37 | if (! in_array(needle: $autoAddStrategy, haystack: $autoAddAvailableOptions)) { 38 | if (! $this->option(key: 'no-display')) { 39 | $this->showFailureInfo(message: 'Invalid auto add option provided. Available options are: ' . implode(', ', $autoAddAvailableOptions)); 40 | } 41 | 42 | return self::FAILURE; 43 | } 44 | 45 | if ($envFiles === []) { 46 | if (! $this->option(key: 'no-display')) { 47 | $this->showFailureInfo(message: 'No .env files found.'); 48 | } 49 | 50 | return self::FAILURE; 51 | } 52 | 53 | $envFiles = $filterFiles->handle(envFiles: $envFiles, ignoredFiles: $ignoredFiles); 54 | 55 | if ($envFiles === []) { 56 | if (! $this->option(key: 'no-display')) { 57 | $this->showFailureInfo(message: 'No .env files found.'); 58 | } 59 | 60 | return self::FAILURE; 61 | } 62 | 63 | $keys = $getKeys->handle(files: $envFiles); 64 | 65 | $missingKeys = collect(); 66 | 67 | $processKeys = fn ($key) => $checkKeys->handle(keyData: $key, envFiles: $envFiles, missingKeys: $missingKeys); 68 | 69 | if ($this->option(key: 'no-progress')) { 70 | $keys->each(callback: $processKeys); 71 | } else { 72 | progress( 73 | label: 'Checking keys...', 74 | steps: $keys, 75 | callback: $processKeys, 76 | hint: 'It won\'t take long.' 77 | ); 78 | } 79 | 80 | if ($missingKeys->isEmpty()) { 81 | if (! $this->option(key: 'no-display')) { 82 | $this->showSuccessInfo(message: 'All keys are present in all .env files.'); 83 | } 84 | 85 | return self::SUCCESS; 86 | } 87 | 88 | if (! $this->option(key: 'no-display')) { 89 | $this->showMissingKeysTable(missingKeys: $missingKeys); 90 | } 91 | 92 | if ($autoAddStrategy === 'ask') { 93 | $confirmation = confirm(label: 'Do you want to add the missing keys to the .env files?'); 94 | 95 | if ($confirmation) { 96 | $addKeys->handle(missingKeys: $missingKeys); 97 | 98 | if (! $this->option(key: 'no-display')) { 99 | $this->showSuccessInfo(message: 'All missing keys have been added to the .env files.'); 100 | } 101 | } 102 | 103 | return self::SUCCESS; 104 | } 105 | 106 | if ($autoAddStrategy === 'auto') { 107 | $addKeys->handle(missingKeys: $missingKeys); 108 | 109 | return self::SUCCESS; 110 | } 111 | 112 | return self::FAILURE; 113 | 114 | } 115 | 116 | private function showMissingKeysTable(Collection $missingKeys): void 117 | { 118 | table( 119 | headers: ['Line', 'Key', 'Is missing in'], 120 | rows: $missingKeys->map(callback: fn ($missingKey): array => [ 121 | $missingKey['line'], 122 | $missingKey['key'], 123 | $missingKey['envFile'], 124 | ])->toArray() 125 | ); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Concerns/HelperFunctions.php: -------------------------------------------------------------------------------- 1 | ' . $message); 15 | } 16 | 17 | private function showFailureInfo(string $message): void 18 | { 19 | error(message: ' !! ' . $message); 20 | } 21 | 22 | private function getEnvs(): array 23 | { 24 | return glob(pattern: base_path(path: '.env*')); 25 | } 26 | 27 | private function getFilesToIgnore(): array 28 | { 29 | return (array) config(key: 'env-keys-checker.ignore_files', default: []); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/LaravelEnvKeysCheckerServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('laravel-env-keys-checker') 26 | ->hasConfigFile() 27 | ->hasCommand(KeysCheckerCommand::class) 28 | ->hasCommand(EnvInGitIgnoreCommand::class) 29 | ->hasCommand(EnvKeysSyncCommand::class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "disabled": ["QualifiedNamesOnlyForClassName", "RemoveLeadingSlashNamespaces"] 3 | } 4 | --------------------------------------------------------------------------------