Skip to content

Commit 95187df

Browse files
committed
feat: add support for resuming discussion copying with --start-from option
1 parent 02b7fa7 commit 95187df

File tree

1 file changed

+47
-8
lines changed

1 file changed

+47
-8
lines changed

scripts/copy-discussions.js

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,17 @@ if (args.includes('--help') || args.includes('-h')) {
4040
console.log('Copy Discussions between GitHub repositories');
4141
console.log('');
4242
console.log('Usage:');
43-
console.log(' node copy-discussions.js <source_org> <source_repo> <target_org> <target_repo>');
43+
console.log(' node copy-discussions.js <source_org> <source_repo> <target_org> <target_repo> [options]');
4444
console.log('');
4545
console.log('Arguments:');
4646
console.log(' source_org Source organization name');
4747
console.log(' source_repo Source repository name');
4848
console.log(' target_org Target organization name');
4949
console.log(' target_repo Target repository name');
5050
console.log('');
51+
console.log('Options:');
52+
console.log(' --start-from <number> Start copying from a specific discussion number (useful for resuming)');
53+
console.log('');
5154
console.log('Environment Variables (Required):');
5255
console.log(' SOURCE_TOKEN GitHub token with read access to source repository discussions');
5356
console.log(' TARGET_TOKEN GitHub token with write access to target repository discussions');
@@ -59,6 +62,9 @@ if (args.includes('--help') || args.includes('-h')) {
5962
console.log('Example:');
6063
console.log(' node copy-discussions.js source-org repo1 target-org repo2');
6164
console.log('');
65+
console.log('Example with resume from discussion #50:');
66+
console.log(' node copy-discussions.js source-org repo1 target-org repo2 --start-from 50');
67+
console.log('');
6268
console.log('Example with GHES:');
6369
console.log(' SOURCE_API_URL=https://github.mycompany.com/api/v3 \\');
6470
console.log(' TARGET_API_URL=https://api.github.com \\');
@@ -71,13 +77,33 @@ if (args.includes('--help') || args.includes('-h')) {
7177
console.log(' - This script copies discussion content, comments, replies, polls, reactions,');
7278
console.log(' locked status, and pinned status');
7379
console.log(' - Attachments (images and files) will not copy over and require manual handling');
80+
console.log(' - Use --start-from to resume from a specific discussion in case of interruption');
7481
process.exit(0);
7582
}
7683

84+
// Parse --start-from option
85+
let startFromNumber = null;
86+
const startFromIndex = args.indexOf('--start-from');
87+
if (startFromIndex !== -1) {
88+
if (startFromIndex + 1 >= args.length) {
89+
console.error("ERROR: --start-from requires a discussion number");
90+
process.exit(1);
91+
}
92+
startFromNumber = parseInt(args[startFromIndex + 1], 10);
93+
if (isNaN(startFromNumber) || startFromNumber < 1) {
94+
console.error("ERROR: --start-from must be a positive integer");
95+
process.exit(1);
96+
}
97+
// Remove the option and its value from args
98+
args.splice(startFromIndex, 2);
99+
}
100+
77101
if (args.length !== 4) {
78-
console.error("Usage: node copy-discussions.js <source_org> <source_repo> <target_org> <target_repo>");
102+
console.error("Usage: node copy-discussions.js <source_org> <source_repo> <target_org> <target_repo> [--start-from <number>]");
79103
console.error("\nExample:");
80104
console.error(" node copy-discussions.js source-org repo1 target-org repo2");
105+
console.error("\nExample with resume:");
106+
console.error(" node copy-discussions.js source-org repo1 target-org repo2 --start-from 50");
81107
console.error("\nFor more information, use --help");
82108
process.exit(1);
83109
}
@@ -690,7 +716,7 @@ async function createDiscussion(octokit, repositoryId, categoryId, title, body,
690716
}
691717

692718
// Add metadata
693-
enhancedBody += `\n---\n<details>\n<summary><i>Original discussion metadata</i></summary>\n\n_Original discussion by @${sourceAuthor} on ${sourceCreated}_\n_Source: ${sourceUrl}_\n${locked ? '\n_🔒 This discussion was locked in the source repository_' : ''}\n</details>`;
719+
enhancedBody += `\n\n<details>\n<summary><i>Original discussion metadata</i></summary>\n\n_Original discussion by @${sourceAuthor} on ${sourceCreated}_\n_Source: ${sourceUrl}_\n${locked ? '\n_🔒 This discussion was locked in the source repository_' : ''}\n</details>`;
694720

695721
log(`Creating discussion: '${title}'`);
696722

@@ -759,7 +785,7 @@ async function addDiscussionComment(octokit, discussionId, body, originalAuthor,
759785
enhancedBody += reactionsMarkdown;
760786
}
761787

762-
enhancedBody += `\n---\n<details>\n<summary><i>Original comment metadata</i></summary>\n\n_Original comment by @${originalAuthor} on ${originalCreated}_\n</details>`;
788+
enhancedBody += `\n\n<details>\n<summary><i>Original comment metadata</i></summary>\n\n_Original comment by @${originalAuthor} on ${originalCreated}_\n</details>`;
763789

764790
log("Adding comment to discussion");
765791

@@ -789,7 +815,7 @@ async function addDiscussionCommentReply(octokit, discussionId, replyToId, body,
789815
enhancedBody += reactionsMarkdown;
790816
}
791817

792-
enhancedBody += `\n---\n_Original reply by @${originalAuthor} on ${originalCreated}_`;
818+
enhancedBody += `\n\n<details>\n<summary><i>Original reply metadata</i></summary>\n\n_Original reply by @${originalAuthor} on ${originalCreated}_\n</details>`;
793819

794820
log(`Adding reply to comment ${replyToId}`);
795821

@@ -922,7 +948,7 @@ async function copyDiscussionComments(octokit, discussionId, comments, answerCom
922948
return null;
923949
}
924950

925-
async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo, targetRepoId, targetCategories, targetLabels, cursor = null) {
951+
async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo, targetRepoId, targetCategories, targetLabels, cursor = null, startFromNumber = null) {
926952
log(`Fetching discussions page (cursor: ${cursor || "null"})...`);
927953

928954
await rateLimitSleep();
@@ -946,6 +972,13 @@ async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo,
946972
for (const discussion of discussions) {
947973
totalDiscussions++;
948974

975+
// Skip discussions before the start-from number
976+
if (startFromNumber !== null && discussion.number < startFromNumber) {
977+
log(`Skipping discussion #${discussion.number}: '${discussion.title}' (before start-from #${startFromNumber})`);
978+
skippedDiscussions++;
979+
continue;
980+
}
981+
949982
log(`\n=== Processing discussion #${discussion.number}: '${discussion.title}' ===`);
950983

951984
// Get or fallback category
@@ -1070,7 +1103,8 @@ async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo,
10701103
targetRepoId,
10711104
targetCategories,
10721105
targetLabels,
1073-
pageInfo.endCursor
1106+
pageInfo.endCursor,
1107+
startFromNumber
10741108
);
10751109
} else {
10761110
log("No more pages to process");
@@ -1088,6 +1122,9 @@ async function main() {
10881122
log("Starting discussion copy process...");
10891123
log(`Source: ${SOURCE_ORG}/${SOURCE_REPO}`);
10901124
log(`Target: ${TARGET_ORG}/${TARGET_REPO}`);
1125+
if (startFromNumber !== null) {
1126+
log(`Resume mode: Starting from discussion #${startFromNumber}`);
1127+
}
10911128
log("");
10921129
log("⚡ This script uses conservative rate limiting to avoid GitHub API limits");
10931130
log("");
@@ -1133,7 +1170,9 @@ async function main() {
11331170
SOURCE_REPO,
11341171
targetRepoId,
11351172
targetCategories,
1136-
targetLabels
1173+
targetLabels,
1174+
null,
1175+
startFromNumber
11371176
);
11381177

11391178
// Summary

0 commit comments

Comments
 (0)