Skip to content

Commit 3c1e8e7

Browse files
authored
Env key sync (#15)
1 parent 234656f commit 3c1e8e7

File tree

6 files changed

+217
-27
lines changed

6 files changed

+217
-27
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ to the .env.example file or the other way around.
3737
- Add the missing keys to the .env files automatically (configurable) considering the line numbers and empty lines.
3838
- Check if the .env and other provided files are present in .gitignore, so that they are not committed to git by
3939
mistake.
40+
- Sync all the available keys by line across all the .env files. Referencing the master .env file. (Configurable, default is .env)
4041

4142
## Installation
4243

@@ -73,12 +74,20 @@ The default value is ``ask``.
7374

7475
``--no-progress``: This option will disable the progress bar.
7576

77+
``--no-display``: This option will disable all output.
78+
7679
### To check if the .env and other provided files are present in .gitignore.
7780

7881
```bash
7982
php artisan env:in-git-ignore
8083
```
8184

85+
### To sync all the available keys by line across all the .env files.
86+
87+
```bash
88+
php artisan env:sync-keys
89+
```
90+
8291
## In Test
8392

8493
You can also use this package in your test cases to make sure the required feature is working as expected.

config/env-keys-checker.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,7 @@
1616
// List of all the .env.* files to be checked if they
1717
// are present in the .gitignore file
1818
'gitignore_files' => explode(',', env('KEYS_CHECKER_GITIGNORE_FILES', '.env')),
19+
20+
// Master .env file to be used for syncing the keys
21+
'master_env' => env('MASTER_ENV', '.env'),
1922
];

