opencode-studio-server 1.20.0 → 1.22.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.
- package/index.js +180 -136
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1095,82 +1095,95 @@ async function getGitHubUser(token) {
|
|
|
1095
1095
|
return await response.json();
|
|
1096
1096
|
}
|
|
1097
1097
|
|
|
1098
|
-
async function ensureGitHubRepo(token, repoName) {
|
|
1099
|
-
const owner = repoName.split('/')
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
const err = await createRes.text();
|
|
1133
|
-
throw new Error(`Failed to create repo: ${err}`);
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
const err = await response.text();
|
|
1137
|
-
throw new Error(`Failed to check repo: ${err}`);
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
function
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
}
|
|
1098
|
+
async function ensureGitHubRepo(token, repoName) {
|
|
1099
|
+
const [owner, repo] = repoName.split('/');
|
|
1100
|
+
|
|
1101
|
+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
|
1102
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
if (response.ok) {
|
|
1106
|
+
const data = await response.json();
|
|
1107
|
+
if (!data.default_branch) {
|
|
1108
|
+
await bootstrapEmptyRepo(token, owner, repo);
|
|
1109
|
+
}
|
|
1110
|
+
return data;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (response.status === 404) {
|
|
1114
|
+
const createRes = await fetch(`https://api.github.com/user/repos`, {
|
|
1115
|
+
method: 'POST',
|
|
1116
|
+
headers: {
|
|
1117
|
+
'Authorization': `Bearer ${token}`,
|
|
1118
|
+
'Content-Type': 'application/json'
|
|
1119
|
+
},
|
|
1120
|
+
body: JSON.stringify({
|
|
1121
|
+
name: repo,
|
|
1122
|
+
private: true,
|
|
1123
|
+
description: 'OpenCode Studio backup',
|
|
1124
|
+
auto_init: true
|
|
1125
|
+
})
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
if (createRes.ok) {
|
|
1129
|
+
return await createRes.json();
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
const err = await createRes.text();
|
|
1133
|
+
throw new Error(`Failed to create repo: ${err}`);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const err = await response.text();
|
|
1137
|
+
throw new Error(`Failed to check repo: ${err}`);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
async function bootstrapEmptyRepo(token, owner, repo) {
|
|
1141
|
+
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/README.md`, {
|
|
1142
|
+
method: 'PUT',
|
|
1143
|
+
headers: {
|
|
1144
|
+
'Authorization': `Bearer ${token}`,
|
|
1145
|
+
'Content-Type': 'application/json'
|
|
1146
|
+
},
|
|
1147
|
+
body: JSON.stringify({
|
|
1148
|
+
message: 'Initial commit',
|
|
1149
|
+
content: Buffer.from('# OpenCode Studio Backup\n').toString('base64')
|
|
1150
|
+
})
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
if (!res.ok) {
|
|
1154
|
+
const err = await res.text();
|
|
1155
|
+
throw new Error(`Failed to bootstrap repo: ${err}`);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function collectBlobs(rootDir, basePath = '', blobs = []) {
|
|
1160
|
+
const dir = basePath || rootDir;
|
|
1161
|
+
if (!fs.existsSync(dir)) return blobs;
|
|
1162
|
+
|
|
1163
|
+
for (const name of fs.readdirSync(dir)) {
|
|
1164
|
+
const fullPath = path.join(dir, name);
|
|
1165
|
+
const stat = fs.statSync(fullPath);
|
|
1166
|
+
|
|
1167
|
+
if (stat.isDirectory()) {
|
|
1168
|
+
if (name === 'node_modules' || name === '.git' || name === '.next') continue;
|
|
1169
|
+
collectBlobs(rootDir, fullPath, blobs);
|
|
1170
|
+
} else {
|
|
1171
|
+
if (name.endsWith('.log') || name.endsWith('.tmp')) continue;
|
|
1172
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
1173
|
+
blobs.push({
|
|
1174
|
+
path: path.relative(rootDir, fullPath).replace(/\\/g, '/'),
|
|
1175
|
+
mode: '100644',
|
|
1176
|
+
type: 'blob',
|
|
1177
|
+
content
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return blobs;
|
|
1182
|
+
}
|
|
1171
1183
|
|
|
1172
|
-
async function createGitHubBlob(token, blob) {
|
|
1173
|
-
const
|
|
1184
|
+
async function createGitHubBlob(token, repoName, blob) {
|
|
1185
|
+
const [owner, repo] = repoName.split('/');
|
|
1186
|
+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/blobs`, {
|
|
1174
1187
|
method: 'POST',
|
|
1175
1188
|
headers: {
|
|
1176
1189
|
'Authorization': `Bearer ${token}`,
|
|
@@ -1191,32 +1204,35 @@ async function createGitHubBlob(token, blob) {
|
|
|
1191
1204
|
return data.sha;
|
|
1192
1205
|
}
|
|
1193
1206
|
|
|
1194
|
-
async function createGitHubTree(token, repoName, treeItems) {
|
|
1195
|
-
const [owner, repo] = repoName.split('/');
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1207
|
+
async function createGitHubTree(token, repoName, treeItems, baseSha = null) {
|
|
1208
|
+
const [owner, repo] = repoName.split('/');
|
|
1209
|
+
const body = {
|
|
1210
|
+
tree: treeItems.map(item => ({
|
|
1211
|
+
path: item.path,
|
|
1212
|
+
mode: item.mode,
|
|
1213
|
+
type: item.type,
|
|
1214
|
+
sha: item.sha
|
|
1215
|
+
}))
|
|
1216
|
+
};
|
|
1217
|
+
if (baseSha) body.base_tree = baseSha;
|
|
1218
|
+
|
|
1219
|
+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
|
|
1220
|
+
method: 'POST',
|
|
1221
|
+
headers: {
|
|
1222
|
+
'Authorization': `Bearer ${token}`,
|
|
1223
|
+
'Content-Type': 'application/json'
|
|
1224
|
+
},
|
|
1225
|
+
body: JSON.stringify(body)
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
if (!response.ok) {
|
|
1229
|
+
const err = await response.text();
|
|
1230
|
+
throw new Error(`Failed to create tree: ${err}`);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const data = await response.json();
|
|
1234
|
+
return data.sha;
|
|
1235
|
+
}
|
|
1220
1236
|
|
|
1221
1237
|
async function createGitHubCommit(token, repoName, treeSha, message) {
|
|
1222
1238
|
const [owner, repo] = repoName.split('/');
|
|
@@ -1253,25 +1269,49 @@ async function createGitHubCommit(token, repoName, treeSha, message) {
|
|
|
1253
1269
|
return data.sha;
|
|
1254
1270
|
}
|
|
1255
1271
|
|
|
1256
|
-
async function updateGitHubRef(token, repoName, commitSha, branch = 'main') {
|
|
1257
|
-
const [owner, repo] = repoName.split('/');
|
|
1258
|
-
|
|
1259
|
-
const
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
}
|
|
1272
|
+
async function updateGitHubRef(token, repoName, commitSha, branch = 'main') {
|
|
1273
|
+
const [owner, repo] = repoName.split('/');
|
|
1274
|
+
|
|
1275
|
+
const checkRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
|
|
1276
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
if (checkRes.status === 404) {
|
|
1280
|
+
const createRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs`, {
|
|
1281
|
+
method: 'POST',
|
|
1282
|
+
headers: {
|
|
1283
|
+
'Authorization': `Bearer ${token}`,
|
|
1284
|
+
'Content-Type': 'application/json'
|
|
1285
|
+
},
|
|
1286
|
+
body: JSON.stringify({
|
|
1287
|
+
ref: `refs/heads/${branch}`,
|
|
1288
|
+
sha: commitSha
|
|
1289
|
+
})
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
if (!createRes.ok) {
|
|
1293
|
+
const err = await createRes.text();
|
|
1294
|
+
throw new Error(`Failed to create ref: ${err}`);
|
|
1295
|
+
}
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
|
|
1300
|
+
method: 'PATCH',
|
|
1301
|
+
headers: {
|
|
1302
|
+
'Authorization': `Bearer ${token}`,
|
|
1303
|
+
'Content-Type': 'application/json'
|
|
1304
|
+
},
|
|
1305
|
+
body: JSON.stringify({
|
|
1306
|
+
sha: commitSha
|
|
1307
|
+
})
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
if (!response.ok) {
|
|
1311
|
+
const err = await response.text();
|
|
1312
|
+
throw new Error(`Failed to update ref: ${err}`);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1275
1315
|
|
|
1276
1316
|
app.get('/api/github/backup/status', async (req, res) => {
|
|
1277
1317
|
try {
|
|
@@ -1328,22 +1368,26 @@ app.post('/api/github/backup', async (req, res) => {
|
|
|
1328
1368
|
const opencodeConfig = getConfigPath();
|
|
1329
1369
|
if (!opencodeConfig) return res.status(400).json({ error: 'No opencode config path found' });
|
|
1330
1370
|
|
|
1331
|
-
const opencodeDir = path.dirname(opencodeConfig);
|
|
1332
|
-
const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio');
|
|
1333
|
-
|
|
1334
|
-
const
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1371
|
+
const opencodeDir = path.dirname(opencodeConfig);
|
|
1372
|
+
const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio');
|
|
1373
|
+
|
|
1374
|
+
const opencodeBlobs = collectBlobs(opencodeDir);
|
|
1375
|
+
const studioBlobs = collectBlobs(studioDir);
|
|
1376
|
+
|
|
1377
|
+
for (const blob of opencodeBlobs) {
|
|
1378
|
+
blob.sha = await createGitHubBlob(token, repoName, blob);
|
|
1379
|
+
blob.path = `opencode/${blob.path}`;
|
|
1380
|
+
delete blob.content;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
for (const blob of studioBlobs) {
|
|
1384
|
+
blob.sha = await createGitHubBlob(token, repoName, blob);
|
|
1385
|
+
blob.path = `opencode-studio/${blob.path}`;
|
|
1386
|
+
delete blob.content;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
const allBlobs = [...opencodeBlobs, ...studioBlobs];
|
|
1390
|
+
const rootTreeSha = await createGitHubTree(token, repoName, allBlobs);
|
|
1347
1391
|
|
|
1348
1392
|
const timestamp = new Date().toISOString();
|
|
1349
1393
|
const commitMessage = `OpenCode Studio backup ${timestamp}`;
|