From 98294a7aa747e5973d910a912625895a0a6c3fe4 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sat, 11 Oct 2025 09:45:06 -0400 Subject: [PATCH 1/6] feat(cli): add support for dry-run, verbose --- .../create-meeting-artifacts-manual.yml | 7 +- .../create-meeting-artifacts-scheduled.yml | 2 +- create-node-meeting-artifacts.mjs | 69 ++++++++---- package-lock.json | 10 ++ package.json | 1 + src/config.mjs | 10 +- src/github.mjs | 4 +- src/types.d.ts | 15 ++- test/config.test.mjs | 103 ------------------ 9 files changed, 80 insertions(+), 141 deletions(-) delete mode 100644 test/config.test.mjs diff --git a/.github/workflows/create-meeting-artifacts-manual.yml b/.github/workflows/create-meeting-artifacts-manual.yml index d819e7a..21cc97a 100644 --- a/.github/workflows/create-meeting-artifacts-manual.yml +++ b/.github/workflows/create-meeting-artifacts-manual.yml @@ -31,6 +31,11 @@ on: - uvwasi - userfeedback - web-server-frameworks + dry_run: + type: boolean + description: 'Dry run?' + default: false + required: true jobs: create-artifacts: @@ -68,7 +73,7 @@ jobs: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} HACKMD_API_TOKEN: ${{ secrets.HACKMD_API_TOKEN }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} - run: node create-node-meeting-artifacts.mjs ${{ github.event.inputs.meeting_group }} + run: node create-node-meeting-artifacts.mjs ${{ github.event.inputs.meeting_group }} --verbose ${{ github.event.inputs.dry_run && '--dry-run' }} - name: Upload artifacts if: always() diff --git a/.github/workflows/create-meeting-artifacts-scheduled.yml b/.github/workflows/create-meeting-artifacts-scheduled.yml index 2cc8a0b..0f58830 100644 --- a/.github/workflows/create-meeting-artifacts-scheduled.yml +++ b/.github/workflows/create-meeting-artifacts-scheduled.yml @@ -68,7 +68,7 @@ jobs: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} HACKMD_API_TOKEN: ${{ secrets.HACKMD_API_TOKEN }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} - run: node create-node-meeting-artifacts.mjs ${{ matrix.meeting_group }} + run: node create-node-meeting-artifacts.mjs ${{ matrix.meeting_group }} --verbose - name: Upload artifacts if: always() diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs index 70a0bd1..f63dd47 100644 --- a/create-node-meeting-artifacts.mjs +++ b/create-node-meeting-artifacts.mjs @@ -9,14 +9,28 @@ * npm run dev -- tsc */ -import { getConfig } from './src/config.mjs'; +import { Command } from 'commander'; + +import environmentConfig from './src/config.mjs'; import * as github from './src/github.mjs'; import * as google from './src/google.mjs'; import * as hackmd from './src/hackmd.mjs'; import * as meetings from './src/meeting.mjs'; +const program = new Command(); +program + .argument('', 'Meeting group') + .option('--dry-run', 'Show output without creating/updating anything', false) + .option('--verbose', 'Show debug output') + .parse(process.argv); + // Step 1: Application configuration -const config = getConfig(); +/** @type {import('./src/types').AppConfig} */ +const config = { + ...environmentConfig, + ...program.opts(), + meetingGroup: program.args[0], +}; // Step 2: Initialize Google Calendar client with API Key const calendarClient = google.createCalendarClient(config.google); @@ -28,7 +42,8 @@ const githubClient = github.createGitHubClient(config); const meetingConfig = await meetings.readMeetingConfig(config); // Step 5: Initialize HackMD client with meeting configuration -const hackmdClient = hackmd.createHackMDClient(config, meetingConfig); +const hackmdClient = + config.dryRun || hackmd.createHackMDClient(config, meetingConfig); // Step 6: Find next meeting event in calendar const event = await google.findNextMeetingEvent(calendarClient, meetingConfig); @@ -54,11 +69,9 @@ 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( - hackmdClient, - meetingTitle, - '' -); +const hackmdNote = config.dryRun + ? {} + : await hackmd.createMeetingNotesDocument(hackmdClient, meetingTitle, ''); // Step 12: Get the HackMD document link const minutesDocLink = @@ -74,12 +87,14 @@ const issueContent = await meetings.generateMeetingIssue( ); // Step 14: Create GitHub issue with HackMD link -const githubIssue = await github.createGitHubIssue( - githubClient, - meetingConfig, - meetingTitle, - issueContent -); +const githubIssue = config.dryRun + ? {} + : await github.createGitHubIssue( + githubClient, + meetingConfig, + meetingTitle, + issueContent + ); // Step 15: Update the minutes content with the HackMD link const minutesContent = await meetings.generateMeetingMinutes( @@ -91,13 +106,19 @@ const minutesContent = await meetings.generateMeetingMinutes( githubIssue.html_url ); -// Step 16: Update the HackMD document with the self-referencing link -await hackmd.updateMeetingNotesDocument( - hackmdClient, - hackmdNote.id, - minutesContent -); - -// Output success information with links -console.log(`Created GitHub issue: ${githubIssue.html_url}`); -console.log(`Created HackMD document: ${minutesDocLink}`); +if (config.dryRun) { + console.log('Would create GitHub issue with the following content:'); + console.log(issueContent); + console.log('Would create HackMD note with the following content:'); + console.log(minutesContent); +} else { + // Step 16: Update the HackMD document with the self-referencing link + await hackmd.updateMeetingNotesDocument( + hackmdClient, + hackmdNote.id, + minutesContent + ); + + console.log(`Created GitHub issue: ${githubIssue.html_url}`); + console.log(`Created HackMD document: ${minutesDocLink}`); +} diff --git a/package-lock.json b/package-lock.json index e6462f1..45b26ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@googleapis/calendar": "^11.0.1", "@hackmd/api": "^2.5.0", "@octokit/rest": "^22.0.0", + "commander": "^14.0.1", "dedent": "^1.6.0", "dotenv": "^17.2.2" }, @@ -1120,6 +1121,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", + "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/comment-parser": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", diff --git a/package.json b/package.json index e5dc8fd..322b9d3 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@googleapis/calendar": "^11.0.1", "@hackmd/api": "^2.5.0", "@octokit/rest": "^22.0.0", + "commander": "^14.0.1", "dedent": "^1.6.0", "dotenv": "^17.2.2" }, diff --git a/src/config.mjs b/src/config.mjs index 38ad883..64561bc 100644 --- a/src/config.mjs +++ b/src/config.mjs @@ -4,13 +4,9 @@ import { join, dirname } from 'node:path'; const defaultMeetingsDirectory = join(homedir(), '.make-node-meeting'); /** - * Gets the application configuration from environment variables and arguments - * @returns {import('./types.d.ts').AppConfig} Application configuration object + * @type {import('./types.d.ts').EnvironmentConfig} Environment configuration object */ -export const getConfig = () => ({ - // Meeting group from command line argument, defaults to 'tsc' - meetingGroup: process.argv[2], - +export default { // GitHub personal access token from environment githubToken: process.env.GITHUB_TOKEN, @@ -32,4 +28,4 @@ export const getConfig = () => ({ output: process.env.MEETINGS_OUTPUT_DIR || defaultMeetingsDirectory, templates: join(dirname(import.meta.dirname), 'templates'), }, -}); +}; diff --git a/src/github.mjs b/src/github.mjs index ec828b9..35d7fbf 100644 --- a/src/github.mjs +++ b/src/github.mjs @@ -7,8 +7,8 @@ import { DEFAULT_CONFIG } from './constants.mjs'; * @param {import('./types.d.ts').AppConfig} config - Application configuration * @returns {import('@octokit/rest').Octokit} Configured GitHub API client */ -export const createGitHubClient = ({ githubToken: auth }) => - new Octokit({ auth }); +export const createGitHubClient = ({ githubToken: auth, verbose }) => + new Octokit({ auth, log: verbose ? console : undefined }); /** * Creates GitHub issue with meeting information and Google Doc link diff --git a/src/types.d.ts b/src/types.d.ts index e7a2d94..95d15cf 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,9 +1,7 @@ /** * Application configuration object */ -export interface AppConfig { - /** Meeting group from command line argument */ - meetingGroup: string; +export interface EnvironmentConfig { /** GitHub personal access token */ githubToken: string; /** Google API configuration (Calendar only) */ @@ -14,6 +12,17 @@ export interface AppConfig { directories: DirectoryConfig; } +/** + * CLI configuration object + */ +export interface CLIConfig { + verbose: boolean; + dryRun: boolean; + meetingGroup: string; +} + +export type AppConfig = EnvironmentConfig & CLIConfig; + /** * Google authentication configuration (Calendar only) */ diff --git a/test/config.test.mjs b/test/config.test.mjs deleted file mode 100644 index 6dc92a3..0000000 --- a/test/config.test.mjs +++ /dev/null @@ -1,103 +0,0 @@ -import assert from 'node:assert'; -import process from 'node:process'; -import { describe, it, beforeEach, afterEach } from 'node:test'; - -import { getConfig } from '../src/config.mjs'; - -describe('Config', () => { - let originalEnv; - let originalArgv; - - beforeEach(() => { - // Save original environment and argv - originalEnv = { ...process.env }; - originalArgv = [...process.argv]; - }); - - afterEach(() => { - // Restore original environment and argv - process.env = originalEnv; - process.argv = originalArgv; - }); - - describe('getConfig', () => { - it('should return empty meeting group when no argument provided', () => { - process.argv = ['node', 'script.mjs']; - - const config = getConfig(); - - assert.strictEqual(config.meetingGroup, undefined); - }); - - it('should use command line argument for meeting group', () => { - process.argv = ['node', 'script.mjs', 'build']; - - const config = getConfig(); - - assert.strictEqual(config.meetingGroup, 'build'); - }); - - it('should read GitHub token from environment', () => { - process.env.GITHUB_TOKEN = 'test_token'; - - const config = getConfig(); - - assert.strictEqual(config.githubToken, 'test_token'); - }); - - it('should read Google API Key config from environment', () => { - process.env.GOOGLE_API_KEY = 'test_google_api_key_123'; - - const config = getConfig(); - - assert.strictEqual(config.google.apiKey, 'test_google_api_key_123'); - }); - - it('should handle missing Google API Key gracefully', () => { - delete process.env.GOOGLE_API_KEY; - - const config = getConfig(); - - assert.strictEqual(config.google.apiKey, undefined); - }); - - it('should read HackMD config from environment', () => { - process.env.HACKMD_API_TOKEN = 'hackmd_token'; - - const config = getConfig(); - - assert.strictEqual(config.hackmd.apiToken, 'hackmd_token'); - }); - - it('should handle undefined environment variables gracefully', () => { - // Clear all relevant env vars - delete process.env.GITHUB_TOKEN; - delete process.env.HACKMD_API_TOKEN; - delete process.env.GOOGLE_CLIENT_ID; - - const config = getConfig(); - - assert.strictEqual(config.githubToken, undefined); - assert.strictEqual(config.hackmd.apiToken, undefined); - assert.strictEqual(config.google.clientId, undefined); - }); - - it('should contain all required config sections', () => { - const config = getConfig(); - - assert.ok('meetingGroup' in config); - assert.ok('githubToken' in config); - assert.ok('google' in config); - assert.ok('hackmd' in config); - assert.ok('directories' in config); - }); - - it('should contain all required directory paths', () => { - const config = getConfig(); - - assert.ok('config' in config.directories); - assert.ok('output' in config.directories); - assert.ok('templates' in config.directories); - }); - }); -}); From 433dffa94c61f180dbc9c890e8edb8cd4d746073 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sun, 12 Oct 2025 14:25:25 -0400 Subject: [PATCH 2/6] fixup! --- create-node-meeting-artifacts.mjs | 73 +++++++++++++++++++------------ 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs index f63dd47..d0066a8 100644 --- a/create-node-meeting-artifacts.mjs +++ b/create-node-meeting-artifacts.mjs @@ -42,8 +42,31 @@ const githubClient = github.createGitHubClient(config); const meetingConfig = await meetings.readMeetingConfig(config); // Step 5: Initialize HackMD client with meeting configuration -const hackmdClient = - config.dryRun || hackmd.createHackMDClient(config, meetingConfig); +const hackmdClient = hackmd.createHackMDClient(config, meetingConfig); + +if (config.dryRun) { + const meetingDate = new Date(); + + const gitHubAgendaIssues = await github.getAgendaIssues( + githubClient, + config, + meetingConfig + ); + + const meetingAgenda = meetings.generateMeetingAgenda(gitHubAgendaIssues); + + const issueContent = await meetings.generateMeetingIssue( + config, + meetingConfig, + meetingDate, + meetingAgenda, + '' + ); + + console.log(issueContent); + + process.exit(0); +} // Step 6: Find next meeting event in calendar const event = await google.findNextMeetingEvent(calendarClient, meetingConfig); @@ -69,9 +92,11 @@ const gitHubAgendaIssues = await github.getAgendaIssues( const meetingAgenda = meetings.generateMeetingAgenda(gitHubAgendaIssues); // Step 11: Create HackMD document with meeting notes and tags -const hackmdNote = config.dryRun - ? {} - : await hackmd.createMeetingNotesDocument(hackmdClient, meetingTitle, ''); +const hackmdNote = await hackmd.createMeetingNotesDocument( + hackmdClient, + meetingTitle, + '' +); // Step 12: Get the HackMD document link const minutesDocLink = @@ -87,14 +112,12 @@ const issueContent = await meetings.generateMeetingIssue( ); // Step 14: Create GitHub issue with HackMD link -const githubIssue = config.dryRun - ? {} - : await github.createGitHubIssue( - githubClient, - meetingConfig, - meetingTitle, - issueContent - ); +const githubIssue = await github.createGitHubIssue( + githubClient, + meetingConfig, + meetingTitle, + issueContent +); // Step 15: Update the minutes content with the HackMD link const minutesContent = await meetings.generateMeetingMinutes( @@ -106,19 +129,13 @@ const minutesContent = await meetings.generateMeetingMinutes( githubIssue.html_url ); -if (config.dryRun) { - console.log('Would create GitHub issue with the following content:'); - console.log(issueContent); - console.log('Would create HackMD note with the following content:'); - console.log(minutesContent); -} else { - // Step 16: Update the HackMD document with the self-referencing link - await hackmd.updateMeetingNotesDocument( - hackmdClient, - hackmdNote.id, - minutesContent - ); +// Step 16: Update the HackMD document with the self-referencing link +await hackmd.updateMeetingNotesDocument( + hackmdClient, + hackmdNote.id, + minutesContent +); - console.log(`Created GitHub issue: ${githubIssue.html_url}`); - console.log(`Created HackMD document: ${minutesDocLink}`); -} +// Step 16: Update the HackMD document with the self-referencing link +console.log(`Created GitHub issue: ${githubIssue.html_url}`); +console.log(`Created HackMD document: ${minutesDocLink}`); From 9a5d0d0b028746a2e10969380077717504985a7c Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sun, 12 Oct 2025 14:26:33 -0400 Subject: [PATCH 3/6] fixup! --- create-node-meeting-artifacts.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs index d0066a8..95646c8 100644 --- a/create-node-meeting-artifacts.mjs +++ b/create-node-meeting-artifacts.mjs @@ -136,6 +136,6 @@ await hackmd.updateMeetingNotesDocument( minutesContent ); -// Step 16: Update the HackMD document with the self-referencing link +// Output success information with links console.log(`Created GitHub issue: ${githubIssue.html_url}`); console.log(`Created HackMD document: ${minutesDocLink}`); From e64316cdc5fc3b88af0eb1a9122a043d547fa17f Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Sun, 12 Oct 2025 14:30:16 -0400 Subject: [PATCH 4/6] Update .github/workflows/create-meeting-artifacts-manual.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/create-meeting-artifacts-manual.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create-meeting-artifacts-manual.yml b/.github/workflows/create-meeting-artifacts-manual.yml index 21cc97a..7a11e2e 100644 --- a/.github/workflows/create-meeting-artifacts-manual.yml +++ b/.github/workflows/create-meeting-artifacts-manual.yml @@ -73,7 +73,7 @@ jobs: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} HACKMD_API_TOKEN: ${{ secrets.HACKMD_API_TOKEN }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} - run: node create-node-meeting-artifacts.mjs ${{ github.event.inputs.meeting_group }} --verbose ${{ github.event.inputs.dry_run && '--dry-run' }} + run: node create-node-meeting-artifacts.mjs ${{ github.event.inputs.meeting_group }} --verbose ${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }} - name: Upload artifacts if: always() From 5ff8f4db25d19e1c9690aa7c0a475aa6585dba6f Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Sun, 12 Oct 2025 14:32:52 -0400 Subject: [PATCH 5/6] Update src/config.mjs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.mjs b/src/config.mjs index 64561bc..df3e2f5 100644 --- a/src/config.mjs +++ b/src/config.mjs @@ -4,7 +4,7 @@ import { join, dirname } from 'node:path'; const defaultMeetingsDirectory = join(homedir(), '.make-node-meeting'); /** - * @type {import('./types.d.ts').EnvironmentConfig} Environment configuration object + * @type {import('./types.d.ts').AppConfig} Environment configuration object */ export default { // GitHub personal access token from environment From 2665126a6581cd05103069b09302e49c93a1822d Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sun, 12 Oct 2025 15:02:58 -0400 Subject: [PATCH 6/6] inline --- create-node-meeting-artifacts.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs index 95646c8..94e30a1 100644 --- a/create-node-meeting-artifacts.mjs +++ b/create-node-meeting-artifacts.mjs @@ -45,8 +45,6 @@ const meetingConfig = await meetings.readMeetingConfig(config); const hackmdClient = hackmd.createHackMDClient(config, meetingConfig); if (config.dryRun) { - const meetingDate = new Date(); - const gitHubAgendaIssues = await github.getAgendaIssues( githubClient, config, @@ -58,7 +56,7 @@ if (config.dryRun) { const issueContent = await meetings.generateMeetingIssue( config, meetingConfig, - meetingDate, + new Date(), meetingAgenda, '' );