opencode-studio-server 1.20.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +119 -94
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1137,40 +1137,34 @@ async function ensureGitHubRepo(token, repoName) {
1137
1137
  throw new Error(`Failed to check repo: ${err}`);
1138
1138
  }
1139
1139
 
1140
- function buildCommitTree(opencodeDir, studioDir, files = {}, basePath = '') {
1141
- const tree = [];
1142
-
1143
- for (const name of fs.readdirSync(basePath || opencodeDir)) {
1144
- const fullPath = path.join(basePath || opencodeDir, name);
1145
- const stat = fs.statSync(fullPath);
1146
-
1147
- if (stat.isDirectory()) {
1148
- if (name === 'node_modules' || name === '.git' || name === '.next') continue;
1149
- const subtree = buildCommitTree(opencodeDir, studioDir, files, fullPath);
1150
- tree.push({
1151
- path: path.relative(opencodeDir, fullPath),
1152
- mode: '040000',
1153
- type: 'tree',
1154
- sha: subtree.sha,
1155
- size: subtree.size
1156
- });
1157
- if (subtree.size) tree[tree.length - 1].size = subtree.size;
1158
- } else {
1159
- if (name.endsWith('.log') || name.endsWith('.tmp')) continue;
1160
- const content = fs.readFileSync(fullPath, 'utf8');
1161
- const size = Buffer.byteLength(content);
1162
- const blob = { path: path.relative(opencodeDir, fullPath), mode: '100644', type: 'blob', size, content };
1163
- const blobKey = `${basePath || opencodeDir}/${name}`;
1164
- files[blobKey] = blob;
1165
- tree.push(blob);
1166
- }
1167
- }
1168
-
1169
- return { sha: null, tree };
1170
- }
1140
+ function collectBlobs(rootDir, basePath = '', blobs = []) {
1141
+ const dir = basePath || rootDir;
1142
+ if (!fs.existsSync(dir)) return blobs;
1143
+
1144
+ for (const name of fs.readdirSync(dir)) {
1145
+ const fullPath = path.join(dir, name);
1146
+ const stat = fs.statSync(fullPath);
1147
+
1148
+ if (stat.isDirectory()) {
1149
+ if (name === 'node_modules' || name === '.git' || name === '.next') continue;
1150
+ collectBlobs(rootDir, fullPath, blobs);
1151
+ } else {
1152
+ if (name.endsWith('.log') || name.endsWith('.tmp')) continue;
1153
+ const content = fs.readFileSync(fullPath, 'utf8');
1154
+ blobs.push({
1155
+ path: path.relative(rootDir, fullPath).replace(/\\/g, '/'),
1156
+ mode: '100644',
1157
+ type: 'blob',
1158
+ content
1159
+ });
1160
+ }
1161
+ }
1162
+ return blobs;
1163
+ }
1171
1164
 
