@@ -16,7 +16,7 @@ class SyncBunny extends Command
1616 *
1717 * @var string
1818 */
19- protected $ signature = 'sync:bunny {--templates} {--release} {--github-releases} {--nightly} ' ;
19+ protected $ signature = 'sync:bunny {--templates} {--release} {--github-releases} {--github-versions} {-- nightly} ' ;
2020
2121 /**
2222 * The console command description.
@@ -70,12 +70,25 @@ private function syncReleasesToGitHubRepo(): bool
7070 // Write releases.json
7171 $ this ->info ('Writing releases.json... ' );
7272 $ releasesPath = "$ tmpDir/json/releases.json " ;
73+ $ releasesDir = dirname ($ releasesPath );
74+
75+ // Ensure directory exists
76+ if (! is_dir ($ releasesDir )) {
77+ $ this ->info ("Creating directory: $ releasesDir " );
78+ if (! mkdir ($ releasesDir , 0755 , true )) {
79+ $ this ->error ("Failed to create directory: $ releasesDir " );
80+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
81+
82+ return false ;
83+ }
84+ }
85+
7386 $ jsonContent = json_encode ($ releases , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
7487 $ bytesWritten = file_put_contents ($ releasesPath , $ jsonContent );
7588
7689 if ($ bytesWritten === false ) {
7790 $ this ->error ("Failed to write releases.json to: $ releasesPath " );
78- $ this ->error ('Possible reasons: directory does not exist, permission denied, or disk full. ' );
91+ $ this ->error ('Possible reasons: permission denied or disk full. ' );
7992 exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
8093
8194 return false ;
@@ -158,6 +171,156 @@ private function syncReleasesToGitHubRepo(): bool
158171 }
159172 }
160173
174+ /**
175+ * Sync versions.json to GitHub repository via PR
176+ */
177+ private function syncVersionsToGitHubRepo (string $ versionsLocation , bool $ nightly = false ): bool
178+ {
179+ $ this ->info ('Syncing versions.json to GitHub repository... ' );
180+ try {
181+ if (! file_exists ($ versionsLocation )) {
182+ $ this ->error ("versions.json not found at: $ versionsLocation " );
183+
184+ return false ;
185+ }
186+
187+ $ file = file_get_contents ($ versionsLocation );
188+ $ json = json_decode ($ file , true );
189+ $ actualVersion = data_get ($ json , 'coolify.v4.version ' );
190+
191+ $ timestamp = time ();
192+ $ tmpDir = sys_get_temp_dir ().'/coolify-cdn-versions- ' .$ timestamp ;
193+ $ branchName = 'update-versions- ' .$ timestamp ;
194+ $ targetPath = $ nightly ? 'json/versions-nightly.json ' : 'json/versions.json ' ;
195+
196+ // Clone the repository
197+ $ this ->info ('Cloning coolify-cdn repository... ' );
198+ exec ('gh repo clone coollabsio/coolify-cdn ' .escapeshellarg ($ tmpDir ).' 2>&1 ' , $ output , $ returnCode );
199+ if ($ returnCode !== 0 ) {
200+ $ this ->error ('Failed to clone repository: ' .implode ("\n" , $ output ));
201+
202+ return false ;
203+ }
204+
205+ // Create feature branch
206+ $ this ->info ('Creating feature branch... ' );
207+ $ output = [];
208+ exec ('cd ' .escapeshellarg ($ tmpDir ).' && git checkout -b ' .escapeshellarg ($ branchName ).' 2>&1 ' , $ output , $ returnCode );
209+ if ($ returnCode !== 0 ) {
210+ $ this ->error ('Failed to create branch: ' .implode ("\n" , $ output ));
211+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
212+
213+ return false ;
214+ }
215+
216+ // Write versions.json
217+ $ this ->info ('Writing versions.json... ' );
218+ $ versionsPath = "$ tmpDir/ $ targetPath " ;
219+ $ versionsDir = dirname ($ versionsPath );
220+
221+ // Ensure directory exists
222+ if (! is_dir ($ versionsDir )) {
223+ $ this ->info ("Creating directory: $ versionsDir " );
224+ if (! mkdir ($ versionsDir , 0755 , true )) {
225+ $ this ->error ("Failed to create directory: $ versionsDir " );
226+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
227+
228+ return false ;
229+ }
230+ }
231+
232+ $ jsonContent = json_encode ($ json , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
233+ $ bytesWritten = file_put_contents ($ versionsPath , $ jsonContent );
234+
235+ if ($ bytesWritten === false ) {
236+ $ this ->error ("Failed to write versions.json to: $ versionsPath " );
237+ $ this ->error ('Possible reasons: permission denied or disk full. ' );
238+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
239+
240+ return false ;
241+ }
242+
243+ // Stage and commit
244+ $ this ->info ('Committing changes... ' );
245+ $ output = [];
246+ exec ('cd ' .escapeshellarg ($ tmpDir ).' && git add ' .escapeshellarg ($ targetPath ).' 2>&1 ' , $ output , $ returnCode );
247+ if ($ returnCode !== 0 ) {
248+ $ this ->error ('Failed to stage changes: ' .implode ("\n" , $ output ));
249+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
250+
251+ return false ;
252+ }
253+
254+ $ this ->info ('Checking for changes... ' );
255+ $ statusOutput = [];
256+ exec ('cd ' .escapeshellarg ($ tmpDir ).' && git status --porcelain ' .escapeshellarg ($ targetPath ).' 2>&1 ' , $ statusOutput , $ returnCode );
257+ if ($ returnCode !== 0 ) {
258+ $ this ->error ('Failed to check repository status: ' .implode ("\n" , $ statusOutput ));
259+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
260+
261+ return false ;
262+ }
263+
264+ if (empty (array_filter ($ statusOutput ))) {
265+ $ this ->info ('versions.json is already up to date. No changes to commit. ' );
266+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
267+
268+ return true ;
269+ }
270+
271+ $ envLabel = $ nightly ? 'NIGHTLY ' : 'PRODUCTION ' ;
272+ $ commitMessage = "Update $ envLabel versions.json to $ actualVersion - " .date ('Y-m-d H:i:s ' );
273+ $ output = [];
274+ exec ('cd ' .escapeshellarg ($ tmpDir ).' && git commit -m ' .escapeshellarg ($ commitMessage ).' 2>&1 ' , $ output , $ returnCode );
275+ if ($ returnCode !== 0 ) {
276+ $ this ->error ('Failed to commit changes: ' .implode ("\n" , $ output ));
277+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
278+
279+ return false ;
280+ }
281+
282+ // Push to remote
283+ $ this ->info ('Pushing branch to remote... ' );
284+ $ output = [];
285+ exec ('cd ' .escapeshellarg ($ tmpDir ).' && git push origin ' .escapeshellarg ($ branchName ).' 2>&1 ' , $ output , $ returnCode );
286+ if ($ returnCode !== 0 ) {
287+ $ this ->error ('Failed to push branch: ' .implode ("\n" , $ output ));
288+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
289+
290+ return false ;
291+ }
292+
293+ // Create pull request
294+ $ this ->info ('Creating pull request... ' );
295+ $ prTitle = "Update $ envLabel versions.json to $ actualVersion - " .date ('Y-m-d H:i:s ' );
296+ $ prBody = "Automated update of $ envLabel versions.json to version $ actualVersion " ;
297+ $ output = [];
298+ $ prCommand = 'gh pr create --repo coollabsio/coolify-cdn --title ' .escapeshellarg ($ prTitle ).' --body ' .escapeshellarg ($ prBody ).' --base main --head ' .escapeshellarg ($ branchName ).' 2>&1 ' ;
299+ exec ($ prCommand , $ output , $ returnCode );
300+
301+ // Clean up
302+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
303+
304+ if ($ returnCode !== 0 ) {
305+ $ this ->error ('Failed to create PR: ' .implode ("\n" , $ output ));
306+
307+ return false ;
308+ }
309+
310+ $ this ->info ('Pull request created successfully! ' );
311+ if (! empty ($ output )) {
312+ $ this ->info ('PR URL: ' .implode ("\n" , $ output ));
313+ }
314+ $ this ->info ("Version synced: $ actualVersion " );
315+
316+ return true ;
317+ } catch (\Throwable $ e ) {
318+ $ this ->error ('Error syncing versions.json: ' .$ e ->getMessage ());
319+
320+ return false ;
321+ }
322+ }
323+
161324 /**
162325 * Execute the console command.
163326 */
@@ -167,6 +330,7 @@ public function handle()
167330 $ only_template = $ this ->option ('templates ' );
168331 $ only_version = $ this ->option ('release ' );
169332 $ only_github_releases = $ this ->option ('github-releases ' );
333+ $ only_github_versions = $ this ->option ('github-versions ' );
170334 $ nightly = $ this ->option ('nightly ' );
171335 $ bunny_cdn = 'https://cdn.coollabs.io ' ;
172336 $ bunny_cdn_path = 'coolify ' ;
@@ -224,7 +388,7 @@ public function handle()
224388 $ install_script_location = "$ parent_dir/other/nightly/ $ install_script " ;
225389 $ versions_location = "$ parent_dir/other/nightly/ $ versions " ;
226390 }
227- if (! $ only_template && ! $ only_version && ! $ only_github_releases ) {
391+ if (! $ only_template && ! $ only_version && ! $ only_github_releases && ! $ only_github_versions ) {
228392 if ($ nightly ) {
229393 $ this ->info ('About to sync files NIGHTLY (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN. ' );
230394 } else {
@@ -249,6 +413,11 @@ public function handle()
249413
250414 return ;
251415 } elseif ($ only_version ) {
416+ $ this ->warn ('⚠️ DEPRECATION WARNING: The --release option is deprecated. ' );
417+ $ this ->warn (' Please use --github-versions instead to create a PR to the coolify-cdn repository. ' );
418+ $ this ->warn (' This option will continue to work but may be removed in a future version. ' );
419+ $ this ->newLine ();
420+
252421 if ($ nightly ) {
253422 $ this ->info ('About to sync NIGHLTY versions.json to BunnyCDN. ' );
254423 } else {
@@ -281,6 +450,22 @@ public function handle()
281450 // Sync releases to GitHub repository
282451 $ this ->syncReleasesToGitHubRepo ();
283452
453+ return ;
454+ } elseif ($ only_github_versions ) {
455+ $ envLabel = $ nightly ? 'NIGHTLY ' : 'PRODUCTION ' ;
456+ $ file = file_get_contents ($ versions_location );
457+ $ json = json_decode ($ file , true );
458+ $ actual_version = data_get ($ json , 'coolify.v4.version ' );
459+
460+ $ this ->info ("About to sync $ envLabel versions.json ( $ actual_version) to GitHub repository. " );
461+ $ confirmed = confirm ('Are you sure you want to sync versions.json via GitHub PR? ' );
462+ if (! $ confirmed ) {
463+ return ;
464+ }
465+
466+ // Sync versions.json to GitHub repository
467+ $ this ->syncVersionsToGitHubRepo ($ versions_location , $ nightly );
468+
284469 return ;
285470 }
286471
0 commit comments