src/Actions/GetKeys.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
class GetKeys
88
{
9-
public function handle(array|string $files): Collection
9+
public function handle(array|string $files, ?bool $withComments = false): Collection
1010
{
1111
$ignoredKeys = config('env-keys-checker.ignore_keys', []);
1212

@@ -15,18 +15,24 @@ public function handle(array|string $files): Collection
1515
: collect([$files]);
1616

1717
return $files
18-
->map(function ($file) use ($ignoredKeys) {
19-
return collect(file($file))->map(function ($line, $index) use ($file) {
18+
->map(function ($file) use ($ignoredKeys, $withComments) {
19+
$collection = collect(file($file))->map(function ($line, $index) use ($file) {
2020
[$key] = explode('=', $line);
2121

2222
return [
2323
'key' => $key,
2424
'line' => $index + 1,
2525
'is_next_line_empty' => isset(file($file)[$index + 1]) && file($file)[$index + 1] === "\n",
2626
];
27-
})->filter(function ($item) {
28-
return $item['key'] !== "\n" && ! str_starts_with($item['key'], '#');
29-
})->filter(function ($keyData) use ($ignoredKeys) {
27+
});
28+
29+
if (! $withComments) {
30+
$collection = $collection->filter(function ($item) {
31+
return $item['key'] !== "\n" && ! str_starts_with($item['key'], '#');
32+
});
33+
}
34+
35+
return $collection->filter(function ($keyData) use ($ignoredKeys) {
3036
return ! in_array($keyData['key'], $ignoredKeys);
3137
});
3238
})
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<?php
2+
3+
namespace Msamgan\LaravelEnvKeysChecker\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use Msamgan\LaravelEnvKeysChecker\Actions\GetKeys;
7+
use Msamgan\LaravelEnvKeysChecker\Concerns\HelperFunctions;
8+
9+
class EnvKeysSyncCommand extends Command
10+
{
11+
use HelperFunctions;
12+
13+
public $signature = 'env:sync-keys';
14+
15+
public $description = 'Sync keys from master .env file to other .env files.';
16+
17+
public function handle(GetKeys $getKeys): int
18+
{
19+
$allKeysCheck = $this->call('env:keys-check', [
20+
'--auto-add' => 'none',
21+
'--no-progress' => true,
22+
'--no-display' => true,
23+
]);
24+
25+
if ($allKeysCheck === self::FAILURE) {
26+
$this->showFailureInfo('keys mismatch found. Syncing keys is not possible. Please fix the keys mismatch first.');
27+
$this->showFailureInfo('Run `php artisan env:keys-check --auto-add=auto` to add missing keys automatically.');
28+
29+
return self::FAILURE;
30+
}
31+
32+
$envFiles = glob(base_path('.env*'));
33+
$ignoredFiles = config('env-keys-checker.ignore_files', []);
34+
35+
if (empty($envFiles)) {
36+
$this->showFailureInfo(
37+
message: 'No .env files found.'
38+
);
39+
40+
return self::FAILURE;
41+
}
42+
43+
$envFiles = collect($envFiles)->filter(function ($file) use ($ignoredFiles) {
44+
return ! in_array(basename($file), $ignoredFiles);
45+
})->toArray();
46+
47+
if (empty($envFiles)) {
48+
$this->showFailureInfo(
49+
message: 'No .env files found.'
50+
);
51+
52+
return self::FAILURE;
53+
}
54+
55+
$envFiles = collect($envFiles)->filter(function ($file) {
56+
return basename($file) !== config('env-keys-checker.master_env', '.env');
57+
});
58+
59+
$envFiles->each(function ($envFile) {
60+
$totalKeysFromMaster = count(file(config('env-keys-checker.master_env', '.env')));
61+
for ($line = 1; $line <= $totalKeysFromMaster; $line++) {
62+
$keyMaster = $this->getKeyFromFileOnLine(config('env-keys-checker.master_env', '.env'), $line);
63+
$keyEnvFile = $this->getKeyFromFileOnLine($envFile, $line);
64+
65+
if ($keyMaster === $keyEnvFile) {
66+
continue;
67+
}
68+
69+
$keyMasterKey = explode('=', $keyMaster)[0];
70+
$keyEnvFileKey = explode('=', $keyEnvFile)[0];
71+
72+
if ($keyMasterKey === $keyEnvFileKey) {
73+
continue;
74+
}
75+
76+
if ($this->checkIfComment($keyMaster)) {
77+
$this->pushKeyOnLine($envFile, $line, $keyMaster);
78+
79+
continue;
80+
}
81+
82+
if ($this->checkIfEmptyLine($keyMaster)) {
83+
$this->pushKeyOnLine($envFile, $line, $keyMaster);
84+
85+
continue;
86+
}
87+
88+
$this->moveKeyToLine($envFile, $keyMasterKey, $line);
89+
}
90+
91+
$this->removeAllLinesAfter($totalKeysFromMaster, $envFile);
92+
});
93+
94+
$this->showSuccessInfo(
95+
message: 'Keys synced successfully.'
96+
);
97+
98+
return self::SUCCESS;
99+
}
100+
101+
private function getKeyFromFileOnLine(string $file, int $line): string
102+
{
103+
return file($file)[$line - 1];
104+
}
105+
106+
private function checkIfComment(string $line): bool
107+
{
108+
return str_starts_with($line, '#');
109+
}
110+
111+
private function pushKeyOnLine($file, $line, $key): void
112+
{
113+
$lines = file($file);
114+
array_splice($lines, $line - 1, 0, $key);
115+
116+
file_put_contents($file, implode('', $lines));
117+
}
118+
119+
private function checkIfEmptyLine(string $line): bool
120+
{
121+
return $line === "\n";
122+
}
123+
124+
private function moveKeyToLine(string $file, string $key, int $toLine): void
125+
{
126+
$lines = file($file);
127+
$keyLine = array_filter($lines, function ($line) use ($key) {
128+
return str_starts_with($line, $key);
129+
});
130+
131+
if (empty($keyLine)) {
132+
return;
133+
}
134+
135+
$keyLine = array_keys($keyLine)[0];
136+
$keyData = $lines[$keyLine];
137+
138+
unset($lines[$keyLine]);
139+
140+
array_splice($lines, $toLine - 1, 0, $keyData);
141+
142+
file_put_contents($file, implode('', $lines));
143+
}
144+
145+
private function removeAllLinesAfter(int $lineNumber, string $file): void
146+
{
147+
$lines = file($file);
148+
$lines = array_slice($lines, 0, $lineNumber);
149+
150+
file_put_contents($file, implode('', $lines));
151+
}
152+
}

src/Commands/KeysCheckerCommand.php

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,19 @@
44

55
use Illuminate\Console\Command;
66
use Illuminate\Support\Collection;
7-
8-
use function Laravel\Prompts\confirm;
9-
use function Laravel\Prompts\progress;
10-
use function Laravel\Prompts\table;
11-
127
use Msamgan\LaravelEnvKeysChecker\Actions\AddKeys;
138
use Msamgan\LaravelEnvKeysChecker\Actions\CheckKeys;
149
use Msamgan\LaravelEnvKeysChecker\Actions\GetKeys;
1510
use Msamgan\LaravelEnvKeysChecker\Concerns\HelperFunctions;
11+
use function Laravel\Prompts\confirm;
12+
use function Laravel\Prompts\progress;
13+
use function Laravel\Prompts\table;
1614

1715
class KeysCheckerCommand extends Command
1816
{
1917
use HelperFunctions;
2018

21-
public $signature = 'env:keys-check {--auto-add=} {--no-progress}';
19+
public $signature = 'env:keys-check {--auto-add=} {--no-progress} {--no-display}';
2220

2321
public $description = 'Check if all keys in .env file are present across all .env files. Like .env, .env.example, .env.testing, etc.';
2422

@@ -32,31 +30,45 @@ public function handle(GetKeys $getKeys, CheckKeys $checkKeys, AddKeys $addKeys)
3230

3331
$autoAddStrategy = $autoAddOption ?: config('env-keys-checker.auto_add', 'ask');
3432

35-
if (! in_array($autoAddStrategy, $autoAddAvailableOptions)) {
36-
$this->showFailureInfo(
37-
message: 'Invalid auto add option provided. Available options are: ' . implode(', ', $autoAddAvailableOptions)
38-
);
33+
if (!in_array($autoAddStrategy, $autoAddAvailableOptions)) {
34+
if (!$this->option('no-display')) {
35+
$this->showFailureInfo(
36+
message: 'Invalid auto add option provided. Available options are: ' . implode(', ', $autoAddAvailableOptions)
37+
);
38+
}
3939

4040
return self::FAILURE;
4141
}
4242

4343
if (empty($envFiles)) {
44-
$this->showFailureInfo(
45-
message: 'No .env files found.'
46-
);
44+
if (!$this->option('no-display')) {
45+
$this->showFailureInfo(
46+
message: 'No .env files found.'
47+
);
48+
}
4749

4850
return self::FAILURE;
4951
}
5052

5153
$envFiles = collect($envFiles)->filter(function ($file) use ($ignoredFiles) {
52-
return ! in_array(basename($file), $ignoredFiles);
54+
return !in_array(basename($file), $ignoredFiles);
5355
})->toArray();
5456

57+
if (empty($envFiles)) {
58+
if (!$this->option('no-display')) {
59+
$this->showFailureInfo(
60+
message: 'No .env files found.'
61+
);
62+
}
63+
64+
return self::FAILURE;
65+
}
66+
5567
$keys = $getKeys->handle(files: $envFiles);
5668

5769
$missingKeys = collect();
5870

59-
$processKeys = fn ($key) => $checkKeys->handle(keyData: $key, envFiles: $envFiles, missingKeys: $missingKeys);
71+
$processKeys = fn($key) => $checkKeys->handle(keyData: $key, envFiles: $envFiles, missingKeys: $missingKeys);
6072

6173
if ($this->option('no-progress')) {
6274
$keys->each($processKeys);
@@ -70,22 +82,28 @@ public function handle(GetKeys $getKeys, CheckKeys $checkKeys, AddKeys $addKeys)
7082
}
7183

7284
if ($missingKeys->isEmpty()) {
73-
$this->showSuccessInfo(
74-
message: 'All keys are present in all .env files.'
75-
);
85+
if (!$this->option('no-display')) {
86+
$this->showSuccessInfo(
87+
message: 'All keys are present in all .env files.'
88+
);
89+
}
7690

7791
return self::SUCCESS;
7892
}
7993

80-
$this->showMissingKeysTable($missingKeys);
94+
if (!$this->option('no-display')) {
95+
$this->showMissingKeysTable($missingKeys);
96+
}
8197

8298
if ($autoAddStrategy === 'ask') {
8399
$confirmation = confirm('Do you want to add the missing keys to the .env files?');
84100

85101
if ($confirmation) {
86102
$addKeys->handle(missingKeys: $missingKeys);
87103

88-
$this->showSuccessInfo('All missing keys have been added to the .env files.');
104+
if (!$this->option('no-display')) {
105+
$this->showSuccessInfo('All missing keys have been added to the .env files.');
106+
}
89107
}
90108

91109
return self::SUCCESS;

src/LaravelEnvKeysCheckerServiceProvider.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Msamgan\LaravelEnvKeysChecker;
44

55
use Msamgan\LaravelEnvKeysChecker\Commands\EnvInGitIgnoreCommand;
6+
use Msamgan\LaravelEnvKeysChecker\Commands\EnvKeysSyncCommand;
67
use Msamgan\LaravelEnvKeysChecker\Commands\KeysCheckerCommand;
78
use Spatie\LaravelPackageTools\Package;
89
use Spatie\LaravelPackageTools\PackageServiceProvider;
@@ -20,6 +21,7 @@ public function configurePackage(Package $package): void
2021
->name('laravel-env-keys-checker')
2122
->hasConfigFile()
2223
->hasCommand(KeysCheckerCommand::class)
23-
->hasCommand(EnvInGitIgnoreCommand::class);
24+
->hasCommand(EnvInGitIgnoreCommand::class)
25+
->hasCommand(EnvKeysSyncCommand::class);
2426
}
2527
}

0 commit comments

Comments
 (0)