1172
- async function createGitHubBlob(token, blob) {
1173
- const response = await fetch('https://api.github.com/git/blobs', {
1165
+ async function createGitHubBlob(token, repoName, blob) {
1166
+ const [owner, repo] = repoName.split('/');
1167
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/blobs`, {
1174
1168
  method: 'POST',
1175
1169
  headers: {
1176
1170
  'Authorization': `Bearer ${token}`,
@@ -1191,32 +1185,35 @@ async function createGitHubBlob(token, blob) {
1191
1185
  return data.sha;
1192
1186
  }
1193
1187
 
1194
- async function createGitHubTree(token, repoName, treeItems) {
1195
- const [owner, repo] = repoName.split('/');
1196
- const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
1197
- method: 'POST',
1198
- headers: {
1199
- 'Authorization': `Bearer ${token}`,
1200
- 'Content-Type': 'application/json'
1201
- },
1202
- body: JSON.stringify({
1203
- tree: treeItems.map(item => ({
1204
- path: item.path,
1205
- mode: item.mode,
1206
- type: item.type,
1207
- sha: item.sha
1208
- }))
1209
- })
1210
- });
1211
-
1212
- if (!response.ok) {
1213
- const err = await response.text();
1214
- throw new Error(`Failed to create tree: ${err}`);
1215
- }
1216
-
1217
- const data = await response.json();
1218
- return data.sha;
1219
- }
1188
+ async function createGitHubTree(token, repoName, treeItems, baseSha = null) {
1189
+ const [owner, repo] = repoName.split('/');
1190
+ const body = {
1191
+ tree: treeItems.map(item => ({
1192
+ path: item.path,
1193
+ mode: item.mode,
1194
+ type: item.type,
1195
+ sha: item.sha
1196
+ }))
1197
+ };
1198
+ if (baseSha) body.base_tree = baseSha;
1199
+
1200
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
1201
+ method: 'POST',
1202
+ headers: {
1203
+ 'Authorization': `Bearer ${token}`,
1204
+ 'Content-Type': 'application/json'
1205
+ },
1206
+ body: JSON.stringify(body)
1207
+ });
1208
+
1209
+ if (!response.ok) {
1210
+ const err = await response.text();
1211
+ throw new Error(`Failed to create tree: ${err}`);
1212
+ }
1213
+
1214
+ const data = await response.json();
1215
+ return data.sha;
1216
+ }
1220
1217
 
1221
1218
  async function createGitHubCommit(token, repoName, treeSha, message) {
1222
1219
  const [owner, repo] = repoName.split('/');
@@ -1253,25 +1250,49 @@ async function createGitHubCommit(token, repoName, treeSha, message) {
1253
1250
  return data.sha;
1254
1251
  }
1255
1252
 
1256
- async function updateGitHubRef(token, repoName, commitSha, branch = 'main') {
1257
- const [owner, repo] = repoName.split('/');
1258
-
1259
- const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
1260
- method: 'PATCH',
1261
- headers: {
1262
- 'Authorization': `Bearer ${token}`,
1263
- 'Content-Type': 'application/json'
1264
- },
1265
- body: JSON.stringify({
1266
- sha: commitSha
1267
- })
1268
- });
1269
-
1270
- if (!response.status === 200 && response.status !== 201) {
1271
- const err = await response.text();
1272
- throw new Error(`Failed to update ref: ${err}`);
1273
- }
1274
- }
1253
+ async function updateGitHubRef(token, repoName, commitSha, branch = 'main') {
1254
+ const [owner, repo] = repoName.split('/');
1255
+
1256
+ const checkRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
1257
+ headers: { 'Authorization': `Bearer ${token}` }
1258
+ });
1259
+
1260
+ if (checkRes.status === 404) {
1261
+ const createRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs`, {
1262
+ method: 'POST',
1263
+ headers: {
1264
+ 'Authorization': `Bearer ${token}`,
1265
+ 'Content-Type': 'application/json'
1266
+ },
1267
+ body: JSON.stringify({
1268
+ ref: `refs/heads/${branch}`,
1269
+ sha: commitSha
1270
+ })
1271
+ });
1272
+
1273
+ if (!createRes.ok) {
1274
+ const err = await createRes.text();
1275
+ throw new Error(`Failed to create ref: ${err}`);
1276
+ }
1277
+ return;
1278
+ }
1279
+
1280
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
1281
+ method: 'PATCH',
1282
+ headers: {
1283
+ 'Authorization': `Bearer ${token}`,
1284
+ 'Content-Type': 'application/json'
1285
+ },
1286
+ body: JSON.stringify({
1287
+ sha: commitSha
1288
+ })
1289
+ });
1290
+
1291
+ if (!response.ok) {
1292
+ const err = await response.text();
1293
+ throw new Error(`Failed to update ref: ${err}`);
1294
+ }
1295
+ }
1275
1296
 
1276
1297
  app.get('/api/github/backup/status', async (req, res) => {
1277
1298
  try {
@@ -1328,22 +1349,26 @@ app.post('/api/github/backup', async (req, res) => {
1328
1349
  const opencodeConfig = getConfigPath();
1329
1350
  if (!opencodeConfig) return res.status(400).json({ error: 'No opencode config path found' });
1330
1351
 
1331
- const opencodeDir = path.dirname(opencodeConfig);
1332
- const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio');
1333
-
1334
- const blobs = {};
1335
- const opencodeTree = buildCommitTree(opencodeDir, studioDir, blobs);
1336
- const studioTree = buildCommitTree(studioDir, studioDir, {}, studioDir);
1337
-
1338
- const opencodeTreeSha = await createGitHubTree(token, repoName, opencodeTree.tree);
1339
- const studioTreeSha = await createGitHubTree(token, repoName, studioTree.tree);
1340
-
1341
- const rootTreeItems = [
1342
- { path: 'opencode', mode: '040000', type: 'tree', sha: opencodeTreeSha },
1343
- { path: 'opencode-studio', mode: '040000', type: 'tree', sha: studioTreeSha }
1344
- ];
1345
-
1346
- const rootTreeSha = await createGitHubTree(token, repoName, rootTreeItems);
1352
+ const opencodeDir = path.dirname(opencodeConfig);
1353
+ const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio');
1354
+
1355
+ const opencodeBlobs = collectBlobs(opencodeDir);
1356
+ const studioBlobs = collectBlobs(studioDir);
1357
+
1358
+ for (const blob of opencodeBlobs) {
1359
+ blob.sha = await createGitHubBlob(token, repoName, blob);
1360
+ blob.path = `opencode/${blob.path}`;
1361
+ delete blob.content;
1362
+ }
1363
+
1364
+ for (const blob of studioBlobs) {
1365
+ blob.sha = await createGitHubBlob(token, repoName, blob);
1366
+ blob.path = `opencode-studio/${blob.path}`;
1367
+ delete blob.content;
1368
+ }
1369
+
1370
+ const allBlobs = [...opencodeBlobs, ...studioBlobs];
1371
+ const rootTreeSha = await createGitHubTree(token, repoName, allBlobs);
1347
1372
 
1348
1373
  const timestamp = new Date().toISOString();
1349
1374
  const commitMessage = `OpenCode Studio backup ${timestamp}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-studio-server",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "description": "Backend server for OpenCode Studio - manages opencode configurations",
5
5
  "main": "index.js",
6
6
  "bin": {