Skip to content

Instantly share code, notes, and snippets.

@phpwalter
Created March 11, 2026 02:50
Show Gist options
  • Select an option

  • Save phpwalter/dfe6690cbb419a7a22b98a1bc7365477 to your computer and use it in GitHub Desktop.

Select an option

Save phpwalter/dfe6690cbb419a7a22b98a1bc7365477 to your computer and use it in GitHub Desktop.
github.milestone.upload
/*
Before running:
1. Go to your GitHub Settings > Developer Settings > Personal Access Tokens (classic).
[https://github.com/settings/tokens]
2. GENERATE NEW TOKEN -> GENERATE NEW TOKEN [not classic!]
3. name it "labels"
4. Experation: tommorow
5. Select ONLY SELECT REPOSITORIES: your target repository
6. + ADD PERMISSIONS
7. Select ISSUES
NOTE:
For a token to be granted "Read and write" access to Issues
8. ACCESS: Read and Write
9. GENERATE TOKEN
10. Review your settings -> GENERATE TOKENS
11. Copy token
12. Paste that token into the YOUR_TOKEN_HERE.
13. go to your MILESTONES page
[yourname/your_project/milestones/]
14. Open the browser CONSOLE
15. paste the modified script
16. run script and select JSON file
17. verify it placed issues as expected
Use the sample JSON file to see how this works, than modify the JSON
to add your own issues, desc, etc.
NOTE:
- All fields are required
- DESC must be less than 100 characters
*/
// UPLOAD MILESTONES FROM JSON FILE
// 1. CONFIGURATION
// Generate a token at: https://github.com/settings/tokens
// Scope required: repo (or public_repo if public)
const CONFIG = {
token: "YOUR_ACCESS_TOKEN_HERE",
owner: "USERNAME_OR_ORG",
repo: "REPOSITORY_NAME"
};
// 2. FILE PICKER
function selectJSONFile() {
return new Promise((resolve, reject) => {
const input = document.createElement("input");
input.type = "file";
input.accept = "application/json";
input.onchange = () => {
if (!input.files.length) {
reject(new Error("No file selected"));
return;
}
resolve(input.files[0]);
};
input.click();
});
}
// 3. READ JSON
function readJSON(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
try {
resolve(JSON.parse(reader.result));
} catch {
reject(new Error("Invalid JSON file."));
}
};
reader.onerror = () => reject(new Error("File read error."));
reader.readAsText(file);
});
}
// 4. MAIN SCRIPT
async function uploadMilestones() {
const headers = {
"Authorization": `token ${CONFIG.token}`,
"Accept": "application/vnd.github.v3+json",
"Content-Type": "application/json"
};
try {
console.log("πŸ“‚ Select milestones JSON file...");
const file = await selectJSONFile();
console.log(`πŸ“„ Loading file: ${file.name}`);
const json = await readJSON(file);
if (!json || !Array.isArray(json.milestones)) {
throw new Error("JSON must contain a 'milestones' array.");
}
const milestones = json.milestones;
console.log(`βœ… ${milestones.length} milestones loaded.`);
// Fetch existing milestones
console.log("πŸ”„ Fetching existing milestones...");
let existing = [];
let page = 1;
while (true) {
const url = `https://api.github.com/repos/${CONFIG.owner}/${CONFIG.repo}/milestones?state=all&per_page=100&page=${page}`;
const res = await fetch(url, { headers });
if (!res.ok) {
throw new Error(`Fetch failed: ${res.status} ${res.statusText}`);
}
const data = await res.json();
if (data.length === 0) break;
existing = existing.concat(data);
page++;
}
const existingTitles = new Set(existing.map(m => m.title));
console.log(`πŸ“¦ Found ${existing.length} existing milestones.`);
let created = 0;
let skipped = 0;
for (const ms of milestones) {
if (!ms.title) {
console.warn("⚠️ Skipping milestone with no title");
skipped++;
continue;
}
if (existingTitles.has(ms.title)) {
console.log(`⏭️ Skipping existing: "${ms.title}"`);
skipped++;
continue;
}
const body = {
title: ms.title
};
if (ms.description) body.description = ms.description;
if (ms.dueOn) body.due_on = new Date(ms.dueOn).toISOString();
console.log(`βž• Creating milestone: "${ms.title}"`);
const createUrl = `https://api.github.com/repos/${CONFIG.owner}/${CONFIG.repo}/milestones`;
const res = await fetch(createUrl, {
method: "POST",
headers,
body: JSON.stringify(body)
});
if (res.ok) {
created++;
} else {
console.error(`❌ Failed to create "${ms.title}" (${res.status})`);
}
await new Promise(r => setTimeout(r, 300));
}
console.log("🏁 Upload complete.");
console.log(`βœ… Created: ${created}`);
console.log(`⏭️ Skipped: ${skipped}`);
} catch (err) {
console.error("❌ Error:", err.message);
}
}
// 5. RUN
uploadMilestones();
[
{
"title": "πŸ“‘ [M1] Spec Resolution & Registry",
"description": "\ud83c\udfc1 Milestone 1: The Blueprint\n\n\ud83c\udfaf Objective\nTransform OpenAPI 3.1 YAML into executable intent by resolving all references and building a frozen registry.\n\n\u2705 Definition of Done (DoD)\n- CLI loads a multi-file spec and outputs a registry preview.\n- Resolved `/openapi.json` endpoint serves full spec.\n- All $refs resolved and frozen.\n\n\ud83d\udce6 Acceptance Checks\n- CLI output shows full operation map (paths + security).\n- $ref errors cause readable CLI failure.\n- Registry is immutable after startup.\n\n\ud83d\udd17 Related Exec FRD\n- Contract Integrity\n- Spec Validation\n\n\ud83d\udee0\ufe0f Technical Guardrails\n- Python 3.11+, Pydantic v2\n- JSON Schema 2020-12 support\n- SpecLoader must not mutate spec at runtime.",
"dueOn": "2026-02-06T00:00:00Z"
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment