Repository navigation
Expand file tree
/
Copy pathsubIssue.js
More file actions
130 lines (114 loc) · 3.37 KB
/
Copy pathsubIssue.js
File metadata and controls
130 lines (114 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// subIssue.js
import fs from "fs";
import readline from "readline";
import { graphql } from "@octokit/graphql";
import dotenv from "dotenv";
import { hideBin } from "yargs/helpers";
dotenv.config();
const token = process.env.GITHUB_TOKEN;
if (!token) {
console.error("❌ Missing GITHUB_TOKEN in .env");
process.exit(1);
}
const args = hideBin(process.argv);
let repo;
if (args.length >= 1) {
repo = args[0];
} else {
const readline = await import("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const ask = (q) => new Promise((res) => rl.question(q, res));
repo = await ask("Enter the target repository name (e.g. my-repo): ");
rl.close();
repo = repo.trim();
}
const gql = graphql.defaults({ headers: { authorization: `token ${token}` } });
function parseTSV(filepath) {
const raw = fs.readFileSync(filepath, "utf-8");
const lines = raw.trim().split("\n");
const headers = lines[0].split("\t").map((h) => h.trim());
return lines.slice(1).map((line) => {
const values = line.split("\t");
return Object.fromEntries(headers.map((h, i) => [h, values[i]?.trim()]));
});
}
function extractIssueNumber(url) {
if (!url || typeof url !== "string") return null;
const match = url.match(/issues\/(\d+)/);
return match ? parseInt(match[1]) : null;
}
async function getIssueId(owner, repo, issueNumber) {
const query = `
query ($owner: String!, $repo: String!, $issueNumber: Int!) {
repository(owner: $owner, name: $repo) {
issue(number: $issueNumber) {
id
}
}
}
`;
const result = await gql(query, { owner, repo, issueNumber });
return result.repository.issue.id;
}
async function linkIssues(parentId, childId, childNum, parentNum) {
const mutation = `
mutation ($input: AddSubIssueInput!) {
addSubIssue(input: $input) {
issue { id }
subIssue { id }
}
}
`;
try {
await gql(mutation, {
input: {
issueId: parentId,
subIssueId: childId,
},
});
console.log(`🔗 Linked issue #${childNum} as child of #${parentNum}`);
} catch (err) {
console.error(
`❌ Failed to link issue #${childNum} to #${parentNum}:`,
err.message
);
}
}
(async () => {
const owner = "Kin230k";
const rows = parseTSV("parents.tsv");
console.log("👀 First row sample:", rows[0]);
for (const row of rows) {
if (!row.URL) {
console.warn("⚠️ Skipping row with missing URL:", row);
continue;
}
console.log("📦 Checking row:", row);
console.log("🔍 Parent field raw value:", row["Parent issue"]);
const childNumber = extractIssueNumber(row.URL);
const parentNumber = extractIssueNumber(row["Parent issue"]);
if (!childNumber) {
console.warn("⚠️ Skipping row with invalid or missing child issue:", row);
continue;
}
if (!parentNumber) {
console.log(
`ℹ️ No parent specified for issue #${childNumber}, skipping link.`
);
continue;
}
try {
const childId = await getIssueId(owner, repo, childNumber);
const parentId = await getIssueId(owner, repo, parentNumber);
await linkIssues(parentId, childId, childNumber, parentNumber);
} catch (err) {
console.error(
`❌ Could not link issue #${childNumber} to #${parentNumber}:`,
err.message
);
}
}
})();