-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
161 lines (133 loc) · 5.64 KB
/
script.js
File metadata and controls
161 lines (133 loc) · 5.64 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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Get references to our HTML elements
const tokenInput = document.getElementById('token');
const sourceRepoInput = document.getElementById('source-repo');
const targetRepoInput = document.getElementById('target-repo');
const deleteCheckbox = document.getElementById('delete-labels');
const copyButton = document.getElementById('copy-button');
const logOutput = document.getElementById('log-output');
// --- Helper Functions ---
// Function to log messages to the screen
function log(message) {
logOutput.textContent += message + '\n';
logOutput.scrollTop = logOutput.scrollHeight; // Auto-scroll to the bottom
}
// Function to make authenticated API requests to GitHub
async function githubApiRequest(endpoint, options = {}) {
const token = tokenInput.value;
const url = `https://api.github.com/${endpoint}`;
const headers = {
'Authorization': `token ${token}`,
'Accept': 'application/vnd.github.v3+json',
...options.headers,
};
const response = await fetch(url, { ...options, headers });
if (!response.ok) {
const errorData = await response.json();
throw new Error(`GitHub API Error for ${endpoint}: ${response.status} - ${errorData.message}`);
}
// DELETE requests might not have a body, so handle that
if (response.status === 204) {
return null;
}
return response.json();
}
// --- Main Logic ---
// Returns true if the input is an "owner/repo" repository reference, false if it's an org name.
function isRepository(input) {
return /^[^/]+\/[^/]+$/.test(input);
}
// Returns the base labels API endpoint for a given repository or organization input.
function getLabelsEndpoint(input) {
if (isRepository(input)) {
return `repos/${input}/labels`;
} else {
return `orgs/${input}/labels`;
}
}
async function copyLabels() {
// Clear previous logs and set button state
logOutput.textContent = '';
copyButton.disabled = true;
copyButton.textContent = 'Copying...';
try {
// 1. Get user inputs
const source = sourceRepoInput.value.trim();
const target = targetRepoInput.value.trim();
const deleteOldLabels = deleteCheckbox.checked;
if (!tokenInput.value || !source || !target) {
throw new Error("Please fill in all required fields.");
}
const sourceEndpoint = getLabelsEndpoint(source);
const targetEndpoint = getLabelsEndpoint(target);
log(`Fetching labels from ${source}...`);
const sourceLabels = await githubApiRequest(`${sourceEndpoint}?per_page=100`);
log(`Found ${sourceLabels.length} labels in source.`);
log(`Fetching labels from ${target}...`);
const targetLabels = await githubApiRequest(`${targetEndpoint}?per_page=100`);
log(`Found ${targetLabels.length} labels in target.`);
// Create a Map for efficient lookups of source labels by name
const sourceLabelsMap = new Map(sourceLabels.map(label => [label.name, label]));
const targetLabelsMap = new Map(targetLabels.map(label => [label.name, label]));
const labelsToCreate = [];
const labelsToUpdate = [];
const labelsToDelete = [];
// 2. Determine which labels to update or create
for (const [name, sourceLabel] of sourceLabelsMap.entries()) {
if (targetLabelsMap.has(name)) {
// Label exists in both, check if it needs an update
const targetLabel = targetLabelsMap.get(name);
if (sourceLabel.color !== targetLabel.color || sourceLabel.description !== targetLabel.description) {
labelsToUpdate.push(sourceLabel);
}
} else {
// Label only exists in source, needs to be created
labelsToCreate.push(sourceLabel);
}
}
// 3. Determine which labels to delete (if requested)
if (deleteOldLabels) {
for (const [name, targetLabel] of targetLabelsMap.entries()) {
if (!sourceLabelsMap.has(name)) {
labelsToDelete.push(targetLabel);
}
}
}
// 4. Execute the API calls
log('\n--- Starting Sync Process ---');
for (const label of labelsToDelete) {
log(`Deleting label: ${label.name}`);
await githubApiRequest(`${targetEndpoint}/${encodeURIComponent(label.name)}`, { method: 'DELETE' });
}
for (const label of labelsToUpdate) {
log(`Updating label: ${label.name}`);
await githubApiRequest(`${targetEndpoint}/${encodeURIComponent(label.name)}`, {
method: 'PATCH',
body: JSON.stringify({
color: label.color,
description: label.description || '', // Ensure description is not null
})
});
}
for (const label of labelsToCreate) {
log(`Creating label: ${label.name}`);
await githubApiRequest(`${targetEndpoint}`, {
method: 'POST',
body: JSON.stringify({
name: label.name,
color: label.color,
description: label.description || '',
})
});
}
log('\n✅ Done! All labels are synced.');
} catch (error) {
log(`\n❌ ERROR: ${error.message}`);
console.error(error);
} finally {
// Re-enable the button
copyButton.disabled = false;
copyButton.textContent = 'Copy Labels';
}
}
// Attach the event listener to the button
copyButton.addEventListener('click', copyLabels);