diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index a480f4cb4f..949dbb3e6b 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -486,9 +486,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND $useAi = ($ai !== 'no'); $apiKey = $useAi ? System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', '') : ''; $aiChangelog = ''; // Track AI-generated changelog for PR description - Console::info('Checking for _APP_ASSISTANT_OPENAI_API_KEY... [' . (! empty($apiKey) ? 'FOUND' : 'NOT FOUND') . ']'); + if (! empty($apiKey) && ! $examplesOnly) { - Console::info("Using AI to determine version bump and changelog for {$language['name']} SDK..."); + Console::info("Analyzing SDK changes with AI..."); $aiResult = $this->generateVersionAndChangelog($language, $result); if ($aiResult !== null) { @@ -502,6 +502,12 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // Update the changelog file $this->updateChangelogFile($language['changelog'], $newVersion, $newChangelog); + // Also update CHANGELOG.md in the generated SDK directory + $sdkChangelogPath = $result . '/CHANGELOG.md'; + if (file_exists($sdkChangelogPath)) { + $this->updateChangelogFile($sdkChangelogPath, $newVersion, $newChangelog); + } + // Reload the language config with updated values $language['version'] = $newVersion; @@ -512,10 +518,8 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND } catch (\Throwable $exception) { Console::error($exception->getMessage()); } - - Console::success("AI determined version: {$newVersion} ({$aiResult['versionBump']} bump)"); } else { - Console::warning('AI version generation failed, using existing version'); + Console::warning('AI analysis failed, using existing version'); } } @@ -524,33 +528,45 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND $repoBranch = $language['repoBranch'] ?? 'main'; if ($git && ! empty($gitUrl)) { + Console::info("Preparing {$language['name']} SDK repository..."); + \exec('rm -rf ' . $target . ' && \ mkdir -p ' . $target . ' && \ cd ' . $target . ' && \ - git init && \ + git init --quiet && \ git config core.ignorecase false && \ git config pull.rebase false && \ + git config advice.defaultBranchName false && \ git remote add origin ' . $gitUrl . ' && \ - git fetch origin && \ + git fetch origin --quiet --no-tags --depth 1 ' . $repoBranch . ' 2>&1 | grep -v "^remote:" | grep -v "^From " | grep -v "^ \* " || true && \ (git checkout -f ' . $repoBranch . ' 2>/dev/null || git checkout -b ' . $repoBranch . ') && \ - git pull origin ' . $repoBranch . ' && \ + git pull origin ' . $repoBranch . ' --quiet --no-tags 2>&1 | grep -v "^From " | grep -v "^ \* " || true && \ (git checkout -f ' . $gitBranch . ' 2>/dev/null || git checkout -b ' . $gitBranch . ') && \ - (git fetch origin ' . $gitBranch . ' 2>/dev/null || git push -u origin ' . $gitBranch . ') && \ + (git fetch origin ' . $gitBranch . ' --quiet --no-tags --depth 1 2>/dev/null || git push -u origin ' . $gitBranch . ' --quiet 2>&1 | grep -v "^remote:" || true) && \ git reset --hard origin/' . $gitBranch . ' 2>/dev/null || true && \ - (test -d .github && cp -r .github /tmp/.github-backup-$$ || true) && \ - git rm -rf --cached . && \ - git clean -fdx -e .git -e .github && \ + (if [ -d .github ]; then cp -r .github /tmp/.github-backup-$$ 2>/dev/null; fi) && \ + git rm -rf --cached . 2>/dev/null && \ + git clean -fdx -e .git -e .github 2>/dev/null && \ cp -r ' . $result . '/. ' . $target . '/ && \ - (test -d /tmp/.github-backup-$$ && cp -rn /tmp/.github-backup-$$/.github . && rm -rf /tmp/.github-backup-$$ || true) && \ + (if [ -d /tmp/.github-backup-$$/.github ]; then cp -rn /tmp/.github-backup-$$/.github . 2>/dev/null && rm -rf /tmp/.github-backup-$$; fi) && \ git add -A && \ - git commit -m "' . $message . '" && \ - git push -u origin ' . $gitBranch . ' - '); + git commit -m "' . $message . '" --quiet && \ + git push -u origin ' . $gitBranch . ' --quiet 2>&1 | grep -E "^(To | |[0-9a-f]+\\.\\.[0-9a-f]+)" || true + ', $gitOutput, $gitReturnCode); + + if ($gitReturnCode !== 0) { + Console::warning("Git operations completed with warnings (exit code: {$gitReturnCode})"); + } Console::success("Pushed {$language['name']} SDK to {$gitUrl}"); if ($git) { $prTitle = "feat: {$language['name']} SDK update for version {$language['version']}"; - $prBody = "This PR contains updates to the {$language['name']} SDK for version {$language['version']} . "; + + // Build PR body with AI changelog if available + $prBody = "This PR contains updates to the {$language['name']} SDK for version {$language['version']}."; + if (!empty($aiChangelog) && $aiChangelog !== '* No user-facing SDK changes.') { + $prBody .= "\n\n## Changes\n\n{$aiChangelog}"; + } $repoName = $language['gitUserName'] . '/' . $language['gitRepoName']; Console::info("Creating pull request for {$language['name']} SDK..."); @@ -765,36 +781,47 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ); $prompt = <<setSchema($schema) @@ -805,11 +832,13 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ->setExcludePaths([ '.github/workflows/**', '.github/ISSUE_TEMPLATE/**', + '.git/**', ]) ->setMaxDiffLines(500) ->setUserId('sdk-analyst'); Console::info("Running DiffCheck for {$language['name']} SDK..."); + $result = (new DiffCheck())->run( runner: $adapter, base: DiffCheckRepository::remote($gitUrl, $repoBranch), @@ -819,7 +848,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ); if (!$result['hasChanges']) { - Console::warning("No changes detected for {$language['name']} SDK"); + Console::info("✓ No changes detected - SDK is up to date"); return null; } @@ -831,15 +860,11 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND return null; } - Console::log('AI raw response:'); - Console::log($responseContent); - Console::log('--- End of AI response ---'); - $parsed = json_decode($responseContent, true); if (json_last_error() !== JSON_ERROR_NONE) { Console::warning('Failed to parse AI response as JSON: ' . json_last_error_msg()); - Console::log('Raw response that failed to parse:'); + Console::log('Raw response:'); Console::log($responseContent); return null; @@ -850,7 +875,14 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND return null; } - Console::info("AI analysis complete - Version bump: {$parsed['versionBump']}, New version: {$parsed['version']}"); + Console::success("✓ Analysis complete"); + Console::log(" Version: {$language['version']} → {$parsed['version']} ({$parsed['versionBump']} bump)"); + Console::log(" Changelog:"); + foreach (explode("\n", $parsed['changelog']) as $line) { + if (trim($line)) { + Console::log(" {$line}"); + } + } return [ 'version' => $parsed['version'], @@ -864,6 +896,16 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND } } + /** + * Get the SDK config file path + * + * @return string Path to the SDK config file + */ + protected function getSdkConfigPath(): string + { + return __DIR__ . '/../../../../app/config/sdks.php'; + } + /** * Update SDK version in the config file * @@ -874,7 +916,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND */ private function updateSdkVersion(string $platform, string $sdkKey, string $newVersion): bool { - $configPath = __DIR__ . '/../../../../app/config/sdks.php'; + $configPath = $this->getSdkConfigPath(); if (! file_exists($configPath)) { Console::error("Config file not found: {$configPath}"); @@ -884,13 +926,13 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND $content = file_get_contents($configPath); - // Find and replace the version for this specific SDK - // Pattern matches the version line in the SDK array - $pattern = '/(\[\s*[\'"]key[\'"]\s*=>\s*[\'"]' . preg_quote($sdkKey, '/') . '[\'"]\s*,[\s\S]*?[\'"]version[\'"]\s*=>\s*[\'"])([^\'"]+)([\'"])/m'; + // First, try to find inline version in SDK array (pattern 1) + // Pattern matches: ['key' => 'nodejs', ... 'version' => '22.1.2'] + $inlinePattern = '/(\[\s*[\'"]key[\'"]\s*=>\s*[\'"]' . preg_quote($sdkKey, '/') . '[\'"]\s*,[\s\S]*?[\'"]version[\'"]\s*=>\s*[\'"])([^\'"]+)([\'"])/m'; - if (preg_match($pattern, $content, $matches)) { + if (preg_match($inlinePattern, $content, $matches)) { $oldVersion = $matches[2]; - $newContent = preg_replace($pattern, '${1}' . $newVersion . '${3}', $content); + $newContent = preg_replace($inlinePattern, '${1}' . $newVersion . '${3}', $content); if (file_put_contents($configPath, $newContent) !== false) { Console::success("Updated {$sdkKey} version from {$oldVersion} to {$newVersion} in config"); @@ -901,11 +943,31 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND return false; } - } else { - Console::warning("Could not find version entry for {$sdkKey} in config"); - - return false; } + + // Second, try to find version in array format (pattern 2) + // Pattern matches: 'nodejs' => '22.1.2', or "nodejs" => "22.1.2", + // Also handles extra whitespace: 'nodejs' => '22.1.2', + $arrayPattern = '/([\'"]' . preg_quote($sdkKey, '/') . '[\'"]\s*=>\s*[\'"])([^\'"]+)([\'"],)/m'; + + if (preg_match($arrayPattern, $content, $matches)) { + $oldVersion = $matches[2]; + $newContent = preg_replace($arrayPattern, '${1}' . $newVersion . '${3}', $content); + + if (file_put_contents($configPath, $newContent) !== false) { + Console::success("Updated {$sdkKey} version from {$oldVersion} to {$newVersion} in config"); + + return true; + } else { + Console::error('Failed to write config file'); + + return false; + } + } + + Console::warning("Could not find version entry for {$sdkKey} in config"); + + return false; } /**