[Good First Issue] 🦊 Add new Theme: Cedar Moss - Beginner-Friendly Open-source Contribution #3270
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Re-enable Community Backlog on Close | |
| on: | |
| issues: | |
| types: [closed] | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| concurrency: | |
| group: community-issue-closed | |
| cancel-in-progress: true | |
| jobs: | |
| reenable-backlog: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| if: github.repository == 'lingdojo/kana-dojo' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.AUTOMATION_PR_TOKEN }} | |
| - name: Re-enable backlog item from closed issue | |
| id: reenable | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.AUTOMATION_PR_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| const templates = require('./.github/templates/messages.cjs'); | |
| const issue = context.payload.issue; | |
| if (!issue) { | |
| console.log('No issue in payload; skipping.'); | |
| return; | |
| } | |
| if (issue.pull_request) { | |
| console.log('Issue is a PR; skipping.'); | |
| return; | |
| } | |
| const labels = (issue.labels || []).map(function(label) { | |
| return label.name; | |
| }); | |
| if (!labels.includes(templates.labels.community)) { | |
| console.log('Issue is not a community issue; skipping.'); | |
| return; | |
| } | |
| if (issue.state_reason !== 'not_planned') { | |
| console.log(`Issue closed with reason "${issue.state_reason}"; skipping.`); | |
| return; | |
| } | |
| const themeBacklogPath = 'community/backlog/theme-backlog.json'; | |
| const factsBacklogPath = 'community/backlog/facts-backlog.json'; | |
| const proverbsBacklogPath = 'community/backlog/proverbs-backlog.json'; | |
| const haikuBacklogPath = 'community/backlog/haiku-backlog.json'; | |
| const triviaBacklogPath = 'community/backlog/trivia-backlog.json'; | |
| const grammarBacklogPath = 'community/backlog/grammar-backlog.json'; | |
| const animeQuotesBacklogPath = 'community/backlog/anime-quotes-backlog.json'; | |
| const idiomsBacklogPath = 'community/backlog/idioms-backlog.json'; | |
| const regionalDialectsBacklogPath = 'community/backlog/regional-dialects-backlog.json'; | |
| const falseFriendsBacklogPath = 'community/backlog/false-friends-backlog.json'; | |
| const culturalEtiquetteBacklogPath = 'community/backlog/cultural-etiquette-backlog.json'; | |
| const exampleSentencesBacklogPath = 'community/backlog/example-sentences-backlog.json'; | |
| const commonMistakesBacklogPath = 'community/backlog/common-mistakes-backlog.json'; | |
| const videoGameQuotesBacklogPath = 'community/backlog/video-game-quotes-backlog.json'; | |
| const wallpaperUrlsBacklogPath = 'community/backlog/wallpaper-urls-backlog.json'; | |
| const communityNotesBacklogPath = 'community/backlog/community-notes-backlog.json'; | |
| const themes = JSON.parse(fs.readFileSync(themeBacklogPath, 'utf8')); | |
| const facts = JSON.parse(fs.readFileSync(factsBacklogPath, 'utf8')); | |
| const proverbs = JSON.parse(fs.readFileSync(proverbsBacklogPath, 'utf8')); | |
| const haiku = JSON.parse(fs.readFileSync(haikuBacklogPath, 'utf8')); | |
| const trivia = JSON.parse(fs.readFileSync(triviaBacklogPath, 'utf8')); | |
| const grammar = JSON.parse(fs.readFileSync(grammarBacklogPath, 'utf8')); | |
| const animeQuotes = JSON.parse(fs.readFileSync(animeQuotesBacklogPath, 'utf8')); | |
| const idioms = JSON.parse(fs.readFileSync(idiomsBacklogPath, 'utf8')); | |
| const regionalDialects = JSON.parse(fs.readFileSync(regionalDialectsBacklogPath, 'utf8')); | |
| const falseFriends = JSON.parse(fs.readFileSync(falseFriendsBacklogPath, 'utf8')); | |
| const culturalEtiquette = JSON.parse(fs.readFileSync(culturalEtiquetteBacklogPath, 'utf8')); | |
| const exampleSentences = JSON.parse(fs.readFileSync(exampleSentencesBacklogPath, 'utf8')); | |
| const commonMistakes = JSON.parse(fs.readFileSync(commonMistakesBacklogPath, 'utf8')); | |
| const videoGameQuotes = JSON.parse(fs.readFileSync(videoGameQuotesBacklogPath, 'utf8')); | |
| const wallpaperUrls = JSON.parse(fs.readFileSync(wallpaperUrlsBacklogPath, 'utf8')); | |
| const communityNotes = JSON.parse(fs.readFileSync(communityNotesBacklogPath, 'utf8')); | |
| let needsCommit = false; | |
| const title = issue.title || ''; | |
| function reenableBacklogItem() { | |
| if (title.includes('Add New Color Theme:') || title.includes('Add Theme:')) { | |
| const themeMatch = title.match(/Add (?:New Color )?Theme:\s*(.+?)\s*(?:—|\(good)/i); | |
| if (themeMatch) { | |
| const themeName = themeMatch[1].trim(); | |
| const themeIndex = themes.findIndex(function(t) { return t.name === themeName; }); | |
| if (themeIndex !== -1) { | |
| themes[themeIndex].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled theme: ${themeName}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Japan Fact #') || title.includes('Add new Japan Fact #')) { | |
| const factIdMatch = title.match(/#(\d+)/); | |
| if (factIdMatch) { | |
| const factId = parseInt(factIdMatch[1]); | |
| const factIndex = facts.findIndex(function(f) { return f.id === factId; }); | |
| if (factIndex !== -1) { | |
| facts[factIndex].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled fact #${factId}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Japanese Proverb #') || title.includes('Add new Japanese Proverb #')) { | |
| const proverbIdMatch = title.match(/#(\d+)/); | |
| if (proverbIdMatch) { | |
| const proverbId = parseInt(proverbIdMatch[1]); | |
| const proverbIndex = proverbs.findIndex(function(p) { return p.id === proverbId; }); | |
| if (proverbIndex !== -1) { | |
| proverbs[proverbIndex].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled proverb #${proverbId}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Classic Japanese Haiku #') || title.includes('Add Japanese Haiku #') || title.includes('Add new Japanese Haiku #')) { | |
| const haikuIdMatch = title.match(/#(\d+)/); | |
| if (haikuIdMatch) { | |
| const haikuId = parseInt(haikuIdMatch[1]); | |
| const haikuIndex = haiku.findIndex(function(h) { return h.id === haikuId; }); | |
| if (haikuIndex !== -1) { | |
| haiku[haikuIndex].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled haiku #${haikuId}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add New Trivia Question #') || title.includes('Add Trivia Question #') || title.includes('Add new Trivia Question #')) { | |
| const triviaIdMatch = title.match(/#(\d+)/); | |
| if (triviaIdMatch) { | |
| const triviaId = parseInt(triviaIdMatch[1]); | |
| const triviaIndex = trivia.findIndex(function(q) { return q.id === triviaId; }); | |
| if (triviaIndex !== -1) { | |
| trivia[triviaIndex].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled trivia #${triviaId}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add New Grammar Point #') || title.includes('Add Grammar Point #') || title.includes('Add new Grammar Point #')) { | |
| const grammarIdMatch = title.match(/#(\d+)/); | |
| if (grammarIdMatch) { | |
| const grammarId = parseInt(grammarIdMatch[1]); | |
| const grammarIndex = grammar.findIndex(function(g) { return g.id === grammarId; }); | |
| if (grammarIndex !== -1) { | |
| grammar[grammarIndex].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled grammar #${grammarId}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Famous Anime Quote #') || title.includes('Add Anime Quote #') || title.includes('Add new Anime Quote #')) { | |
| const quoteIdMatch = title.match(/#(\d+)/); | |
| if (quoteIdMatch) { | |
| const quoteId = parseInt(quoteIdMatch[1]); | |
| const quoteIndex = animeQuotes.findIndex(function(q) { return q.id === quoteId; }); | |
| if (quoteIndex !== -1) { | |
| animeQuotes[quoteIndex].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled anime quote #${quoteId}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add New Japanese Idiom #') || title.includes('Add Japanese Idiom #') || title.includes('Add new Japanese Idiom #')) { | |
| const idMatch = title.match(/#(\d+)/); | |
| if (idMatch) { | |
| const id = parseInt(idMatch[1]); | |
| const idx = idioms.findIndex(function(i) { return i.id === id; }); | |
| if (idx !== -1) { | |
| idioms[idx].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled idiom #${id}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Regional Dialect Entry #') || title.includes('Add Dialect Entry #') || title.includes('Add new Dialect Entry #')) { | |
| const idMatch = title.match(/#(\d+)/); | |
| if (idMatch) { | |
| const id = parseInt(idMatch[1]); | |
| const idx = regionalDialects.findIndex(function(i) { return i.id === id; }); | |
| if (idx !== -1) { | |
| regionalDialects[idx].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled regional dialect #${id}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Japanese False Friend #') || title.includes('Add False Friend Pair #') || title.includes('Add new False Friend Pair #')) { | |
| const idMatch = title.match(/#(\d+)/); | |
| if (idMatch) { | |
| const id = parseInt(idMatch[1]); | |
| const idx = falseFriends.findIndex(function(i) { return i.id === id; }); | |
| if (idx !== -1) { | |
| falseFriends[idx].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled false friend #${id}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Japanese Cultural Etiquette Tip #') || title.includes('Add Etiquette Tip #') || title.includes('Add new Etiquette Tip #')) { | |
| const idMatch = title.match(/#(\d+)/); | |
| if (idMatch) { | |
| const id = parseInt(idMatch[1]); | |
| const idx = culturalEtiquette.findIndex(function(i) { return i.id === id; }); | |
| if (idx !== -1) { | |
| culturalEtiquette[idx].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled cultural etiquette #${id}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Japanese Example Sentence #') || title.includes('Add Example Sentence #') || title.includes('Add new Example Sentence #')) { | |
| const idMatch = title.match(/#(\d+)/); | |
| if (idMatch) { | |
| const id = parseInt(idMatch[1]); | |
| const idx = exampleSentences.findIndex(function(i) { return i.id === id; }); | |
| if (idx !== -1) { | |
| exampleSentences[idx].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled example sentence #${id}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Common Japanese Learner Mistake #') || title.includes('Add Learner Mistake #') || title.includes('Add new Learner Mistake #')) { | |
| const idMatch = title.match(/#(\d+)/); | |
| if (idMatch) { | |
| const id = parseInt(idMatch[1]); | |
| const idx = commonMistakes.findIndex(function(i) { return i.id === id; }); | |
| if (idx !== -1) { | |
| commonMistakes[idx].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled common mistake #${id}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Famous Japanese Video Game Quote #') || title.includes('Add Video Game Quote #') || title.includes('Add new Video Game Quote #')) { | |
| const idMatch = title.match(/#(\d+)/); | |
| if (idMatch) { | |
| const id = parseInt(idMatch[1]); | |
| const idx = videoGameQuotes.findIndex(function(i) { return i.id === id; }); | |
| if (idx !== -1) { | |
| videoGameQuotes[idx].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled video game quote #${id}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Wallpaper URL #') || title.includes('Add new Wallpaper URL #')) { | |
| const idMatch = title.match(/#(\d+)/); | |
| if (idMatch) { | |
| const id = parseInt(idMatch[1]); | |
| const idx = wallpaperUrls.findIndex(function(i) { return i.id === id; }); | |
| if (idx !== -1) { | |
| wallpaperUrls[idx].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled wallpaper URL #${id}`); | |
| } | |
| } | |
| return; | |
| } | |
| if (title.includes('Add Community Note Line #') || title.includes('Add new Community Note Line #')) { | |
| const idMatch = title.match(/#(\d+)/); | |
| if (idMatch) { | |
| const id = parseInt(idMatch[1]); | |
| const idx = communityNotes.findIndex(function(i) { return i.id === id; }); | |
| if (idx !== -1) { | |
| communityNotes[idx].issued = false; | |
| needsCommit = true; | |
| console.log(`Re-enabled community note #${id}`); | |
| } | |
| } | |
| } | |
| } | |
| reenableBacklogItem(); | |
| if (needsCommit) { | |
| fs.writeFileSync(themeBacklogPath, JSON.stringify(themes, null, 2)); | |
| fs.writeFileSync(factsBacklogPath, JSON.stringify(facts, null, 2)); | |
| fs.writeFileSync(proverbsBacklogPath, JSON.stringify(proverbs, null, 2)); | |
| fs.writeFileSync(haikuBacklogPath, JSON.stringify(haiku, null, 2)); | |
| fs.writeFileSync(triviaBacklogPath, JSON.stringify(trivia, null, 2)); | |
| fs.writeFileSync(grammarBacklogPath, JSON.stringify(grammar, null, 2)); | |
| fs.writeFileSync(animeQuotesBacklogPath, JSON.stringify(animeQuotes, null, 2)); | |
| fs.writeFileSync(idiomsBacklogPath, JSON.stringify(idioms, null, 2)); | |
| fs.writeFileSync(regionalDialectsBacklogPath, JSON.stringify(regionalDialects, null, 2)); | |
| fs.writeFileSync(falseFriendsBacklogPath, JSON.stringify(falseFriends, null, 2)); | |
| fs.writeFileSync(culturalEtiquetteBacklogPath, JSON.stringify(culturalEtiquette, null, 2)); | |
| fs.writeFileSync(exampleSentencesBacklogPath, JSON.stringify(exampleSentences, null, 2)); | |
| fs.writeFileSync(commonMistakesBacklogPath, JSON.stringify(commonMistakes, null, 2)); | |
| fs.writeFileSync(videoGameQuotesBacklogPath, JSON.stringify(videoGameQuotes, null, 2)); | |
| fs.writeFileSync(wallpaperUrlsBacklogPath, JSON.stringify(wallpaperUrls, null, 2)); | |
| fs.writeFileSync(communityNotesBacklogPath, JSON.stringify(communityNotes, null, 2)); | |
| core.setOutput('needs_commit', 'true'); | |
| return; | |
| } | |
| core.setOutput('needs_commit', 'false'); | |
| - name: Commit backlog updates directly to main | |
| if: steps.reenable.outputs.needs_commit == 'true' | |
| run: | | |
| git config user.name "てんとう虫" | |
| git config user.email "reservecrate@gmail.com" | |
| git add community/backlog/ | |
| if git diff --cached --quiet; then | |
| echo "No backlog changes to commit" | |
| exit 0 | |
| fi | |
| commit_msg="chore(automation): re-enable closed issue backlog" | |
| max_attempts=5 | |
| attempt=1 | |
| while [ "$attempt" -le "$max_attempts" ]; do | |
| git commit -m "$commit_msg" | |
| if git pull --rebase origin main && git push origin HEAD:main; then | |
| echo "Backlog push succeeded on attempt $attempt" | |
| exit 0 | |
| fi | |
| echo "Backlog push attempt $attempt failed; retrying..." | |
| git rebase --abort || true | |
| git fetch origin main | |
| git reset --hard origin/main | |
| git add community/backlog/ | |
| if git diff --cached --quiet; then | |
| echo "No backlog changes remain after sync" | |
| exit 0 | |
| fi | |
| attempt=$((attempt + 1)) | |
| done | |
| echo "Failed to push backlog updates after $max_attempts attempts" | |
| exit 1 |