(options = {})
| 2152 | let autoSyncTimer = null; |
| 2153 | |
| 2154 | async function performGitHubBackup(options = {}) { |
| 2155 | const { owner, repo, branch } = options; |
| 2156 | let tempDir = null; |
| 2157 | try { |
| 2158 | const token = await getGitHubToken(); |
| 2159 | if (!token) throw new Error('Not logged in to gh CLI. Run: gh auth login'); |
| 2160 | |
| 2161 | const user = await getGitHubUser(token); |
| 2162 | if (!user) throw new Error('Failed to get GitHub user'); |
| 2163 | |
| 2164 | const studio = loadStudioConfig(); |
| 2165 | |
| 2166 | const finalOwner = owner || studio.githubBackup?.owner || user.login; |
| 2167 | const finalRepo = repo || studio.githubBackup?.repo; |
| 2168 | const finalBranch = branch || studio.githubBackup?.branch || 'main'; |
| 2169 | |
| 2170 | if (!finalRepo) throw new Error('No repo name provided'); |
| 2171 | |
| 2172 | const repoName = `${finalOwner}/${finalRepo}`; |
| 2173 | |
| 2174 | await ensureGitHubRepo(token, repoName); |
| 2175 | |
| 2176 | const opencodeConfig = getConfigPath(); |
| 2177 | if (!opencodeConfig) throw new Error('No opencode config path found'); |
| 2178 | |
| 2179 | const opencodeDir = path.dirname(opencodeConfig); |
| 2180 | const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio'); |
| 2181 | |
| 2182 | tempDir = path.join(os.tmpdir(), `opencode-backup-${Date.now()}`); |
| 2183 | fs.mkdirSync(tempDir, { recursive: true }); |
| 2184 | |
| 2185 | // Clone or init |
| 2186 | try { |
| 2187 | await execPromise(`git clone --depth 1 https://x-access-token:${token}@github.com/${repoName}.git .`, { cwd: tempDir }); |
| 2188 | } catch (e) { |
| 2189 | // If clone fails (empty repo?), try init |
| 2190 | await execPromise('git init', { cwd: tempDir }); |
| 2191 | await execPromise(`git remote add origin https://x-access-token:${token}@github.com/${repoName}.git`, { cwd: tempDir }); |
| 2192 | await execPromise(`git checkout -b ${finalBranch}`, { cwd: tempDir }); |
| 2193 | } |
| 2194 | |
| 2195 | // Configure git to suppress warnings |
| 2196 | try { |
| 2197 | await execPromise('git config core.autocrlf false', { cwd: tempDir }); |
| 2198 | await execPromise('git config core.safecrlf false', { cwd: tempDir }); |
| 2199 | } catch (e) { |
| 2200 | console.warn('Failed to set git config:', e.message); |
| 2201 | } |
| 2202 | |
| 2203 | const gitignoreContent = RESERVED_WIN_NAMES.map(n => `**/${n}`).join('\n') + '\n'; |
| 2204 | fs.writeFileSync(path.join(tempDir, '.gitignore'), gitignoreContent); |
| 2205 | |
| 2206 | const backupOpencodeDir = path.join(tempDir, 'opencode'); |
| 2207 | const backupStudioDir = path.join(tempDir, 'opencode-studio'); |
| 2208 | |
| 2209 | if (fs.existsSync(backupOpencodeDir)) fs.rmSync(backupOpencodeDir, { recursive: true }); |
| 2210 | if (fs.existsSync(backupStudioDir)) fs.rmSync(backupStudioDir, { recursive: true }); |
| 2211 |
no test coverage detected