From ca36690943b226836e54c8a50b992c3641a7e8f4 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Thu, 30 Oct 2025 16:42:23 -0400 Subject: [PATCH 1/5] feat(duplicates): edit existing issues --- create-node-meeting-artifacts.mjs | 21 ++--------- src/github.mjs | 63 +++++++++++++++++++++++++++++++ src/hackmd.mjs | 24 ++++++++++++ 3 files changed, 91 insertions(+), 17 deletions(-) diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs index 32e90e0..3c8dc3d 100644 --- a/create-node-meeting-artifacts.mjs +++ b/create-node-meeting-artifacts.mjs @@ -78,20 +78,6 @@ const meetingTitle = meetings.generateMeetingTitle( meetingDate ); -// Look for existing issues -if (!config.force) { - const existingIssue = await github.findIssueByTitle( - githubClient, - meetingTitle, - meetingConfig - ); - - if (existingIssue) { - console.log(`${existingIssue.html_url} already exists. Exiting.`); - process.exit(0); - } -} - // Step 9: Get agenda information using native implementation const gitHubAgendaIssues = await github.getAgendaIssues( githubClient, @@ -103,10 +89,10 @@ const gitHubAgendaIssues = await github.getAgendaIssues( const meetingAgenda = meetings.generateMeetingAgenda(gitHubAgendaIssues); // Step 11: Create HackMD document with meeting notes and tags -const hackmdNote = await hackmd.createMeetingNotesDocument( +const hackmdNote = await hackmd.getOrCreateNotesDocument( hackmdClient, meetingTitle, - '' + config ); // Step 12: Get the HackMD document link @@ -123,8 +109,9 @@ const issueContent = await meetings.generateMeetingIssue( ); // Step 14: Create GitHub issue with HackMD link -const githubIssue = await github.createGitHubIssue( +const githubIssue = await github.createOrUpdateMeetingIssue( githubClient, + config, meetingConfig, meetingTitle, issueContent diff --git a/src/github.mjs b/src/github.mjs index 5871cd0..d49281c 100644 --- a/src/github.mjs +++ b/src/github.mjs @@ -42,6 +42,46 @@ export const createGitHubIssue = async ( return response.data; }; +/** + * Creates or updates a GitHub issue with meeting information and Google Doc link + * @param {import('@octokit/rest').Octokit} githubClient - Authenticated GitHub API client + * @param {import('./types.d.ts').AppConfig} config - The application config + * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration object + * @param {string} title - Issue title + * @param {string} content - Issue content + * @returns {Promise} Created issue data + */ +export const createOrUpdateMeetingIssue = async ( + githubClient, + { force }, + meetingConfig, + title, + content +) => { + if (!force) { + const existingIssue = await findIssueByTitle( + githubClient, + title, + meetingConfig + ); + + if (existingIssue) { + // If content != body, update the issue + return ( + content === existingIssue.body || + updateMeetingIssue( + githubClient, + existingIssue.number, + content, + meetingConfig + ) + ); + } + } + + return createGitHubIssue(githubClient, meetingConfig, title, content); +}; + /** * Sorts issues by repository * @param {Array} issues The issues to sort @@ -55,6 +95,29 @@ export const sortIssuesByRepo = issues => return obj; }, {}); +/** + * Fetches GitHub issue from a repo with a given title + * @param {import('@octokit/rest').Octokit} githubClient - Authenticated GitHub API client + * @param {number} number - The issue number + * @param {string} content - The new content + * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration + */ +export const updateMeetingIssue = async ( + githubClient, + number, + content, + { properties } +) => { + const githubOrg = properties.USER ?? DEFAULT_CONFIG.githubOrg; + + return githubClient.issues.update({ + issue_number: number, + body: content, + owner: githubOrg, + repo: properties.REPO, + }); +}; + /** * Fetches GitHub issue from a repo with a given title * @param {import('@octokit/rest').Octokit} githubClient - Authenticated GitHub API client diff --git a/src/hackmd.mjs b/src/hackmd.mjs index 96096cf..5d744c5 100644 --- a/src/hackmd.mjs +++ b/src/hackmd.mjs @@ -40,6 +40,30 @@ export const createMeetingNotesDocument = (hackmdClient, title, content) => { .then(response => response?.note ?? response); }; +/** + * Creates a new meeting notes document in HackMD with appropriate tags + * @param {HackMDAPI} hackmdClient - HackMD API client + * @param {string} title - Document title + * @param {import('./types.d.ts').AppConfig} config - Configuration + * @returns {Promise} The created / fetched note + */ +export const getOrCreateNotesDocument = async ( + hackmdClient, + title, + { force } +) => { + if (!force) { + const notes = await hackmdClient.getNoteList(); + const existingNote = notes.find(note => note.title === title); + + if (existingNote) { + return existingNote; + } + } + + return createMeetingNotesDocument(hackmdClient, title, ''); +}; + /** * Updates an existing meeting notes document in HackMD with retry logic * @param {HackMDClient} hackmdClient - HackMD API client From e6e6e94155a5a5cd9a8090bc658237cb6adfe84f Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Thu, 30 Oct 2025 16:48:13 -0400 Subject: [PATCH 2/5] Update src/github.mjs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/github.mjs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/github.mjs b/src/github.mjs index d49281c..3b6fa35 100644 --- a/src/github.mjs +++ b/src/github.mjs @@ -67,15 +67,14 @@ export const createOrUpdateMeetingIssue = async ( if (existingIssue) { // If content != body, update the issue - return ( - content === existingIssue.body || - updateMeetingIssue( - githubClient, - existingIssue.number, - content, - meetingConfig - ) - ); + return content === existingIssue.body + ? existingIssue + : await updateMeetingIssue( + githubClient, + existingIssue.number, + content, + meetingConfig + ); } } From 7e4d2586ac15ec7a1c00262d9453b54597a4b0dd Mon Sep 17 00:00:00 2001 From: avivkeller Date: Thu, 30 Oct 2025 16:48:41 -0400 Subject: [PATCH 3/5] fixup! --- src/github.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github.mjs b/src/github.mjs index d49281c..89da040 100644 --- a/src/github.mjs +++ b/src/github.mjs @@ -96,7 +96,7 @@ export const sortIssuesByRepo = issues => }, {}); /** - * Fetches GitHub issue from a repo with a given title + * Updates an existing GitHub issue with new content * @param {import('@octokit/rest').Octokit} githubClient - Authenticated GitHub API client * @param {number} number - The issue number * @param {string} content - The new content From 465aa78abbb688ae406a49425b73929ecdf3a26b Mon Sep 17 00:00:00 2001 From: avivkeller Date: Thu, 30 Oct 2025 16:50:27 -0400 Subject: [PATCH 4/5] fixup! --- src/github.mjs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/github.mjs b/src/github.mjs index 787e2df..4a58e03 100644 --- a/src/github.mjs +++ b/src/github.mjs @@ -66,15 +66,16 @@ export const createOrUpdateMeetingIssue = async ( ); if (existingIssue) { - // If content != body, update the issue - return content === existingIssue.body - ? existingIssue - : await updateMeetingIssue( - githubClient, - existingIssue.number, - content, - meetingConfig - ); + if (content !== existingIssue.body) { + await updateMeetingIssue( + githubClient, + existingIssue.number, + content, + meetingConfig + ); + } + + return existingIssue; } } From 2c3ffec7a52f0a770833e96997dd0525fc4c3129 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sun, 2 Nov 2025 12:03:48 -0500 Subject: [PATCH 5/5] standard names --- create-node-meeting-artifacts.mjs | 4 ++-- src/github.mjs | 14 +++++++++----- src/hackmd.mjs | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs index 3c8dc3d..6abaa94 100644 --- a/create-node-meeting-artifacts.mjs +++ b/create-node-meeting-artifacts.mjs @@ -89,7 +89,7 @@ const gitHubAgendaIssues = await github.getAgendaIssues( const meetingAgenda = meetings.generateMeetingAgenda(gitHubAgendaIssues); // Step 11: Create HackMD document with meeting notes and tags -const hackmdNote = await hackmd.getOrCreateNotesDocument( +const hackmdNote = await hackmd.getOrCreateMeetingNotesDocument( hackmdClient, meetingTitle, config @@ -109,7 +109,7 @@ const issueContent = await meetings.generateMeetingIssue( ); // Step 14: Create GitHub issue with HackMD link -const githubIssue = await github.createOrUpdateMeetingIssue( +const githubIssue = await github.createOrUpdateGitHubIssue( githubClient, config, meetingConfig, diff --git a/src/github.mjs b/src/github.mjs index 4a58e03..ebb91ac 100644 --- a/src/github.mjs +++ b/src/github.mjs @@ -51,7 +51,7 @@ export const createGitHubIssue = async ( * @param {string} content - Issue content * @returns {Promise} Created issue data */ -export const createOrUpdateMeetingIssue = async ( +export const createOrUpdateGitHubIssue = async ( githubClient, { force }, meetingConfig, @@ -59,7 +59,7 @@ export const createOrUpdateMeetingIssue = async ( content ) => { if (!force) { - const existingIssue = await findIssueByTitle( + const existingIssue = await findGitHubIssueByTitle( githubClient, title, meetingConfig @@ -67,7 +67,7 @@ export const createOrUpdateMeetingIssue = async ( if (existingIssue) { if (content !== existingIssue.body) { - await updateMeetingIssue( + await updateGitHubIssue( githubClient, existingIssue.number, content, @@ -102,7 +102,7 @@ export const sortIssuesByRepo = issues => * @param {string} content - The new content * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration */ -export const updateMeetingIssue = async ( +export const updateGitHubIssue = async ( githubClient, number, content, @@ -124,7 +124,11 @@ export const updateMeetingIssue = async ( * @param {string} title - The title to find * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration */ -export const findIssueByTitle = async (githubClient, title, { properties }) => { +export const findGitHubIssueByTitle = async ( + githubClient, + title, + { properties } +) => { const githubOrg = properties.USER ?? DEFAULT_CONFIG.githubOrg; const issues = await githubClient.request('GET /search/issues', { diff --git a/src/hackmd.mjs b/src/hackmd.mjs index 5d744c5..917bb8c 100644 --- a/src/hackmd.mjs +++ b/src/hackmd.mjs @@ -47,7 +47,7 @@ export const createMeetingNotesDocument = (hackmdClient, title, content) => { * @param {import('./types.d.ts').AppConfig} config - Configuration * @returns {Promise} The created / fetched note */ -export const getOrCreateNotesDocument = async ( +export const getOrCreateMeetingNotesDocument = async ( hackmdClient, title, { force }