diff --git a/.github/workflows/create-meeting-artifacts-scheduled.yml b/.github/workflows/create-meeting-artifacts-scheduled.yml index 7b9e8df..f4a03f7 100644 --- a/.github/workflows/create-meeting-artifacts-scheduled.yml +++ b/.github/workflows/create-meeting-artifacts-scheduled.yml @@ -3,7 +3,7 @@ name: Create Meeting Artifacts (Scheduled) on: workflow_dispatch: schedule: - - cron: '0 10 * * 1' + - cron: '0 0 * * *' jobs: create-matrix: diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs index 26633f2..32e90e0 100644 --- a/create-node-meeting-artifacts.mjs +++ b/create-node-meeting-artifacts.mjs @@ -21,6 +21,7 @@ const program = new Command(); program .argument('', 'Meeting group') .option('--dry-run', 'Show output without creating/updating anything', false) + .option('--force', 'Create a new issue even if one already exists', false) .option('--verbose', 'Show debug output') .parse(process.argv); @@ -77,6 +78,20 @@ 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, diff --git a/src/calendar.mjs b/src/calendar.mjs index ed3a5ee..6278354 100644 --- a/src/calendar.mjs +++ b/src/calendar.mjs @@ -12,16 +12,13 @@ export const getEventsFromCalendar = async url => { }; /** - * @param {Date} now + * @param {Date} start */ -const getWeekBounds = (now = new Date()) => { - const startDate = now.getUTCDate() - now.getUTCDay(); - - const start = new Date(now.setUTCDate(startDate)); +const getWeekBounds = (start = new Date()) => { start.setUTCHours(0, 0, 0, 0); - const end = new Date(now.setUTCDate(startDate + 7)); - end.setUTCHours(0, 0, 0, 0); + const end = new Date(start); + end.setUTCDate(start.getUTCDate() + 7); return [start, end]; }; @@ -55,7 +52,7 @@ export const findNextMeetingDate = async (allEvents, { properties }) => { throw new Error( `No meeting found for ${properties.GROUP_NAME || 'this group'} ` + - `in the current week (${weekStart.toISOString().split('T')[0]} to ${weekEnd.toISOString().split('T')[0]}). ` + + `in the next week (${weekStart.toISOString().split('T')[0]} to ${weekEnd.toISOString().split('T')[0]}). ` + `This is expected for bi-weekly meetings or meetings that don't occur every week.` ); }; diff --git a/src/github.mjs b/src/github.mjs index 35d7fbf..5871cd0 100644 --- a/src/github.mjs +++ b/src/github.mjs @@ -55,6 +55,24 @@ 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 {string} title - The title to find + * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration + */ +export const findIssueByTitle = async (githubClient, title, { properties }) => { + const githubOrg = properties.USER ?? DEFAULT_CONFIG.githubOrg; + + const issues = await githubClient.request('GET /search/issues', { + q: `is:open in:title repo:"${githubOrg}/${properties.REPO}" "${title}"`, + advanced_search: true, + per_page: 1, + }); + + return issues.data.items[0]; +}; + /** * Fetches GitHub issues from all repositories in an organization with a specific label * @param {import('@octokit/rest').Octokit} githubClient - Authenticated GitHub API client diff --git a/src/types.d.ts b/src/types.d.ts index f31c002..6e95690 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -15,6 +15,7 @@ export interface EnvironmentConfig { */ export interface CLIConfig { verbose: boolean; + force: boolean; dryRun: boolean; meetingGroup: string; }