From c750b0fac46cfe366dee037bbb915aa7bfc1ce73 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Tue, 16 Jun 2026 16:59:21 -0500 Subject: [PATCH] chore: move to JSON --- .env.example | 4 +- .../create-meeting-artifacts-manual.yml | 3 +- .../create-meeting-artifacts-scheduled.yml | 2 +- README.md | 169 ++-- TEMPLATES_DOCUMENTATION.md | 348 ++++----- create-node-meeting-artifacts.mjs | 145 ++-- eslint.config.mjs | 7 + global.d.ts | 17 - meetings/benchmarking.meeting.json | 20 + meetings/build.meeting.json | 20 + meetings/bundler_collab.meeting.json | 24 + meetings/cross_project_council.meeting.json | 72 ++ meetings/diag.meeting.json | 20 + meetings/diag_deepdive.meeting.json | 20 + meetings/ecosystem_report.meeting.json | 20 + meetings/loaders.meeting.json | 20 + meetings/modules.meeting.json | 20 + meetings/next-10.meeting.json | 20 + meetings/outreach.meeting.json | 22 + meetings/package-maintenance.meeting.json | 27 + .../package_metadata_interop.meeting.json | 22 + meetings/release.meeting.json | 19 + meetings/security-wg.meeting.json | 29 + meetings/security_collab.meeting.json | 27 + meetings/standards.meeting.json | 25 + meetings/sustainability_collab.meeting.json | 22 + meetings/test_runner.meeting.json | 19 + meetings/tooling.meeting.json | 20 + meetings/tsc.meeting.json | 69 ++ meetings/typescript.meeting.json | 20 + meetings/userfeedback.meeting.json | 21 + meetings/uvwasi.meeting.json | 20 + meetings/web-server-frameworks.meeting.json | 20 + meetings/web.meeting.json | 20 + package-lock.json | 72 +- package.json | 7 +- src/calendar.mjs | 6 +- src/config.mjs | 12 - src/constants.mjs | 20 +- src/github.mjs | 119 ++- src/hackmd.mjs | 22 +- src/meeting.mjs | 252 +++--- src/types.d.ts | 132 +++- src/utils/templates.mjs | 23 - templates/invited_Release | 2 - templates/invited_benchmarking | 1 - templates/invited_build | 1 - templates/invited_bundler_collab | 3 - templates/invited_cross_project_council | 3 - templates/invited_diag | 1 - templates/invited_diag_deepdive | 1 - templates/invited_ecosystem_report | 1 - templates/invited_loaders | 1 - templates/invited_modules | 1 - templates/invited_next-10 | 1 - templates/invited_outreach | 1 - templates/invited_package-maintenance | 1 - templates/invited_package_metadata_interop | 1 - templates/invited_security-wg | 1 - templates/invited_security_collab | 5 - templates/invited_standards | 3 - templates/invited_sustainability_collab | 1 - templates/invited_test_runner | 1 - templates/invited_tooling | 1 - templates/invited_tsc | 27 - templates/invited_typescript | 1 - templates/invited_userfeedback | 1 - templates/invited_uvwasi | 1 - templates/invited_web | 1 - templates/invited_web-server-frameworks | 1 - templates/meeting.mustache | 114 +++ templates/meeting_base_Release | 19 - templates/meeting_base_benchmarking | 11 - templates/meeting_base_build | 24 - templates/meeting_base_bundler_collab | 23 - templates/meeting_base_cross_project_council | 26 - templates/meeting_base_diag | 12 - templates/meeting_base_diag_deepdive | 11 - templates/meeting_base_ecosystem_report | 23 - templates/meeting_base_loaders | 9 - templates/meeting_base_modules | 9 - templates/meeting_base_next-10 | 12 - templates/meeting_base_outreach | 11 - templates/meeting_base_package-maintenance | 11 - .../meeting_base_package_metadata_interop | 25 - templates/meeting_base_security-wg | 11 - templates/meeting_base_security_collab | 25 - templates/meeting_base_standards | 25 - templates/meeting_base_sustainability_collab | 23 - templates/meeting_base_test_runner | 19 - templates/meeting_base_tooling | 11 - templates/meeting_base_tsc | 28 - templates/meeting_base_typescript | 23 - templates/meeting_base_userfeedback | 16 - templates/meeting_base_uvwasi | 12 - templates/meeting_base_web | 22 - templates/meeting_base_web-server-frameworks | 12 - templates/meeting_issue.md | 38 - templates/minutes_base_Release | 28 - templates/minutes_base_benchmarking | 28 - templates/minutes_base_build | 28 - templates/minutes_base_bundler_collab | 27 - templates/minutes_base_cross_project_council | 62 -- templates/minutes_base_diag | 28 - templates/minutes_base_diag_deepdive | 28 - templates/minutes_base_ecosystem_report | 24 - templates/minutes_base_loaders | 18 - templates/minutes_base_modules | 19 - templates/minutes_base_next-10 | 27 - templates/minutes_base_outreach | 29 - templates/minutes_base_package-maintenance | 32 - .../minutes_base_package_metadata_interop | 26 - templates/minutes_base_security-wg | 31 - templates/minutes_base_security_collab | 27 - templates/minutes_base_standards | 27 - templates/minutes_base_sustainability_collab | 24 - templates/minutes_base_tooling | 28 - templates/minutes_base_tsc | 34 - templates/minutes_base_typescript | 27 - templates/minutes_base_userfeedback | 26 - templates/minutes_base_uvwasi | 27 - templates/minutes_base_web | 27 - templates/minutes_base_web-server-frameworks | 27 - templates/observers_Release | 0 templates/observers_benchmarking | 0 templates/observers_build | 0 templates/observers_bundler_collab | 0 templates/observers_cross_project_council | 0 templates/observers_diag | 0 templates/observers_diag_deepdive | 0 templates/observers_ecosystem_report | 0 templates/observers_loaders | 0 templates/observers_modules | 0 templates/observers_next-10 | 0 templates/observers_outreach | 3 - templates/observers_package-maintenance | 0 templates/observers_package_metadata_interop | 1 - templates/observers_security-wg | 0 templates/observers_security_collab | 0 templates/observers_standards | 0 templates/observers_sustainability_collab | 0 templates/observers_test_runner | 0 templates/observers_tooling | 0 templates/observers_tsc | 0 templates/observers_typescript | 0 templates/observers_userfeedback | 0 templates/observers_uvwasi | 0 templates/observers_web | 0 templates/observers_web-server-frameworks | 0 test/calendar.test.mjs | 4 +- test/github.test.mjs | 158 ++-- test/helpers.mjs | 33 +- test/meeting.test.mjs | 737 +++++++++++------- test/utils/templates.test.mjs | 227 ------ 154 files changed, 1899 insertions(+), 2659 deletions(-) delete mode 100644 global.d.ts create mode 100644 meetings/benchmarking.meeting.json create mode 100644 meetings/build.meeting.json create mode 100644 meetings/bundler_collab.meeting.json create mode 100644 meetings/cross_project_council.meeting.json create mode 100644 meetings/diag.meeting.json create mode 100644 meetings/diag_deepdive.meeting.json create mode 100644 meetings/ecosystem_report.meeting.json create mode 100644 meetings/loaders.meeting.json create mode 100644 meetings/modules.meeting.json create mode 100644 meetings/next-10.meeting.json create mode 100644 meetings/outreach.meeting.json create mode 100644 meetings/package-maintenance.meeting.json create mode 100644 meetings/package_metadata_interop.meeting.json create mode 100644 meetings/release.meeting.json create mode 100644 meetings/security-wg.meeting.json create mode 100644 meetings/security_collab.meeting.json create mode 100644 meetings/standards.meeting.json create mode 100644 meetings/sustainability_collab.meeting.json create mode 100644 meetings/test_runner.meeting.json create mode 100644 meetings/tooling.meeting.json create mode 100644 meetings/tsc.meeting.json create mode 100644 meetings/typescript.meeting.json create mode 100644 meetings/userfeedback.meeting.json create mode 100644 meetings/uvwasi.meeting.json create mode 100644 meetings/web-server-frameworks.meeting.json create mode 100644 meetings/web.meeting.json delete mode 100644 src/utils/templates.mjs delete mode 100644 templates/invited_Release delete mode 100644 templates/invited_benchmarking delete mode 100644 templates/invited_build delete mode 100644 templates/invited_bundler_collab delete mode 100644 templates/invited_cross_project_council delete mode 100644 templates/invited_diag delete mode 100644 templates/invited_diag_deepdive delete mode 100644 templates/invited_ecosystem_report delete mode 100644 templates/invited_loaders delete mode 100644 templates/invited_modules delete mode 100644 templates/invited_next-10 delete mode 100644 templates/invited_outreach delete mode 100644 templates/invited_package-maintenance delete mode 100644 templates/invited_package_metadata_interop delete mode 100644 templates/invited_security-wg delete mode 100644 templates/invited_security_collab delete mode 100644 templates/invited_standards delete mode 100644 templates/invited_sustainability_collab delete mode 100644 templates/invited_test_runner delete mode 100644 templates/invited_tooling delete mode 100644 templates/invited_tsc delete mode 100644 templates/invited_typescript delete mode 100644 templates/invited_userfeedback delete mode 100644 templates/invited_uvwasi delete mode 100644 templates/invited_web delete mode 100644 templates/invited_web-server-frameworks create mode 100644 templates/meeting.mustache delete mode 100644 templates/meeting_base_Release delete mode 100644 templates/meeting_base_benchmarking delete mode 100644 templates/meeting_base_build delete mode 100644 templates/meeting_base_bundler_collab delete mode 100644 templates/meeting_base_cross_project_council delete mode 100644 templates/meeting_base_diag delete mode 100644 templates/meeting_base_diag_deepdive delete mode 100644 templates/meeting_base_ecosystem_report delete mode 100644 templates/meeting_base_loaders delete mode 100644 templates/meeting_base_modules delete mode 100644 templates/meeting_base_next-10 delete mode 100644 templates/meeting_base_outreach delete mode 100644 templates/meeting_base_package-maintenance delete mode 100644 templates/meeting_base_package_metadata_interop delete mode 100644 templates/meeting_base_security-wg delete mode 100644 templates/meeting_base_security_collab delete mode 100644 templates/meeting_base_standards delete mode 100644 templates/meeting_base_sustainability_collab delete mode 100644 templates/meeting_base_test_runner delete mode 100644 templates/meeting_base_tooling delete mode 100644 templates/meeting_base_tsc delete mode 100644 templates/meeting_base_typescript delete mode 100644 templates/meeting_base_userfeedback delete mode 100644 templates/meeting_base_uvwasi delete mode 100644 templates/meeting_base_web delete mode 100644 templates/meeting_base_web-server-frameworks delete mode 100644 templates/meeting_issue.md delete mode 100644 templates/minutes_base_Release delete mode 100644 templates/minutes_base_benchmarking delete mode 100644 templates/minutes_base_build delete mode 100644 templates/minutes_base_bundler_collab delete mode 100644 templates/minutes_base_cross_project_council delete mode 100644 templates/minutes_base_diag delete mode 100644 templates/minutes_base_diag_deepdive delete mode 100644 templates/minutes_base_ecosystem_report delete mode 100644 templates/minutes_base_loaders delete mode 100644 templates/minutes_base_modules delete mode 100644 templates/minutes_base_next-10 delete mode 100644 templates/minutes_base_outreach delete mode 100644 templates/minutes_base_package-maintenance delete mode 100644 templates/minutes_base_package_metadata_interop delete mode 100644 templates/minutes_base_security-wg delete mode 100644 templates/minutes_base_security_collab delete mode 100644 templates/minutes_base_standards delete mode 100644 templates/minutes_base_sustainability_collab delete mode 100644 templates/minutes_base_tooling delete mode 100644 templates/minutes_base_tsc delete mode 100644 templates/minutes_base_typescript delete mode 100644 templates/minutes_base_userfeedback delete mode 100644 templates/minutes_base_uvwasi delete mode 100644 templates/minutes_base_web delete mode 100644 templates/minutes_base_web-server-frameworks delete mode 100644 templates/observers_Release delete mode 100644 templates/observers_benchmarking delete mode 100644 templates/observers_build delete mode 100644 templates/observers_bundler_collab delete mode 100644 templates/observers_cross_project_council delete mode 100644 templates/observers_diag delete mode 100644 templates/observers_diag_deepdive delete mode 100644 templates/observers_ecosystem_report delete mode 100644 templates/observers_loaders delete mode 100644 templates/observers_modules delete mode 100644 templates/observers_next-10 delete mode 100644 templates/observers_outreach delete mode 100644 templates/observers_package-maintenance delete mode 100644 templates/observers_package_metadata_interop delete mode 100644 templates/observers_security-wg delete mode 100644 templates/observers_security_collab delete mode 100644 templates/observers_standards delete mode 100644 templates/observers_sustainability_collab delete mode 100644 templates/observers_test_runner delete mode 100644 templates/observers_tooling delete mode 100644 templates/observers_tsc delete mode 100644 templates/observers_typescript delete mode 100644 templates/observers_userfeedback delete mode 100644 templates/observers_uvwasi delete mode 100644 templates/observers_web delete mode 100644 templates/observers_web-server-frameworks delete mode 100644 test/utils/templates.test.mjs diff --git a/.env.example b/.env.example index 27a918b..e0c37a5 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ # Required: GitHub Token (Needs "repo" permissions) # GITHUB_TOKEN=your_personal_access_token_or_org_token -# Required: HackMD Configuration +# Required: HackMD API token (the HackMD team is configured per meeting in +# meetings/.meeting.json) # HACKMD_API_TOKEN=your_hackmd_api_token -# HACKMD_TEAM_NAME=your_hackmd_team_name_optional # Defaults to your own personal space \ No newline at end of file diff --git a/.github/workflows/create-meeting-artifacts-manual.yml b/.github/workflows/create-meeting-artifacts-manual.yml index 9219993..9c2be79 100644 --- a/.github/workflows/create-meeting-artifacts-manual.yml +++ b/.github/workflows/create-meeting-artifacts-manual.yml @@ -42,7 +42,7 @@ jobs: id: read-vars run: | meeting_group="${{ inputs.meeting_group }}" - user=$(grep '^USER=' "templates/meeting_base_${meeting_group}" | cut -d'=' -f2 | xargs) + user=$(jq -r '.github.owner' "meetings/${meeting_group}.meeting.json") echo "user=$user" >> $GITHUB_OUTPUT - name: Create GitHub App Token @@ -66,7 +66,6 @@ jobs: with: name: meeting-artifacts-${{ inputs.meeting_group || 'tsc' }} path: | - ~/.make-node-meeting/ *.md retention-days: 7 if-no-files-found: ignore diff --git a/.github/workflows/create-meeting-artifacts-scheduled.yml b/.github/workflows/create-meeting-artifacts-scheduled.yml index f4a03f7..0e6f296 100644 --- a/.github/workflows/create-meeting-artifacts-scheduled.yml +++ b/.github/workflows/create-meeting-artifacts-scheduled.yml @@ -17,7 +17,7 @@ jobs: - name: Get config basenames id: set-matrix run: | - CONFIGS=$(find templates/ -maxdepth 1 -type f -name 'meeting_base_*' | jq -R -s -c 'split("\n")[:-1] | map(sub(".*meeting_base_";""))') + CONFIGS=$(find meetings/ -maxdepth 1 -type f -name '*.meeting.json' | jq -R -s -c 'split("\n")[:-1] | map(sub(".*/"; "") | sub("\\.meeting\\.json$"; ""))') echo "configs=$CONFIGS" >> $GITHUB_OUTPUT create-artifacts: diff --git a/README.md b/README.md index 745cf4d..356416e 100644 --- a/README.md +++ b/README.md @@ -29,116 +29,77 @@ A modern Node.js application that creates GitHub issues and HackMD documents for ``` create-node-meeting-artifacts/ ├── src/ -│ ├── config.mjs # Configuration management +│ ├── config.mjs # Environment configuration (API tokens) │ ├── constants.mjs # Application constants │ ├── github.mjs # GitHub API integration │ ├── calendar.mjs # Calendar integration -│ ├── meeting.mjs # Meeting operations -│ └── utils.mjs # Utility functions -├── templates/ # Meeting templates +│ ├── hackmd.mjs # HackMD API integration +│ ├── meeting.mjs # Config loading + template rendering +│ ├── types.d.ts # Type definitions +│ └── utils/ # Date and URL helpers +├── meetings/ # One .meeting.json per meeting group +├── templates/ +│ └── meeting.mustache # Single shared template (issue + minutes) ├── .nvmrc # Node.js version -├── .env.example # Environment variables example +├── .env.example # Environment variables example ├── create-node-meeting-artifacts.mjs # Main application -├── TEMPLATES_DOCUMENTATION.md # Template creation guide +├── TEMPLATES_DOCUMENTATION.md # Meeting config reference └── README.md # This file ``` -## 📝 Meeting Templates +## 📝 Meeting Configurations -Meeting configurations are stored in the `templates/` directory. Each meeting group requires four template files: +Every meeting group is described by a single JSON file in the [`meetings/`](./meetings) +directory, named `.meeting.json` (for example, `tsc.meeting.json`). Each +file follows an **identical format**, and the same shared +[`templates/meeting.mustache`](./templates/meeting.mustache) renders both the +GitHub issue and the HackMD minutes for every group — so every meeting's +artifacts look the same. -- `invited_`: List of invited attendees (GitHub team mentions) -- `observers_`: List of observers with their details -- `meeting_base_`: Base meeting configuration (calendar ID, GitHub repo, etc.) -- `minutes_base_`: Template for meeting minutes document - -For detailed information about creating new templates, see [TEMPLATES_DOCUMENTATION.md](./TEMPLATES_DOCUMENTATION.md). - -### Template Variables - -Templates support the following replacement variables: - -- `$TITLE$`: Meeting title -- `$AGENDA_CONTENT$`: Auto-generated agenda items -- `$INVITED$`: Invited attendees list -- `$OBSERVERS$`: Observers list -- `$GITHUB_ISSUE$`: GitHub issue URL -- `$MINUTES_DOC$`: Google Doc URL - -## ➕ Adding New Meeting Groups - -To add a new meeting group to the system, you need to create templates and update configuration in three places: - -### 1. Create Template Files - -Create four template files in the `templates/` directory following the naming convention: - -```bash -# Replace with your meeting group identifier -templates/invited_ -templates/observers_ -templates/meeting_base_ -templates/minutes_base_ +```json +{ + "name": "Technical Steering Committee (TSC)", + "host": "Node.js", + "calendar": { + "filter": "Node.js TSC Meeting", + "url": "https://calendar.google.com/calendar/ical//public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "TSC" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/611357642", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["@nodejs/tsc"] +} ``` -See [TEMPLATES_DOCUMENTATION.md](./TEMPLATES_DOCUMENTATION.md) for detailed template examples and variable explanations. - -### 2. Update GitHub Actions Workflows +See [TEMPLATES_DOCUMENTATION.md](./TEMPLATES_DOCUMENTATION.md) for a full reference +of every field, including the optional ones (`github.agendaLabel`, +`github.issueLabels`, `joining.notes`, `observers`, and curated `agenda` sections). -Add your meeting group to both workflow files: - -- `.github/workflows/create-meeting-artifacts-manual.yml` -- `.github/workflows/create-meeting-artifacts-scheduled.yml` +## ➕ Adding New Meeting Groups -For manual workflow, add your group to the `options` list under `workflow_dispatch.inputs.meeting_group`: +Adding a group is a single step: create its config file. -```yaml -workflow_dispatch: - inputs: - meeting_group: - description: 'Meeting group to create artifacts for' - required: true - type: choice - options: - - uvwasi - - tsc - - build - # ... existing groups ... - - your-new-group # Add your group here -``` +### 1. Create the meeting config -For scheduled workflow, add your group to the `matrix.meeting_group` list: - -```yaml -strategy: - matrix: - meeting_group: - - uvwasi - - tsc - - build - # ... existing groups ... - - your-new-group # Add your group here -``` +Add `meetings/.meeting.json` following the format above. The filename stem +(``) is what you pass on the command line and to the workflows. -### 3. Update Package.json Scripts +### 2. That's it -Add npm scripts to `package.json` following this pattern: +The GitHub Actions workflows discover meeting groups automatically: -```json -{ - "scripts": { - "your-meeting-group-meeting": "node create-node-meeting-artifacts.mjs your_meeting_group", - "your-meeting-group-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs your_meeting_group" - } -} -``` +- The **scheduled** workflow builds its matrix by listing `meetings/*.meeting.json`. +- The **manual** workflow reads the owner straight from the JSON file. -**Important Notes:** - -- Use **kebab-case** for script names: `your-meeting-group-meeting` -- Use **snake_case** for the actual group parameter: `your_meeting_group` -- Always create both production and development (`:dev`) versions -- The development version uses `--env-file=.env` for local testing +No workflow edits and no `package.json` scripts are needed. ## 🏗️ Development @@ -147,7 +108,7 @@ Add npm scripts to `package.json` following this pattern: 1. Clone the repository 2. Install dependencies: `npm install` 3. Copy `.env.example` to `.env` and configure your credentials -4. Create meeting artifacts: `npm run -meeting:dev` +4. Create meeting artifacts: `npm run dev -- ` (e.g. `npm run dev -- tsc`) ### Code Quality @@ -169,8 +130,17 @@ npx --env-file=.env . tsc # Direct execution (with a `.env` file) node --env-file=.env create-node-meeting-artifacts.mjs tsc + +# Preview the rendered issue without creating anything +node --env-file=.env create-node-meeting-artifacts.mjs tsc --dry-run ``` +The CLI accepts the following flags: + +- `--dry-run`: render and print the issue without creating an issue or document +- `--force`: create a new issue/document even if one already exists +- `--verbose`: enable debug logging from the GitHub client + ## 📂 Output The application creates: @@ -188,17 +158,8 @@ The application creates: - `GITHUB_TOKEN`: GitHub Personal Access Token - `HACKMD_API_TOKEN`: HackMD API token for creating and managing documents -### Meeting Base Configuration +### Meeting Configuration -Each `meeting_base_` file contains: - -```bash -CALENDAR_FILTER="Meeting Name in Calendar" -USER="nodejs" -REPO="repository-name" -GROUP_NAME="Full Group Name" -AGENDA_TAG="agenda-label" -ISSUE_LABEL="optional-issue-label" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS="Meeting join instructions" -``` +Each meeting is configured by a `meetings/.meeting.json` file. See +[TEMPLATES_DOCUMENTATION.md](./TEMPLATES_DOCUMENTATION.md) for the complete field +reference. diff --git a/TEMPLATES_DOCUMENTATION.md b/TEMPLATES_DOCUMENTATION.md index aa2b1e7..19c4d0b 100644 --- a/TEMPLATES_DOCUMENTATION.md +++ b/TEMPLATES_DOCUMENTATION.md @@ -1,193 +1,157 @@ -This document contains empty versions of each template needed to successfully create meeting artifacts for a Committee, Working Group, Initiative, or Team. These documents need to go in the [/templates](./templates) directory in this repository. - -## Template Variables - -There are several variables in the documents that need to be configured: - -- **``:** The name of the Committee, Working Group, Initiative, or Team that meeting artifacts are being created for. -- **``:** The abbreviated or shortened name for a group, used in each filename to connect associated files together. -- **``:** The name of the GitHub team in the Node.js org. -- **``:** The name of calendar events that mark the group's meeting date/time. -- **``:** The repository of the group. -- **``:** The label for which to search the Node.js GitHub organization, to add items to the group's agenda. -- **``:** an optional label for the created issue itself. -- **``:** Name of an observer in a group's meetings. - -## Meeting Properties Reference - -The following properties are available in meeting base templates and can be used in meeting issue generation: - -- **`CALENDAR_FILTER`:** The name of calendar events that mark the group's meeting date/time -- **`ICAL_URL`:** The ICAL URL for a given meetings' calendar -- **`USER`:** The GitHub username/organization (typically `nodejs`) -- **`REPO`:** The repository name where meeting issues are created -- **`GROUP_NAME`:** The full name of the Committee, Working Group, Initiative, or Team -- **`AGENDA_TAG`:** The label used to search for agenda items in GitHub issues and PRs -- **`HOST`:** Meeting host information -- **`JOINING_INSTRUCTIONS`:** Instructions for joining the meeting (Zoom links, YouTube streams, etc.) -- **`ISSUE_LABEL`:** Optional label to apply to the created meeting issue - -These properties are defined in the `meeting_base_` template files and are substituted when generating meeting issues. - -# Invited - -The [GitHub Team](https://help.github.com/articles/about-teams/) to invite. The @mention should be a GitHub Team whose members are all invidiuals who are always invited. - -**File:** `invited_` - -``` -* Members: @nodejs/ -``` - -## Invited Example - -**File:** `invited_commcomm` - -``` -* CommComm Members: @nodejs/community-committee -``` - -# Meeting Base - -A base of metadata and some content for the issue to be created on time, with agenda items automatically created. - -**File:** `meeting_base_` - -``` -CALENDAR_FILTER="" -ICAL_URL="" -USER="nodejs" -REPO="" -GROUP_NAME="" -AGENDA_TAG= -JOINING_INSTRUCTIONS=" - -* link for participants: Will be added a few minutes before meeting starts -* For those who just want to watch: https://www.youtube.com/channel/UCQPYJluYC_sn_Qz_XE-YbTQ/live" -``` - -## Meeting Base Example - -**File:** `meeting_base_commcomm` - -``` -CALENDAR_FILTER="Node.js Community Committee" -ICAL_URL="<...>" -USER="nodejs" -REPO="community-committee" -GROUP_NAME="Community Committee" -AGENDA_TAG=cc-agenda -JOINING_INSTRUCTIONS=" - -* link for participants: Will be added a few minutes before meeting starts -* For those who just want to watch: https://www.youtube.com/channel/UCQPYJluYC_sn_Qz_XE-YbTQ/live" -``` - -# Minutes Base - -A basic outline for the meeting minutes to be autogenerated in Google Docs. The only basic change from the default template is the message about what label agenda items are extracted from. - -**File:** `minutes_base_` - -``` -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Foundation Calendar**: https://nodejs.org/calendar - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. - -``` - -## Minutes Base Example - -**File:** `minutes_base_commcomm` - -``` - - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **cc-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Foundation Calendar**: https://nodejs.org/calendar - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. -``` - -# Observers - -List meeting observers who will consistently attend meetings _as observers_. -**File:** `observers_` - -``` -* -* -* -(...) -``` - -## Observers Example - -**File:** `observers_commcomm` - -``` -* @therebelrobot (Oz Haven - observer) -* @ParidelPooya (Pooya Paridel - observer) -* @rykerrumsey (Ryker Rumsey - observer) -* @baez (Behzad Karim - observer) -* @BinarySo1o (Therese Stirling - observer) -* @amiller-gh (Adam Miller - observer) -* @yosuke-furukawa (Yosuke Furukawa - observer) -* @Maurice-Hayward (Maurice Hayward - observer) -* @mikehostetler (Mike Hostetler - observer) -* @sarahkconway (Sarah Conway - observer) -* @Tiriel (Benjamin Zaslavsky - observer) -* @DiegoZoracKy (Diego ZoracKy - observer) -* @ZibbyKeaton (Zibby Keaton - observer) -* @tobyfarley (Toby Farley - observer) -* @Bamieh (Ahmad Bamieh - observer) -* @pchrysa (Chrysa - observer) -* @foadlab (observer) -* @codeekage (Abraham Agiri - observer) -* @Voskan (Voskan Voskanyan - observer) +# Meeting Configuration Reference + +Every meeting group is described by a single JSON file in the +[`meetings/`](./meetings) directory, named `.meeting.json` — for example, +`tsc.meeting.json`. Each file follows the **identical format** documented below. + +All meeting artifacts — both the GitHub issue and the HackMD minutes — are +rendered from one shared template, +[`templates/meeting.mustache`](./templates/meeting.mustache), so every group's +issue and notes share the same structure. + +## Filename + +The filename stem is the **group identifier** you pass on the command line +(`npx . `) and the value the workflows use. For example, +`meetings/tsc.meeting.json` is the `tsc` group. + +## Format + +```json +{ + "name": "Technical Steering Committee (TSC)", + "host": "Node.js", + "calendar": { + "filter": "Node.js TSC Meeting", + "url": "https://calendar.google.com/calendar/ical//public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "TSC", + "agendaLabel": "tsc-agenda", + "issueLabels": ["tsc-meeting"] + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/611357642", + "observer": "https://www.youtube.com/c/nodejs+foundation/live", + "notes": "Regular password." + }, + "invited": ["@nodejs/tsc"], + "observers": [], + "agenda": [] +} +``` + +## Fields + +### Top level + +| Field | Required | Description | +| ----------- | -------- | ------------------------------------------------------------------------ | +| `name` | ✅ | Human-readable group name, used in the meeting title. | +| `host` | ❌ | Meeting host. Defaults to `"Node.js"`. Use `"OpenJS Foundation"` etc. | +| `calendar` | ✅ | Calendar lookup configuration (see below). | +| `github` | ✅ | GitHub configuration (see below). | +| `hackmd` | ✅ | HackMD configuration (see below). | +| `joining` | ✅ | How to join/observe the meeting (see below). | +| `invited` | ✅ | Array of invited attendees (GitHub team mentions or names). | +| `observers` | ❌ | Array of standing observers. Defaults to `[]`. | +| `agenda` | ❌ | Array of manually-curated agenda sections (see below). Defaults to `[]`. | + +### `calendar` + +| Field | Required | Description | +| -------- | -------- | ----------------------------------------------------------------------------------------- | +| `filter` | ✅ | Text matched against calendar event summaries/descriptions. | +| `url` | ✅ | The iCal feed URL searched for the next meeting occurrence. | +| `page` | ❌ | Public "add to your calendar" page linked from the minutes. Defaults by host (see below). | + +The `page` default is `https://calendar.openjsf.org` when `host` is +`"OpenJS Foundation"`, otherwise `https://nodejs.org/calendar`. + +### `github` + +| Field | Required | Description | +| ------------- | -------- | ------------------------------------------------------------------ | +| `owner` | ✅ | Organization/user that owns the meeting repository. | +| `repo` | ✅ | Repository where the meeting issue is created. | +| `agendaLabel` | ❌ | Label used to collect agenda issues. Defaults to `-agenda`. | +| `issueLabels` | ❌ | Labels applied to the created meeting issue. | + +Agenda items are gathered from issues and pull requests across the entire +`owner` organization that carry the `agendaLabel`. + +### `hackmd` + +| Field | Required | Description | +| ------ | -------- | -------------------------------------------------- | +| `team` | ✅ | HackMD team the minutes document is created under. | + +### `joining` + +| Field | Required | Description | +| ------------- | -------- | ------------------------------------------------------------- | +| `participant` | ❌ | Where participants join (a URL or a short instruction). | +| `sessions` | ❌ | Alternating sessions with per-session join links (see below). | +| `observer` | ❌ | Where observers watch the livestream (a URL). | +| `notes` | ❌ | Any additional joining notes (e.g. "Regular password."). | + +#### Alternating sessions + +Some groups alternate between time slots that each have their own join link — for +example, the TSC rotates between a 13:00 UTC and a 17:00 UTC slot, each running +every two weeks. Model these with `joining.sessions` instead of a single +`participant`: + +```json +"joining": { + "observer": "https://www.youtube.com/c/nodejs+foundation/live", + "sessions": [ + { "time": "13:00", "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/94552847907?password=..." }, + { "time": "17:00", "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/96540765177?password=..." } + ] +} +``` + +`time` is the **UTC** time of day (`HH:MM`). When the tool finds the next +occurrence on the calendar, it selects the session whose `time` matches that +occurrence's UTC time and shows its link. If no session matches (such as a +`--dry-run`, which has no real occurrence), every session is listed. + +| Field | Required | Description | +| ------------- | -------- | ------------------------------------------- | +| `time` | ✅ | UTC time of day in `HH:MM` form. | +| `participant` | ✅ | Where participants join this session (URL). | + +### `agenda` (manual sections) + +Use this for groups that maintain curated, recurring sections in addition to the +label-driven agenda — for example, the TSC's reminders, the CPC's working-group +updates, or the Security WG's standing review items. These sections render in the +**minutes only** (the GitHub issue stays focused on the label-driven agenda and +joining details), and they appear before the label-driven "Issues and Pull +Requests" section. A section with an empty `items` array renders just its heading +(useful for live-filled sections like "Strategic Initiatives"). Each section is: + +| Field | Required | Description | +| ------------- | -------- | ------------------------------------ | +| `title` | ✅ | Section heading. | +| `description` | ❌ | Text rendered under the heading. | +| `items` | ✅ | Bullet list items (typically links). | + +Example: + +```json +"agenda": [ + { + "title": "Regular Reviews", + "description": "Please review our standing list before the meeting:", + "items": [ + "https://github.com/nodejs/TSC/labels/tsc-review" + ] + } +] ``` diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs index 1f27add..f486b83 100644 --- a/create-node-meeting-artifacts.mjs +++ b/create-node-meeting-artifacts.mjs @@ -3,10 +3,12 @@ /** * Node.js Meeting Artifacts Creator * + * Creates a GitHub issue and a HackMD minutes document for a Node.js meeting, + * driven entirely by a JSON config in meetings/.meeting.json. + * * Usage: - * node create-node-meeting-artifacts.mjs [meetingGroup] - * npm run tsc-meeting - * npm run dev -- tsc + * npx . tsc + * node --env-file=.env create-node-meeting-artifacts.mjs tsc --dry-run */ import { Command } from 'commander'; @@ -19,145 +21,124 @@ import * as meetings from './src/meeting.mjs'; const program = new Command(); program - .argument('', 'Meeting group') + .argument('', 'Meeting group (the meetings/.meeting.json stem)') .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); -// Step 1: Application configuration +// All dates are computed and rendered in UTC. process.env.TZ = 'UTC'; -/** @type {import('./src/types').AppConfig} */ +/** @type {import('./src/types.d.ts').AppConfig} */ const config = { ...environmentConfig, ...program.opts(), meetingGroup: program.args[0], }; -console.log('Application config loaded', config); +// Load the meeting configuration from its JSON file. +const meeting = await meetings.load(config.meetingGroup); +console.debug('Meeting config loaded', meeting); -// Step 3: Initialize GitHub client +// Initialize API clients. const githubClient = github.createGitHubClient(config); +const hackmdClient = hackmd.createHackMDClient(config, meeting); -// Step 4: Read meeting configuration from templates -const meetingConfig = await meetings.readMeetingConfig(config); -console.debug('Meeting config loaded', meetingConfig); - -// Step 5: Initialize HackMD client with meeting configuration -const hackmdClient = hackmd.createHackMDClient(config, meetingConfig); +// Collect agenda issues for the meeting. +const agendaIssues = await github.getAgendaIssues(githubClient, meeting); +console.debug('Found agenda issues', agendaIssues); if (config.dryRun) { - const gitHubAgendaIssues = await github.getAgendaIssues( - githubClient, - config, - meetingConfig - ); - console.debug('Found agenda issues', gitHubAgendaIssues); - - const meetingAgenda = meetings.generateMeetingAgenda(gitHubAgendaIssues); - - const issueContent = await meetings.generateMeetingIssue( - config, - meetingConfig, - new Date(), - meetingAgenda, - '' + const now = new Date(); + const title = meetings.generateMeetingTitle(meeting, now); + + const issueContent = await meetings.render( + meetings.createTemplateContext( + meeting, + now, + agendaIssues, + [{ title: 'Minutes', url: 'https://hackmd.io/' }], + { title } + ) ); console.log(issueContent); - process.exit(0); } -// Step 6: Find next meeting event in calendar -const events = await calendar.getEventsFromCalendar( - meetingConfig.properties.ICAL_URL -); -console.debug('Loaded calendar', events); - -const meetingDate = await calendar.findNextMeetingDate(events, meetingConfig); +// Find the next meeting occurrence in the calendar. +const events = await calendar.getEventsFromCalendar(meeting.calendar.url); +const meetingDate = await calendar.findNextMeetingDate(events, meeting); console.debug('Next meeting date', meetingDate); -// If no meeting is found, exit gracefully +// If no meeting is found, exit gracefully (expected for non-weekly meetings). if (!meetingDate) { const [weekStart, weekEnd] = calendar.getNextWeek(); console.log( - `No meeting found for ${meetingConfig.properties.GROUP_NAME || 'this group'} ` + + `No meeting found for ${meeting.name} ` + `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.` ); process.exit(0); } -// Step 8: Get Meeting Title -const meetingTitle = meetings.generateMeetingTitle( - config, - meetingConfig, - meetingDate -); +// Build the meeting title. +const meetingTitle = meetings.generateMeetingTitle(meeting, meetingDate); console.debug('Meeting title', meetingTitle); -// Step 9: Get agenda information using native implementation -const gitHubAgendaIssues = await github.getAgendaIssues( - githubClient, - config, - meetingConfig -); -console.debug('Found agenda issues', gitHubAgendaIssues); - -// Step 10: Parse meeting agenda from GitHub issues -const meetingAgenda = meetings.generateMeetingAgenda(gitHubAgendaIssues); - -// Step 11: Create HackMD document with meeting notes and tags +// Create (or fetch) the HackMD minutes document and resolve its link. const hackmdNote = await hackmd.getOrCreateMeetingNotesDocument( hackmdClient, meetingTitle, config ); -console.debug('HackMD document created/retrieved:', hackmdNote); - -// Step 12: Get the HackMD document link const minutesDocLink = hackmdNote.publishLink || `https://hackmd.io/${hackmdNote.id}`; - -// Step 13: Generate meeting issue content using native implementation -const issueContent = await meetings.generateMeetingIssue( - config, - meetingConfig, - meetingDate, - meetingAgenda, - minutesDocLink +console.debug('HackMD document created/retrieved', minutesDocLink); + +// Render and publish the GitHub issue. +const issueContent = await meetings.render( + meetings.createTemplateContext( + meeting, + meetingDate, + agendaIssues, + [{ title: 'Minutes', url: minutesDocLink }], + { title: meetingTitle } + ) ); -// Step 14: Create GitHub issue with HackMD link const githubIssue = await github.createOrUpdateGitHubIssue( githubClient, config, - meetingConfig, + meeting, meetingTitle, issueContent ); -console.debug('GitHub issue created/updated', githubIssue); - -// Step 15: Update the minutes content with the HackMD link -const minutesContent = await meetings.generateMeetingMinutes( - config, - meetingConfig, - meetingTitle, - meetingAgenda, - minutesDocLink, - githubIssue.html_url +console.debug('GitHub issue created/updated', githubIssue.html_url); + +// Render the minutes (identical format, plus a back-link to the issue) and +// store it in the HackMD document. +const minutesContent = await meetings.render( + meetings.createTemplateContext( + meeting, + meetingDate, + agendaIssues, + [ + { title: 'Minutes', url: minutesDocLink }, + { title: 'GitHub Issue', url: githubIssue.html_url }, + ], + { isMinutes: true, title: meetingTitle } + ) ); -// Step 16: Update the HackMD document with the self-referencing link await hackmd.updateMeetingNotesDocument( hackmdClient, hackmdNote.id, minutesContent ); -// Output success information with links +// Output success information with links. console.log(`Created GitHub issue: ${githubIssue.html_url}`); console.log(`Created HackMD document: ${minutesDocLink}`); diff --git a/eslint.config.mjs b/eslint.config.mjs index 979c62d..05de777 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,6 +9,13 @@ export default [ { ignores: ['node_modules', '.env*', 'minutes_temp.txt'], }, + { + files: ['test/**/*.mjs'], + languageOptions: { + ecmaVersion: 'latest', + globals: { ...globals.nodeBuiltin }, + }, + }, { files: ['**/*.mjs'], rules: { diff --git a/global.d.ts b/global.d.ts deleted file mode 100644 index 4ec2382..0000000 --- a/global.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Global TypeScript definitions for JSDoc usage - * This file makes types from third-party libraries available globally without imports in JSDoc comments - */ - -import type { RestEndpointMethodTypes } from '@octokit/rest'; -import type { API } from '@hackmd/api'; -import type { SingleNote } from '@hackmd/api/dist/type.d.ts'; - -declare global { - type HackMDClient = API; - type HackMDNote = SingleNote; - - // GitHub API type aliases - type GitHubIssue = - RestEndpointMethodTypes['issues']['create']['response']['data']; -} diff --git a/meetings/benchmarking.meeting.json b/meetings/benchmarking.meeting.json new file mode 100644 index 0000000..abb7133 --- /dev/null +++ b/meetings/benchmarking.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Benchmarking WorkGroup", + "host": "Node.js", + "calendar": { + "filter": "Benchmarking WG Meeting", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "benchmarking" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/345481302", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["Benchmarking team: @nodejs/benchmarking"] +} diff --git a/meetings/build.meeting.json b/meetings/build.meeting.json new file mode 100644 index 0000000..e6d5c2d --- /dev/null +++ b/meetings/build.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Build WorkGroup", + "host": "Node.js", + "calendar": { + "filter": "Node.js Build Working Group Tri-Weekly Meeting", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" + }, + "github": { + "owner": "nodejs", + "repo": "build" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/97490955692?password=3ea54b47-0782-47a3-9a09-bdd4070d9ef2", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["Build team: @nodejs/build"] +} diff --git a/meetings/bundler_collab.meeting.json b/meetings/bundler_collab.meeting.json new file mode 100644 index 0000000..cf838a9 --- /dev/null +++ b/meetings/bundler_collab.meeting.json @@ -0,0 +1,24 @@ +{ + "name": "Bundler Collaboration Space", + "host": "OpenJS Foundation", + "calendar": { + "filter": "Bundler Working Group Monthly Meeting", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" + }, + "github": { + "owner": "openjs-foundation", + "repo": "bundler-collab-space", + "agendaLabel": "bundler-agenda" + }, + "hackmd": { + "team": "openjs" + }, + "joining": { + "participant": "Refer to https://calendar.openjsf.org for the meeting link" + }, + "invited": [ + "Bundler Collab Space Members", + "OpenJS Foundation Cross Project Council", + "All are welcome" + ] +} diff --git a/meetings/cross_project_council.meeting.json b/meetings/cross_project_council.meeting.json new file mode 100644 index 0000000..24f45aa --- /dev/null +++ b/meetings/cross_project_council.meeting.json @@ -0,0 +1,72 @@ +{ + "name": "Cross Project Council", + "host": "OpenJS Foundation", + "calendar": { + "filter": "Cross Project Council Meeting", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" + }, + "github": { + "owner": "openjs-foundation", + "repo": "cross-project-council", + "agendaLabel": "cross-project-council-agenda", + "issueLabels": ["cpc-meeting"] + }, + "hackmd": { + "team": "openjs" + }, + "joining": { + "participant": "Refer to https://calendar.openjsf.org for the meeting link", + "observer": "https://livestream.openjsf.org" + }, + "invited": [ + "OpenJS Foundation Cross Project Council", + "OpenJS Foundation Project Maintainers", + "OpenJS Foundation Board of Directors" + ], + "agenda": [ + { + "title": "Board Meeting Updates", + "items": [ + "https://github.com/openjs-foundation/cross-project-council/labels/waiting-on-board" + ] + }, + { + "title": "Staff Updates", + "items": [ + "https://github.com/openjs-foundation/cross-project-council/labels/waiting-on-legal-info", + "https://github.com/openjs-foundation/cross-project-council/labels/waiting-on-staff-update", + "https://github.com/openjs-foundation/cross-project-council/labels/waiting-on-website-update" + ] + }, + { + "title": "Next Week's Working Session", + "description": "Are there any initiatives or agenda items that we should use a working session to further progress on?", + "items": [ + "https://github.com/openjs-foundation/cross-project-council/labels/cpc-working-session" + ] + }, + { + "title": "Regular Reviews", + "description": "Please review regularly our list of dates and reminders, our quarterly review issues, and check the list of issues that can be closed:", + "items": [ + "https://github.com/openjs-foundation/cross-project-council/blob/main/Dates-and-Reminders.md", + "https://github.com/openjs-foundation/cross-project-council/labels/cpc-quartely-review", + "https://github.com/openjs-foundation/cross-project-council/labels/cpc-can-issue-be-closed" + ] + }, + { + "title": "Working Group Updates", + "items": [ + "[ai](https://github.com/openjs-foundation/ai-collab-space)", + "[bundler](https://github.com/openjs-foundation/bundler-collab-space)", + "[ecosystem-report](https://github.com/openjs-foundation/ecosystem-report)", + "[openvis](https://github.com/openjs-foundation/openvis-collab-space)", + "[pkg-metadata-interop](https://github.com/openjs-foundation/package-metadata-interoperability-collab-space)", + "[pkg-vuln](https://github.com/openjs-foundation/pkg-vuln-collab-space)", + "[security](https://github.com/openjs-foundation/security-collab-space)", + "[standards](https://github.com/openjs-foundation/standards)", + "[sustainability](https://github.com/openjs-foundation/sustainability-collab-space)" + ] + } + ] +} diff --git a/meetings/diag.meeting.json b/meetings/diag.meeting.json new file mode 100644 index 0000000..cea0ff2 --- /dev/null +++ b/meetings/diag.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Diagnostics WorkGroup", + "host": "Node.js", + "calendar": { + "filter": "Diagnostics WG Meeting", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "diagnostics", + "agendaLabel": "diag-agenda" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/466707925" + }, + "invited": ["Diagnostics team: @nodejs/diagnostics"] +} diff --git a/meetings/diag_deepdive.meeting.json b/meetings/diag_deepdive.meeting.json new file mode 100644 index 0000000..ab46a1d --- /dev/null +++ b/meetings/diag_deepdive.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Diagnostics Deep Dive", + "host": "Node.js", + "calendar": { + "filter": "Diagnostics Deep Dive Meeting", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "diagnostics", + "agendaLabel": "diag-deepdive-agenda" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/466707925" + }, + "invited": ["Diagnostics deep dive meetings: @nodejs/diagnostics"] +} diff --git a/meetings/ecosystem_report.meeting.json b/meetings/ecosystem_report.meeting.json new file mode 100644 index 0000000..4263f5e --- /dev/null +++ b/meetings/ecosystem_report.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Ecosystem Report Collab Space", + "host": "OpenJS Foundation", + "calendar": { + "filter": "Ecosystem Report Collab Space", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" + }, + "github": { + "owner": "openjs-foundation", + "repo": "ecosystem-report-collab-space", + "agendaLabel": "ecosystem-report-agenda" + }, + "hackmd": { + "team": "openjs" + }, + "joining": { + "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/93477817392?password=d91f112c-4840-48a6-8c30-1ce94cc0e418" + }, + "invited": ["Ecosystem Report Collab Space Members: @openjs/ecosystem-report"] +} diff --git a/meetings/loaders.meeting.json b/meetings/loaders.meeting.json new file mode 100644 index 0000000..e7e5bf0 --- /dev/null +++ b/meetings/loaders.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Loaders Team", + "host": "Node.js", + "calendar": { + "filter": "Loaders Team Meeting", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "loaders" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/97506450082", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["Loaders team: @nodejs/loaders"] +} diff --git a/meetings/modules.meeting.json b/meetings/modules.meeting.json new file mode 100644 index 0000000..d392225 --- /dev/null +++ b/meetings/modules.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Modules Team", + "host": "Node.js", + "calendar": { + "filter": "Modules Team Meeting", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "modules" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/656987750", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["Modules team: @nodejs/modules"] +} diff --git a/meetings/next-10.meeting.json b/meetings/next-10.meeting.json new file mode 100644 index 0000000..4086b6f --- /dev/null +++ b/meetings/next-10.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Next 10 Years team", + "host": "Node.js", + "calendar": { + "filter": "Node.js Next-10 Bi-Weekly Meeting", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" + }, + "github": { + "owner": "nodejs", + "repo": "next-10" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/94655111104?password=24076cd8-afca-4239-ae9f-b33b73328d6e", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["Next 10 years team: @nodejs/next-10"] +} diff --git a/meetings/outreach.meeting.json b/meetings/outreach.meeting.json new file mode 100644 index 0000000..518c70b --- /dev/null +++ b/meetings/outreach.meeting.json @@ -0,0 +1,22 @@ +{ + "name": "Outreach", + "host": "Node.js", + "calendar": { + "filter": "Node.js Outreach Meeting", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "outreach", + "agendaLabel": "outreach-agenda" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/526318636", + "observer": "https://www.youtube.com/nodejs-foundation", + "notes": "Guests are welcome to attend via Zoom or follow along on the YouTube live stream. If attending as a guest, please comment on this issue to let us know." + }, + "invited": ["Outreach Members: @nodejs/outreach"] +} diff --git a/meetings/package-maintenance.meeting.json b/meetings/package-maintenance.meeting.json new file mode 100644 index 0000000..84c1d59 --- /dev/null +++ b/meetings/package-maintenance.meeting.json @@ -0,0 +1,27 @@ +{ + "name": "Package Maintenance Team", + "host": "Node.js", + "calendar": { + "filter": "Package Maintenance", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "package-maintenance" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/431077278", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["Package Maintenance team: @nodejs/package-maintenance"], + "agenda": [ + { + "title": "Project Board Review", + "description": "Ask participants about the state of the Project Board and necessary changes, if time permits.", + "items": ["https://github.com/nodejs/package-maintenance/projects/1"] + } + ] +} diff --git a/meetings/package_metadata_interop.meeting.json b/meetings/package_metadata_interop.meeting.json new file mode 100644 index 0000000..6bfe429 --- /dev/null +++ b/meetings/package_metadata_interop.meeting.json @@ -0,0 +1,22 @@ +{ + "name": "Package Metadata Interoperability Collab Space", + "host": "OpenJS Foundation", + "calendar": { + "filter": "Package Metadata Interoperability", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" + }, + "github": { + "owner": "openjs-foundation", + "repo": "package-metadata-interoperability-collab-space", + "agendaLabel": "package-metadata-agenda" + }, + "hackmd": { + "team": "openjs" + }, + "joining": { + "observer": "https://livestream.openjsf.org" + }, + "invited": [ + "Package Metadata Interoperability Collab Space Members: @openjs/package-metadata-interop" + ] +} diff --git a/meetings/release.meeting.json b/meetings/release.meeting.json new file mode 100644 index 0000000..48187e1 --- /dev/null +++ b/meetings/release.meeting.json @@ -0,0 +1,19 @@ +{ + "name": "Release WorkGroup", + "host": "Node.js", + "calendar": { + "filter": "Node.js Release Working Group Monthly Meeting", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" + }, + "github": { + "owner": "nodejs", + "repo": "Release" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/96075006319?password=532f2ca8-9159-4002-8f55-55533138b68e" + }, + "invited": ["Release team: @nodejs/release", "LTS team: @nodejs/lts"] +} diff --git a/meetings/security-wg.meeting.json b/meetings/security-wg.meeting.json new file mode 100644 index 0000000..05f1eb2 --- /dev/null +++ b/meetings/security-wg.meeting.json @@ -0,0 +1,29 @@ +{ + "name": "Security team", + "host": "Node.js", + "calendar": { + "filter": "Node.js Security Working Group Monthly Meeting", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" + }, + "github": { + "owner": "nodejs", + "repo": "security-wg" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/92626800362?password=78ffb026-dfb9-4549-b8cd-c3b82bd907f7", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["Security wg team: @nodejs/security-wg"], + "agenda": [ + { + "title": "Standing Items", + "items": [ + "Vulnerability Review: https://github.com/nodejs/nodejs-dependency-vuln-assessments/issues", + "OpenSSF Scorecard Monitor Review: https://github.com/nodejs/security-wg/issues?q=is%3Aissue+OpenSSF+Scorecard+Report+Updated%21+" + ] + } + ] +} diff --git a/meetings/security_collab.meeting.json b/meetings/security_collab.meeting.json new file mode 100644 index 0000000..12c6bae --- /dev/null +++ b/meetings/security_collab.meeting.json @@ -0,0 +1,27 @@ +{ + "name": "Security Collab Space", + "host": "OpenJS Foundation", + "calendar": { + "filter": "Security Collab Space meeting", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" + }, + "github": { + "owner": "openjs-foundation", + "repo": "security-collab-space", + "agendaLabel": "security-agenda" + }, + "hackmd": { + "team": "openjs" + }, + "joining": { + "participant": "https://zoom.us/j/94497273832", + "observer": "https://livestream.openjsf.org" + }, + "invited": [ + "OpenJS Foundation Security Collab Space Members", + "OpenJS Foundation Cross Project Council", + "OpenJS Foundation Project Maintainers", + "OpenJS Foundation Board of Directors", + "All are welcome" + ] +} diff --git a/meetings/standards.meeting.json b/meetings/standards.meeting.json new file mode 100644 index 0000000..6d47d9c --- /dev/null +++ b/meetings/standards.meeting.json @@ -0,0 +1,25 @@ +{ + "name": "Standards Working Group", + "host": "OpenJS Foundation", + "calendar": { + "filter": "Standards Working Group Meeting", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" + }, + "github": { + "owner": "openjs-foundation", + "repo": "standards", + "agendaLabel": "standards-agenda" + }, + "hackmd": { + "team": "openjs" + }, + "joining": { + "participant": "https://zoom.us/j/599005386", + "observer": "https://livestream.openjsf.org" + }, + "invited": [ + "OpenJS Foundation Cross Project Council", + "OpenJS Foundation Project Maintainers", + "OpenJS Foundation Board of Directors" + ] +} diff --git a/meetings/sustainability_collab.meeting.json b/meetings/sustainability_collab.meeting.json new file mode 100644 index 0000000..a986e8a --- /dev/null +++ b/meetings/sustainability_collab.meeting.json @@ -0,0 +1,22 @@ +{ + "name": "Sustainability Collaboration Space", + "host": "OpenJS Foundation", + "calendar": { + "filter": "Sustainability Collaboration Space", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" + }, + "github": { + "owner": "openjs-foundation", + "repo": "sustainability-collab-space", + "agendaLabel": "sustainability-agenda" + }, + "hackmd": { + "team": "openjs" + }, + "joining": { + "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/97342926119?password=39c25254-e7a3-4830-be8e-d54d63234bfb" + }, + "invited": [ + "Sustainability Collab Space Members: @openjs-foundation/sustainability-collaboration-space" + ] +} diff --git a/meetings/test_runner.meeting.json b/meetings/test_runner.meeting.json new file mode 100644 index 0000000..8ab6d60 --- /dev/null +++ b/meetings/test_runner.meeting.json @@ -0,0 +1,19 @@ +{ + "name": "Test Runner", + "host": "Node.js", + "calendar": { + "filter": "Node.js Test Runner", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" + }, + "github": { + "owner": "nodejs", + "repo": "test-runner" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/95769812407?password=884bba7b-1b1e-490f-bfc3-2eff14e35159" + }, + "invited": ["@nodejs/test_runner"] +} diff --git a/meetings/tooling.meeting.json b/meetings/tooling.meeting.json new file mode 100644 index 0000000..5dfdd64 --- /dev/null +++ b/meetings/tooling.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Tooling Group", + "host": "Node.js", + "calendar": { + "filter": "Node.js Tooling", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "tooling" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/113867470", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["Tooling team: @nodejs/tooling"] +} diff --git a/meetings/tsc.meeting.json b/meetings/tsc.meeting.json new file mode 100644 index 0000000..2173826 --- /dev/null +++ b/meetings/tsc.meeting.json @@ -0,0 +1,69 @@ +{ + "name": "Technical Steering Committee (TSC)", + "host": "Node.js", + "calendar": { + "filter": "Node.js TSC Meeting", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "TSC" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "observer": "https://www.youtube.com/c/nodejs+foundation/live", + "sessions": [ + { + "time": "13:00", + "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/94552847907?password=fbbea37b-f6f4-4dce-bf27-10d3251f0a9c" + }, + { + "time": "17:00", + "participant": "https://zoom-lfx.platform.linuxfoundation.org/meeting/96540765177?password=6f3632c7-f3a6-432b-8a27-99c95caffeaa" + } + ] + }, + "invited": [ + "Antoine du Hamel @aduh95 (voting member)", + "Yagiz Nizipli @anonrig (voting member)", + "Benjamin Gruenbaum @benjamingr (voting member)", + "Ruben Bridgewater @BridgeAR (voting member)", + "Gireesh Punathil @gireeshpunathil (voting member)", + "James Snell @jasnell (voting member)", + "Joyee Cheung @joyeecheung (voting member)", + "Chengzhong Wu @legendecas (voting member)", + "Marco Ippolito @marco-ippolito (voting member)", + "Matteo Collina @mcollina (voting member)", + "Filip Skokan @panva (voting member)", + "Rafael Gonzaga @RafaelGSS (voting member)", + "Darshan Sen @RaisinTen (voting member)", + "Richard Lau @richardlau (voting member)", + "Robert Nagy @ronag (voting member)", + "Ruy Adorno @ruyadorno (voting member)", + "Paolo Insogna @ShogunPanda (voting member)", + "Michaël Zasso @targos (voting member)", + "Tobias Nießen @tniessen (voting member)", + "Beth Griggs @BethGriggs (regular member)", + "Ben Noordhuis @bnoordhuis (regular member)", + "Colin Ihrig @cjihrig (regular member)", + "Geoffrey Booth @GeoffreyBooth (regular member)", + "Moshe Atlow @MoLow (regular member)", + "Shelley Vohr @codebytere (regular member)", + "Rich Trott @Trott (regular member)", + "Joe Sepi @joesepi (Guest - Node.js CPC rep)" + ], + "agenda": [ + { + "title": "Reminders", + "items": [ + "Remember to nominate people for the [contributor spotlight](https://github.com/nodejs/node/blob/main/doc/contributing/reconizing-contributors.md#bi-monthly-contributor-spotlight)" + ] + }, + { + "title": "Strategic Initiatives", + "items": [] + } + ] +} diff --git a/meetings/typescript.meeting.json b/meetings/typescript.meeting.json new file mode 100644 index 0000000..1e990e7 --- /dev/null +++ b/meetings/typescript.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "TypeScript team", + "host": "Node.js", + "calendar": { + "filter": "TypeScript team meeting", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "typescript" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/95749675148", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["Typescript team: @nodejs/typescript"] +} diff --git a/meetings/userfeedback.meeting.json b/meetings/userfeedback.meeting.json new file mode 100644 index 0000000..af261b2 --- /dev/null +++ b/meetings/userfeedback.meeting.json @@ -0,0 +1,21 @@ +{ + "name": "User Feedback", + "host": "Node.js", + "calendar": { + "filter": "Node.js User Feedback Meeting", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "user-feedback", + "agendaLabel": "user-feedback-agenda" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/353642832", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["User Feedback Members: @nodejs/user-feedback"] +} diff --git a/meetings/uvwasi.meeting.json b/meetings/uvwasi.meeting.json new file mode 100644 index 0000000..738735a --- /dev/null +++ b/meetings/uvwasi.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "uvwasi team", + "host": "Node.js", + "calendar": { + "filter": "Node.js uvwasi team meeting", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "uvwasi", + "agendaLabel": "uvwasi-agenda" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/91612484369" + }, + "invited": ["uvwasi team: @nodejs/wasi"] +} diff --git a/meetings/web-server-frameworks.meeting.json b/meetings/web-server-frameworks.meeting.json new file mode 100644 index 0000000..4aebe92 --- /dev/null +++ b/meetings/web-server-frameworks.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Web Server Frameworks", + "host": "Node.js", + "calendar": { + "filter": "Web Server Frameworks", + "url": "https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" + }, + "github": { + "owner": "nodejs", + "repo": "web-server-frameworks" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "https://zoom.us/j/96287211361", + "observer": "https://www.youtube.com/c/nodejs+foundation/live" + }, + "invited": ["Web Server Frameworks team: @nodejs/web-server-frameworks"] +} diff --git a/meetings/web.meeting.json b/meetings/web.meeting.json new file mode 100644 index 0000000..e5594e8 --- /dev/null +++ b/meetings/web.meeting.json @@ -0,0 +1,20 @@ +{ + "name": "Web Team", + "host": "Node.js", + "calendar": { + "filter": "Web Team Monthly", + "url": "https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" + }, + "github": { + "owner": "nodejs", + "repo": "web-team", + "agendaLabel": "web-agenda" + }, + "hackmd": { + "team": "openjs-nodejs" + }, + "joining": { + "participant": "Check LFX for your invite link. If you are having issues, reach out to @avivkeller." + }, + "invited": ["@nodejs/web"] +} diff --git a/package-lock.json b/package-lock.json index 5f1c1f3..baf048e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,8 @@ "@hackmd/api": "^2.5.0", "@octokit/rest": "^22.0.0", "commander": "^14.0.1", - "dedent": "^1.6.0", - "dotenv": "^17.2.2", - "ical": "^0.8.0" + "ical": "^0.8.0", + "mustache": "^4.2.0" }, "bin": { "create-node-meeting-artifacts": "create-node-meeting-artifacts.mjs" @@ -22,7 +21,7 @@ "devDependencies": { "@eslint/js": "^9.33.0", "@types/ical": "^0.8.3", - "@types/properties-parser": "^0.3.3", + "@types/mustache": "^4.2.6", "eslint": "^9.33.0", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-jsdoc": "^54.0.0", @@ -571,25 +570,12 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/node": { - "version": "24.2.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz", - "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", + "node_modules/@types/mustache": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-4.2.6.tgz", + "integrity": "sha512-t+8/QWTAhOFlrF1IVZqKnMRJi84EgkIK5Kh0p2JV4OLywUvCwJPFxbJAl7XAow7DVIHsF+xW9f1MVzg0L6Szjw==", "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.10.0" - } - }, - "node_modules/@types/properties-parser": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@types/properties-parser/-/properties-parser-0.3.3.tgz", - "integrity": "sha512-VZhGpE+QQ2JNbaY4B4Y2iM/jdUsq3HO75uBKLk07VT6P2Kg1aifeYL6I3RosFniSdAb4PtuH5UaY8jXU7JeIYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } + "license": "MIT" }, "node_modules/@typescript-eslint/types": { "version": "8.39.1", @@ -1119,20 +1105,6 @@ } } }, - "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1149,18 +1121,6 @@ "node": ">=0.4.0" } }, - "node_modules/dotenv": { - "version": "17.2.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", - "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1997,6 +1957,15 @@ "dev": true, "license": "MIT" }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/napi-postinstall": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", @@ -2314,13 +2283,6 @@ "node": ">= 0.8.0" } }, - "node_modules/undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "dev": true, - "license": "MIT" - }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", diff --git a/package.json b/package.json index a29b4d4..223537c 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,13 @@ "@hackmd/api": "^2.5.0", "@octokit/rest": "^22.0.0", "commander": "^14.0.1", - "dedent": "^1.6.0", - "dotenv": "^17.2.2", - "ical": "^0.8.0" + "ical": "^0.8.0", + "mustache": "^4.2.0" }, "devDependencies": { "@eslint/js": "^9.33.0", "@types/ical": "^0.8.3", - "@types/properties-parser": "^0.3.3", + "@types/mustache": "^4.2.6", "eslint": "^9.33.0", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-jsdoc": "^54.0.0", diff --git a/src/calendar.mjs b/src/calendar.mjs index 7e4f59a..995ed0d 100644 --- a/src/calendar.mjs +++ b/src/calendar.mjs @@ -24,10 +24,10 @@ export const getNextWeek = (start = new Date()) => { /** * Finds the next meeting event in any iCal feed for the current week * @param {ical.CalendarComponent[]} allEvents - The events - * @param {import('./types').MeetingConfig} meetingConfig - Meeting configuration object + * @param {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration object * @returns {Promise} The date of the next meeting, or null if no meeting is found */ -export const findNextMeetingDate = async (allEvents, { properties }) => { +export const findNextMeetingDate = async (allEvents, { calendar }) => { const [weekStart, weekEnd] = getNextWeek(); const filteredEvents = allEvents.filter( @@ -35,7 +35,7 @@ export const findNextMeetingDate = async (allEvents, { properties }) => { // The event must be recurring event.rrule && // The event must match our filter - (event.summary || event.description)?.includes(properties.CALENDAR_FILTER) + (event.summary || event.description)?.includes(calendar.filter) ); for (const event of filteredEvents) { diff --git a/src/config.mjs b/src/config.mjs index bc21d78..52b66c3 100644 --- a/src/config.mjs +++ b/src/config.mjs @@ -1,8 +1,3 @@ -import { homedir } from 'node:os'; -import { join, dirname } from 'node:path'; - -const defaultMeetingsDirectory = join(homedir(), '.make-node-meeting'); - /** * @type {import('./types.d.ts').AppConfig} Environment configuration object */ @@ -15,11 +10,4 @@ export default { // HackMD API token for authentication apiToken: process.env.HACKMD_API_TOKEN, }, - - // Directory paths for templates, output, and configuration - directories: { - config: process.env.MEETINGS_CONFIG_DIR || './', - output: process.env.MEETINGS_OUTPUT_DIR || defaultMeetingsDirectory, - templates: join(dirname(import.meta.dirname), 'templates'), - }, }; diff --git a/src/constants.mjs b/src/constants.mjs index 897a892..6f1c92f 100644 --- a/src/constants.mjs +++ b/src/constants.mjs @@ -1,15 +1,13 @@ -// Default configuration values -export const DEFAULT_CONFIG = { - // Default GitHub organization name - githubOrg: 'nodejs', - // Default Host of the Meeting - host: 'Node.js', -}; +// Default host used in meeting titles when a config does not set one +export const DEFAULT_HOST = 'Node.js'; + +// Host that uses the OpenJS Foundation public calendar +export const OPENJS_HOST = 'OpenJS Foundation'; -// Time constants for date calculations -export const TIME_CONSTANTS = { - // Week in milliseconds for calendar search - WEEK_IN_MS: 7 * 24 * 60 * 60 * 1000, +// Public "add to your calendar" pages linked from the minutes +export const CALENDAR_PAGES = { + node: 'https://nodejs.org/calendar', + openjs: 'https://calendar.openjsf.org', }; // Relevant Timezones for Date manipulation diff --git a/src/github.mjs b/src/github.mjs index 97c83b5..1589a63 100644 --- a/src/github.mjs +++ b/src/github.mjs @@ -1,7 +1,5 @@ import { Octokit } from '@octokit/rest'; -import { DEFAULT_CONFIG } from './constants.mjs'; - /** * Creates a GitHub API client * @param {import('./types.d.ts').AppConfig} config - Application configuration @@ -11,50 +9,41 @@ export const createGitHubClient = ({ githubToken: auth, verbose }) => new Octokit({ auth, log: verbose ? console : undefined }); /** - * Creates GitHub issue with meeting information and Google Doc link + * Creates a GitHub issue with meeting information * @param {import('@octokit/rest').Octokit} githubClient - Authenticated GitHub API client - * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration object + * @param {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration object * @param {string} title - Issue title * @param {string} content - Issue content - * @returns {Promise} Created issue data + * @returns {Promise} Created issue data */ -export const createGitHubIssue = async ( - { rest }, - { properties }, - title, - content -) => { - const githubOrg = properties.USER ?? DEFAULT_CONFIG.githubOrg; - - const issueLabel = properties.ISSUE_LABEL - ? [properties.ISSUE_LABEL] - : undefined; +export const createGitHubIssue = async ({ rest }, meeting, title, content) => { + const { owner, repo, issueLabels } = meeting.github; // Create the GitHub issue with meeting information const response = await rest.issues.create({ - owner: githubOrg, - repo: properties.REPO, + owner, + repo, title, body: content, - labels: issueLabel, + labels: issueLabels, }); return response.data; }; /** - * Creates or updates a GitHub issue with meeting information and Google Doc link + * Creates or updates a GitHub issue with meeting information * @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 {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration object * @param {string} title - Issue title * @param {string} content - Issue content - * @returns {Promise} Created issue data + * @returns {Promise} Created issue data */ export const createOrUpdateGitHubIssue = async ( githubClient, { force }, - meetingConfig, + meeting, title, content ) => { @@ -62,7 +51,7 @@ export const createOrUpdateGitHubIssue = async ( const existingIssue = await findGitHubIssueByTitle( githubClient, title, - meetingConfig + meeting ); if (existingIssue) { @@ -71,7 +60,7 @@ export const createOrUpdateGitHubIssue = async ( githubClient, existingIssue.number, content, - meetingConfig + meeting ); } @@ -79,60 +68,62 @@ export const createOrUpdateGitHubIssue = async ( } } - return createGitHubIssue(githubClient, meetingConfig, title, content); + return createGitHubIssue(githubClient, meeting, title, content); }; /** - * Sorts issues by repository - * @param {Array} issues The issues to sort - * @returns {Promise<{ [key: string]: Array }>} Sorted issues + * Groups issues by their repository + * @param {Array} issues The issues to group + * @returns {Array} Issues grouped by repository */ -export const sortIssuesByRepo = issues => - issues.reduce((obj, issue) => { - (obj[issue.repository_url.split('/').slice(-2).join('/')] ||= []).push( - issue - ); - return obj; - }, {}); +export const sortIssuesByRepo = issues => { + const byRepo = new Map(); + + for (const issue of issues) { + const repo = issue.repository_url.split('/').slice(-2).join('/'); + + if (!byRepo.has(repo)) { + byRepo.set(repo, []); + } + + byRepo.get(repo).push(issue); + } + + return [...byRepo].map(([repo, repoIssues]) => ({ + repo, + issues: repoIssues, + })); +}; /** * 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 - * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration + * @param {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration */ -export const updateGitHubIssue = async ( - { rest }, - number, - content, - { properties } -) => { - const githubOrg = properties.USER ?? DEFAULT_CONFIG.githubOrg; +export const updateGitHubIssue = async ({ rest }, number, content, meeting) => { + const { owner, repo } = meeting.github; return rest.issues.update({ issue_number: number, body: content, - owner: githubOrg, - repo: properties.REPO, + owner, + repo, }); }; /** - * Fetches GitHub issue from a repo with a given title + * Fetches a 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 + * @param {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration */ -export const findGitHubIssueByTitle = async ( - githubClient, - title, - { properties } -) => { - const githubOrg = properties.USER ?? DEFAULT_CONFIG.githubOrg; +export const findGitHubIssueByTitle = async (githubClient, title, meeting) => { + const { owner, repo } = meeting.github; const issues = await githubClient.request('GET /search/issues', { - q: `in:title repo:"${githubOrg}/${properties.REPO}" "${title}"`, + q: `in:title repo:"${owner}/${repo}" "${title}"`, advanced_search: true, per_page: 1, }); @@ -141,23 +132,17 @@ export const findGitHubIssueByTitle = async ( }; /** - * Fetches GitHub issues from all repositories in an organization with a specific label + * Fetches GitHub issues from all repositories in an organization with the agenda label * @param {import('@octokit/rest').Octokit} githubClient - Authenticated GitHub API client - * @param {import('./types.d.ts').AppConfig} config - Application configuration - * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration - * @returns {Promise<{ [key: string]: Array }>} Meeting agenda + * @param {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration + * @returns {Promise>} Agenda issues grouped by repository */ -export const getAgendaIssues = async ( - githubClient, - { meetingGroup }, - { properties } -) => { - const githubOrg = properties.USER ?? DEFAULT_CONFIG.githubOrg; - const agendaTag = properties.AGENDA_TAG ?? `${meetingGroup}-agenda`; +export const getAgendaIssues = async (githubClient, meeting) => { + const { owner, agendaLabel } = meeting.github; // Get all issues/PRs in the organization const issues = await githubClient.paginate('GET /search/issues', { - q: `is:open label:${agendaTag} org:${githubOrg}`, + q: `is:open label:${agendaLabel} org:${owner}`, advanced_search: true, }); diff --git a/src/hackmd.mjs b/src/hackmd.mjs index 917bb8c..ca52536 100644 --- a/src/hackmd.mjs +++ b/src/hackmd.mjs @@ -5,12 +5,12 @@ import { HACKMD_DEFAULT_PERMISSIONS } from './constants.mjs'; /** * Creates a HackMD API client * @param {import('./types.d.ts').AppConfig} config - Application configuration - * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration - * @returns {HackMDClient} Configured HackMD API client + * @param {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration + * @returns {HackMDAPI} Configured HackMD API client */ -export const createHackMDClient = ({ hackmd: { apiToken } }, meetingConfig) => { - // Use team-specific API endpoint if teamName is provided in meeting config - const teamName = meetingConfig.properties.HACKMD_TEAM_NAME; +export const createHackMDClient = ({ hackmd: { apiToken } }, meeting) => { + // Use the team-specific API endpoint if a team is provided in meeting config + const teamName = meeting.hackmd?.team; const baseURL = teamName ? `https://api.hackmd.io/v1/teams/${teamName}` @@ -24,7 +24,7 @@ export const createHackMDClient = ({ hackmd: { apiToken } }, meetingConfig) => { * @param {HackMDAPI} hackmdClient - HackMD API client * @param {string} title - Document title * @param {string} content - Document content in Markdown - * @returns {Promise} Created note data with ID and URLs + * @returns {Promise} Created note data with ID and URLs */ export const createMeetingNotesDocument = (hackmdClient, title, content) => { const noteOptions = { @@ -41,11 +41,11 @@ export const createMeetingNotesDocument = (hackmdClient, title, content) => { }; /** - * Creates a new meeting notes document in HackMD with appropriate tags + * Fetches or creates a meeting notes document in HackMD * @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 + * @returns {Promise} The created / fetched note */ export const getOrCreateMeetingNotesDocument = async ( hackmdClient, @@ -65,11 +65,11 @@ export const getOrCreateMeetingNotesDocument = async ( }; /** - * Updates an existing meeting notes document in HackMD with retry logic - * @param {HackMDClient} hackmdClient - HackMD API client + * Updates an existing meeting notes document in HackMD + * @param {HackMDAPI} hackmdClient - HackMD API client * @param {string} noteId - HackMD note ID * @param {string} content - Updated document content in Markdown - * @returns {Promise} Updated note data + * @returns {Promise} Updated note data */ export const updateMeetingNotesDocument = (hackmdClient, noteId, content) => { // apparently it can return either { note: {...} } or just {...} diff --git a/src/meeting.mjs b/src/meeting.mjs index 09bd62c..d3236d1 100644 --- a/src/meeting.mjs +++ b/src/meeting.mjs @@ -1,179 +1,159 @@ import { readFile } from 'node:fs/promises'; -import { join } from 'node:path'; -import { parse } from 'dotenv'; +import mustache from 'mustache'; -import { DEFAULT_CONFIG } from './constants.mjs'; +import { CALENDAR_PAGES, DEFAULT_HOST, OPENJS_HOST } from './constants.mjs'; import * as dates from './utils/dates.mjs'; -import * as templates from './utils/templates.mjs'; import * as urls from './utils/urls.mjs'; +// The single template shared by every meeting issue and every minutes document. +const TEMPLATE_URL = new URL('../templates/meeting.mustache', import.meta.url); + /** - * Reads and parses meeting configuration from template files - * @param {import('./types.d.ts').AppConfig} config - Application configuration - * @returns {Promise} Meeting configuration object + * Resolves the path to a meeting config for a given group + * @param {string} group - Meeting group identifier + * @returns {URL} The config file URL */ -export const readMeetingConfig = async config => { - // Read all template files asynchronously - const invited = await readFile( - join(config.directories.templates, `invited_${config.meetingGroup}`), - 'utf8' - ); - - const observers = await readFile( - join(config.directories.templates, `observers_${config.meetingGroup}`), - 'utf8' - ); - - const baseMeetingInfo = await readFile( - join(config.directories.templates, `meeting_base_${config.meetingGroup}`), - 'utf8' - ); +export const configURL = group => + new URL(`../meetings/${group}.meeting.json`, import.meta.url); - return { - invited, - observers, - baseMeetingInfo, - properties: parse(baseMeetingInfo), - }; +/** + * Loads and normalizes a meeting configuration from its JSON file + * @param {string} group - Meeting group identifier (the config filename stem) + * @returns {Promise} Meeting configuration object + */ +export const load = async group => { + /** @type {import('./types.d.ts').MeetingConfig} */ + const meeting = JSON.parse(await readFile(configURL(group), 'utf8')); + + // Apply defaults so every config exposes an identical, fully-populated shape. + meeting.group = group; + meeting.host ??= DEFAULT_HOST; + meeting.github.agendaLabel ??= `${group}-agenda`; + meeting.observers ??= []; + meeting.agenda ??= []; + + return meeting; }; /** * Generates the meeting title based on the meeting configuration - * @param {import('./types.d.ts').AppConfig} config - Application configuration - * @param {import('./types.d.ts').MeetingConfig} meetingConfig + * @param {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration * @param {Date} meetingDate - Date of the meeting - * @returns {Promise} Generated meeting title + * @returns {string} Generated meeting title */ -export const generateMeetingTitle = (config, meetingConfig, meetingDate) => { - const props = meetingConfig.properties; - - const host = props.HOST ?? DEFAULT_CONFIG.host; - const groupName = props.GROUP_NAME ?? config.meetingGroup; +export const generateMeetingTitle = (meeting, meetingDate) => { + const host = meeting.host ?? DEFAULT_HOST; + const name = meeting.name ?? meeting.group; const utcShort = meetingDate.toISOString().split('T')[0]; - return `${host} ${groupName} Meeting ${utcShort}`; + return `${host} ${name} Meeting ${utcShort}`; }; /** - * Generates the meeting agenda from the list of agenda issues - * @param {Array<{ [key: string]: Array }>} agendaIssues - List of agenda issues - * @returns {Promise} Formatted meeting agenda + * Escapes characters that would break markdown link syntax in issue titles + * @param {string} title - The raw issue title + * @returns {string} The escaped title */ -export const generateMeetingAgenda = agendaIssues => { - // Format issues as markdown - let agendaMarkdown = ''; +const escapeTitle = title => title.replace(/([[\]])/g, '\\$1'); - Object.entries(agendaIssues).forEach(([repoName, issues]) => { - if (issues.length > 0) { - agendaMarkdown += `### ${repoName}\n\n`; +/** + * Resolves the public "add to your calendar" page for a meeting + * @param {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration + * @returns {string} The calendar page URL + */ +export const calendarPage = meeting => + meeting.calendar.page ?? + (meeting.host === OPENJS_HOST ? CALENDAR_PAGES.openjs : CALENDAR_PAGES.node); - issues.forEach(issue => { - // Escape markdown characters in title - const cleanTitle = issue.title.replace(/([[\]])/g, '\\$1'); +/** + * Formats a date's UTC time of day as `HH:MM` + * @param {Date} date - The date + * @returns {string} The UTC time, e.g. "13:00" + */ +const utcTimeOfDay = date => + `${String(date.getUTCHours()).padStart(2, '0')}:${String( + date.getUTCMinutes() + ).padStart(2, '0')}`; + +/** + * Resolves the joining details for a specific meeting occurrence. + * + * For meetings that alternate between sessions (e.g. the TSC's 13:00 UTC and + * 17:00 UTC slots, each with its own Zoom link), the session whose `time` + * matches the occurrence's UTC time of day is selected. When no session matches + * (such as a `--dry-run` with no real occurrence), every session is listed. + * @param {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration + * @param {Date} meetingDate - Date of the meeting occurrence + * @returns {{ participant?: string, observer?: string, notes?: string, sessions?: object[] }} Resolved joining details + */ +export const resolveJoining = (meeting, meetingDate) => { + const { participant, observer, notes, sessions } = meeting.joining; - agendaMarkdown += `* ${cleanTitle} [#${issue.number}](${issue.html_url})\n`; - }); + if (!sessions?.length) { + return { participant, observer, notes }; + } - agendaMarkdown += '\n'; - } - }); + const match = sessions.find( + session => session.time === utcTimeOfDay(meetingDate) + ); - return agendaMarkdown.trim(); + return match + ? { participant: match.participant, observer, notes } + : { observer, notes, sessions }; }; /** - * Generates meeting issue content directly (replaces make-node-meeting.sh) - * @param {import('./types.d.ts').AppConfig} config - Application configuration - * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration - * @param {string} meetingAgenda - Meeting agenda (optional) - * @param {string} minutesDocLink - Minutes document link (optional) + * Builds the rendering context shared by the issue and minutes templates + * @param {import('./types.d.ts').MeetingConfig} meeting - Meeting configuration * @param {Date} meetingDate - Date of the meeting + * @param {Array} agenda - Agenda issues grouped by repo + * @param {Array<{ title: string, url: string }>} links - External links (minutes, issue) + * @param {{ isMinutes?: boolean, title?: string }} options - Rendering options + * @returns {object} The mustache rendering context */ -export const generateMeetingIssue = async ( - config, - meetingConfig, +export const createTemplateContext = ( + meeting, meetingDate, - meetingAgenda, - minutesDocLink + agenda, + links, + { isMinutes = false, title = '' } = {} ) => { - const props = meetingConfig.properties; - - const joiningInstructions = props.JOINING_INSTRUCTIONS ?? ''; - const githubOrg = props.USER ?? DEFAULT_CONFIG.githubOrg; - - const groupName = props.GROUP_NAME ?? config.meetingGroup; - const agendaTag = props.AGENDA_TAG ?? `${config.meetingGroup}-agenda`; - - // Format the meeting date and timezones const { utc, timezones } = dates.formatTimezones(meetingDate); - // Generate timezone conversion links - const timeAndDateLink = urls.generateTimeAndDateLink(meetingDate, groupName); - const wolframLink = urls.generateWolframAlphaLink(meetingDate); - - // Generate timezone table - const timezoneTable = timezones - .map(({ label, time }) => `| ${label.padEnd(13)} | ${time} |`) - .join('\n'); - - // Read and process the meeting issue template - const templatePath = join(config.directories.templates, 'meeting_issue.md'); - - const template = await readFile(templatePath, 'utf8'); - - const templateVariables = { - UTC_TIME: utc, - TIMEZONE_TABLE: timezoneTable, - TIME_AND_DATE_LINK: timeAndDateLink, - WOLFRAM_ALPHA_LINK: wolframLink, - AGENDA_LABEL: agendaTag, - GITHUB_ORG: githubOrg, - AGENDA_CONTENT: meetingAgenda ?? '*No agenda items found.*', - INVITEES: meetingConfig.invited, - JOINING_INSTRUCTIONS: joiningInstructions, - MINUTES_DOC: minutesDocLink, - OBSERVERS: meetingConfig.observers ?? '', + return { + meeting, + title, + utc, + timezones, + timeAndDateLink: urls.generateTimeAndDateLink(meetingDate, meeting.name), + wolframLink: urls.generateWolframAlphaLink(meetingDate), + agendaLabel: meeting.github.agendaLabel, + owner: meeting.github.owner, + calendarPage: calendarPage(meeting), + joining: resolveJoining(meeting, meetingDate), + agenda: agenda.map(({ repo, issues }) => ({ + repo, + issues: issues.map(issue => ({ + number: issue.number, + title: escapeTitle(issue.title), + html_url: issue.html_url, + })), + })), + hasAgenda: agenda.some(({ issues }) => issues.length > 0), + links, + isMinutes, }; - - return templates.parseVariables(template, templateVariables); }; /** - * Creates meeting minutes document content by processing template - * @param {import('./types.d.ts').AppConfig} config - Application configuration - * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration - * @param {string} meetingTitle - Meeting title - * @param {string} meetingAgenda - Meeting agenda (optional) - * @param {string} minutesDocLink - Minutes document link (optional) - * @param {string} githubIssueLink - GitHub issue link (optional) - * @returns {Promise} Processed minutes document content + * Renders meeting content (issue or minutes) from the shared template + * @param {object} context - The rendering context from createTemplateContext + * @returns {Promise} The rendered markdown */ -export const generateMeetingMinutes = async ( - config, - meetingConfig, - meetingTitle, - meetingAgenda, - minutesDocLink, - githubIssueLink -) => { - // Read and process the meeting minutes template - const templatePath = join( - config.directories.templates, - `minutes_base_${config.meetingGroup}` - ); - - const template = await readFile(templatePath, 'utf8'); - - const templateVariables = { - TITLE: meetingTitle, - AGENDA_CONTENT: meetingAgenda, - INVITED: meetingConfig.invited, - OBSERVERS: meetingConfig.observers, - MINUTES_DOC: minutesDocLink, - GITHUB_ISSUE: githubIssueLink, - }; +export const render = async context => { + const template = await readFile(TEMPLATE_URL, 'utf8'); - return templates.parseVariables(template, templateVariables); + return mustache.render(template, context); }; diff --git a/src/types.d.ts b/src/types.d.ts index 6e95690..791d1e2 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,17 +1,23 @@ +import type { RestEndpointMethodTypes } from '@octokit/rest'; + /** - * Application configuration object + * A GitHub issue as returned by the REST API. + */ +export type GitHubIssue = + RestEndpointMethodTypes['issues']['create']['response']['data']; + +/** + * Environment configuration object (credentials read from the environment). */ export interface EnvironmentConfig { /** GitHub personal access token */ githubToken: string; /** HackMD API configuration */ hackmd: HackMDConfig; - /** Directory paths configuration */ - directories: DirectoryConfig; } /** - * CLI configuration object + * CLI flags parsed from the command line. */ export interface CLIConfig { verbose: boolean; @@ -23,7 +29,7 @@ export interface CLIConfig { export type AppConfig = EnvironmentConfig & CLIConfig; /** - * HackMD API configuration + * HackMD API configuration. */ export interface HackMDConfig { /** HackMD API token */ @@ -31,49 +37,93 @@ export interface HackMDConfig { } /** - * Directory paths configuration + * A single repository's worth of agenda issues, grouped for rendering. */ -export interface DirectoryConfig { - /** Templates directory path */ - templates: string; +export interface AgendaGroup { + /** Repository in `owner/name` form */ + repo: string; + /** Issues/PRs labelled for the agenda within this repository */ + issues: GitHubIssue[]; } /** - * Meeting configuration object parsed from templates + * A manually-curated agenda section declared in a meeting config. */ -export interface MeetingConfig { - /** Invited attendees list */ - invited: string; - /** Observers list */ - observers: string; - /** Base meeting information */ - baseMeetingInfo: string; - /** Parsed meeting properties */ - properties: MeetingProperties; +export interface AgendaSection { + /** Section heading */ + title: string; + /** Optional descriptive text rendered under the heading */ + description?: string; + /** Bullet list items (typically links) */ + items: string[]; } /** - * Meeting properties parsed from template file + * One alternating meeting session, identified by its UTC time of day. */ -export interface MeetingProperties { - /** ICAL to search for events */ - ICAL_URL?: string; - /** Text filter for calendar events */ - CALENDAR_FILTER?: string; - /** GitHub repository owner/user */ - USER?: string; - /** GitHub repository name */ - REPO?: string; - /** Host organization name (e.g. "Node.js", "OpenJS Foundation") */ - HOST?: string; - /** Display name for the meeting group */ - GROUP_NAME?: string; - /** Meeting agenda tag for labeling issues */ - AGENDA_TAG?: string; - /** Optional GitHub issue label */ - ISSUE_LABEL?: string; - /** HackMD team name for creating documents */ - HACKMD_TEAM_NAME?: string; - /** Meeting joining instructions */ - JOINING_INSTRUCTIONS?: string; +export interface MeetingSession { + /** UTC time of day in `HH:MM` form, matched against the occurrence */ + time: string; + /** Where participants join this session (URL) */ + participant: string; +} + +/** + * A meeting configuration, loaded from `meetings/.meeting.json`. + * + * Every meeting — regardless of group — is described by this identical shape, + * which drives both the GitHub issue and the HackMD minutes. + */ +export interface MeetingConfig { + /** The group identifier, derived from the config filename */ + group: string; + /** Human-readable group name, e.g. "Technical Steering Committee (TSC)" */ + name: string; + /** Meeting host, e.g. "Node.js" or "OpenJS Foundation" (defaults to Node.js) */ + host: string; + /** Calendar lookup configuration */ + calendar: { + /** Text used to match the calendar event summary/description */ + filter: string; + /** iCal feed URL to search for the next occurrence */ + url: string; + /** Public "add to your calendar" page (defaults based on host) */ + page?: string; + }; + /** GitHub configuration */ + github: { + /** Organization/user that owns the meeting repository */ + owner: string; + /** Repository where the meeting issue is created */ + repo: string; + /** Label used to collect agenda issues (defaults to `-agenda`) */ + agendaLabel: string; + /** Optional labels applied to the created meeting issue */ + issueLabels?: string[]; + }; + /** HackMD configuration */ + hackmd: { + /** HackMD team name the minutes document is created under */ + team: string; + }; + /** How to join/observe the meeting */ + joining: { + /** Where participants join (URL or short instruction) */ + participant?: string; + /** + * Alternating sessions, each with its own join link, selected by the + * occurrence's UTC time of day (e.g. a 13:00 and a 17:00 slot). + */ + sessions?: MeetingSession[]; + /** Where observers watch the livestream (URL) */ + observer?: string; + /** Any additional joining notes */ + notes?: string; + }; + /** People/teams always invited */ + invited: string[]; + /** People/teams attending as observers (optional) */ + observers?: string[]; + /** Manually-curated agenda sections (optional) */ + agenda?: AgendaSection[]; } diff --git a/src/utils/templates.mjs b/src/utils/templates.mjs deleted file mode 100644 index 78a997e..0000000 --- a/src/utils/templates.mjs +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Process template with variables - * @param {string} template - The template content - * @param {Object} variables - Object with template variables - * @returns {string} Processed template - */ -export const parseVariables = (template, variables) => { - let processed = template; - - for (const [key, value] of Object.entries(variables)) { - const placeholder = `$${key}$`; - - processed = processed.replace( - new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), - value || '' - ); - } - - // Replace any remaining placeholders with empty strings - processed = processed.replace(/\$[A-Z_]+\$/g, ''); - - return processed; -}; diff --git a/templates/invited_Release b/templates/invited_Release deleted file mode 100644 index c52e9be..0000000 --- a/templates/invited_Release +++ /dev/null @@ -1,2 +0,0 @@ -* Release team: @nodejs/release -* LTS team: @nodejs/lts diff --git a/templates/invited_benchmarking b/templates/invited_benchmarking deleted file mode 100644 index 27bd766..0000000 --- a/templates/invited_benchmarking +++ /dev/null @@ -1 +0,0 @@ -* Benchmarking team: @nodejs/benchmarking diff --git a/templates/invited_build b/templates/invited_build deleted file mode 100644 index 832f6f8..0000000 --- a/templates/invited_build +++ /dev/null @@ -1 +0,0 @@ -* Build team: @nodejs/build diff --git a/templates/invited_bundler_collab b/templates/invited_bundler_collab deleted file mode 100644 index bdc97f7..0000000 --- a/templates/invited_bundler_collab +++ /dev/null @@ -1,3 +0,0 @@ -* Bundler Collab Space Members -* OpenJS Foundation Cross Project Council -* All are welcome diff --git a/templates/invited_cross_project_council b/templates/invited_cross_project_council deleted file mode 100644 index 382ca62..0000000 --- a/templates/invited_cross_project_council +++ /dev/null @@ -1,3 +0,0 @@ -* OpenJS Foundation Cross Project Council -* OpenJS Foundation Project Maintainers -* OpenJS Foundation Board of Directors diff --git a/templates/invited_diag b/templates/invited_diag deleted file mode 100644 index f9ef3c9..0000000 --- a/templates/invited_diag +++ /dev/null @@ -1 +0,0 @@ -* Diagnostics team: @nodejs/diagnostics diff --git a/templates/invited_diag_deepdive b/templates/invited_diag_deepdive deleted file mode 100644 index 85934a4..0000000 --- a/templates/invited_diag_deepdive +++ /dev/null @@ -1 +0,0 @@ -* Diagnostics deep dive meetings: @nodejs/diagnostics diff --git a/templates/invited_ecosystem_report b/templates/invited_ecosystem_report deleted file mode 100644 index 7e5a86a..0000000 --- a/templates/invited_ecosystem_report +++ /dev/null @@ -1 +0,0 @@ -* Ecosystem Report Collab Space Members: @openjs/ecosystem-report diff --git a/templates/invited_loaders b/templates/invited_loaders deleted file mode 100644 index 19a79ae..0000000 --- a/templates/invited_loaders +++ /dev/null @@ -1 +0,0 @@ -* Loaders team: @nodejs/loaders diff --git a/templates/invited_modules b/templates/invited_modules deleted file mode 100644 index c21b093..0000000 --- a/templates/invited_modules +++ /dev/null @@ -1 +0,0 @@ -* Modules team: @nodejs/modules diff --git a/templates/invited_next-10 b/templates/invited_next-10 deleted file mode 100644 index 853831a..0000000 --- a/templates/invited_next-10 +++ /dev/null @@ -1 +0,0 @@ -* Next 10 years team: @nodejs/next-10 diff --git a/templates/invited_outreach b/templates/invited_outreach deleted file mode 100644 index 311fcd5..0000000 --- a/templates/invited_outreach +++ /dev/null @@ -1 +0,0 @@ -* Outreach Members: @nodejs/outreach diff --git a/templates/invited_package-maintenance b/templates/invited_package-maintenance deleted file mode 100644 index f8d1b58..0000000 --- a/templates/invited_package-maintenance +++ /dev/null @@ -1 +0,0 @@ -* Package Maintenance team: @nodejs/package-maintenance diff --git a/templates/invited_package_metadata_interop b/templates/invited_package_metadata_interop deleted file mode 100644 index 74a9010..0000000 --- a/templates/invited_package_metadata_interop +++ /dev/null @@ -1 +0,0 @@ -* Package Metadata Interoperability Collab Space Members: @openjs/package-metadata-interop diff --git a/templates/invited_security-wg b/templates/invited_security-wg deleted file mode 100644 index 3b2c8b6..0000000 --- a/templates/invited_security-wg +++ /dev/null @@ -1 +0,0 @@ -* Security wg team: @nodejs/security-wg diff --git a/templates/invited_security_collab b/templates/invited_security_collab deleted file mode 100644 index 08196d7..0000000 --- a/templates/invited_security_collab +++ /dev/null @@ -1,5 +0,0 @@ -* OpenJS Foundation Security Collab Space Members -* OpenJS Foundation Cross Project Council -* OpenJS Foundation Project Maintainers -* OpenJS Foundation Board of Directors -* All are welcome diff --git a/templates/invited_standards b/templates/invited_standards deleted file mode 100644 index 382ca62..0000000 --- a/templates/invited_standards +++ /dev/null @@ -1,3 +0,0 @@ -* OpenJS Foundation Cross Project Council -* OpenJS Foundation Project Maintainers -* OpenJS Foundation Board of Directors diff --git a/templates/invited_sustainability_collab b/templates/invited_sustainability_collab deleted file mode 100644 index 2a94766..0000000 --- a/templates/invited_sustainability_collab +++ /dev/null @@ -1 +0,0 @@ -* Ecosystem Report Collab Space Members: @openjs-foundation/sustainability-collaboration-space \ No newline at end of file diff --git a/templates/invited_test_runner b/templates/invited_test_runner deleted file mode 100644 index cc05875..0000000 --- a/templates/invited_test_runner +++ /dev/null @@ -1 +0,0 @@ -* @nodejs/test_runner \ No newline at end of file diff --git a/templates/invited_tooling b/templates/invited_tooling deleted file mode 100644 index 84eb25f..0000000 --- a/templates/invited_tooling +++ /dev/null @@ -1 +0,0 @@ -* Tooling team: @nodejs/tooling diff --git a/templates/invited_tsc b/templates/invited_tsc deleted file mode 100644 index 27d09bf..0000000 --- a/templates/invited_tsc +++ /dev/null @@ -1,27 +0,0 @@ -* Antoine du Hamel @aduh95 (voting member) -* Yagiz Nizipli @anonrig (voting member) -* Benjamin Gruenbaum @benjamingr (voting member) -* Ruben Bridgewater @BridgeAR (voting member) -* Gireesh Punathil @gireeshpunathil (voting member) -* James Snell @jasnell (voting member) -* Joyee Cheung @joyeecheung (voting member) -* Chengzhong Wu @legendecas (voting member) -* Marco Ippolito @marco-ippolito (voting member) -* Matteo Collina @mcollina (voting member) -* Filip Skokan @panva (voting member) -* Rafael Gonzaga @RafaelGSS (voting member) -* Darshan Sen @RaisinTen (voting member) -* Richard Lau @richardlau (voting member) -* Robert Nagy @ronag (voting member) -* Ruy Adorno @ruyadorno (voting member) -* Paolo Insogna @ShogunPanda (voting member) -* Michaël Zasso @targos (voting member) -* Tobias Nießen @tniessen (voting member) -* Beth Griggs @BethGriggs (regular member) -* Ben Noordhuis @bnoordhuis (regular member) -* Colin Ihrig @cjihrig (regular member) -* Geoffrey Booth @GeoffreyBooth (regular member) -* Moshe Atlow @MoLow (regular member) -* Shelley Vohr @codebytere (regular member) -* Rich Trott @Trott (regular member) -* Joe Sepi @joesepi (Guest - Node.js CPC rep) diff --git a/templates/invited_typescript b/templates/invited_typescript deleted file mode 100644 index fc6fbe7..0000000 --- a/templates/invited_typescript +++ /dev/null @@ -1 +0,0 @@ -* Typescript team: @nodejs/typescript diff --git a/templates/invited_userfeedback b/templates/invited_userfeedback deleted file mode 100644 index f591342..0000000 --- a/templates/invited_userfeedback +++ /dev/null @@ -1 +0,0 @@ -* User Feedback Members: @nodejs/user-feedback diff --git a/templates/invited_uvwasi b/templates/invited_uvwasi deleted file mode 100644 index 09020b2..0000000 --- a/templates/invited_uvwasi +++ /dev/null @@ -1 +0,0 @@ -* uvwasi team: @nodejs/wasi diff --git a/templates/invited_web b/templates/invited_web deleted file mode 100644 index aa32a46..0000000 --- a/templates/invited_web +++ /dev/null @@ -1 +0,0 @@ -* @nodejs/web \ No newline at end of file diff --git a/templates/invited_web-server-frameworks b/templates/invited_web-server-frameworks deleted file mode 100644 index e68363b..0000000 --- a/templates/invited_web-server-frameworks +++ /dev/null @@ -1 +0,0 @@ -* Web Server Frameworks team: @nodejs/web-server-frameworks diff --git a/templates/meeting.mustache b/templates/meeting.mustache new file mode 100644 index 0000000..cb614da --- /dev/null +++ b/templates/meeting.mustache @@ -0,0 +1,114 @@ +{{#isMinutes}}# {{{title}}} + +{{/isMinutes}}## Time + +**UTC {{utc}}**: + +| Timezone | Date/Time | +| -------- | --------- | +{{#timezones}} +| {{{label}}} | {{{time}}} | +{{/timezones}} + +Or in your local time: + +* {{{timeAndDateLink}}} +* or {{{wolframLink}}} + +## Links + +{{#isMinutes}} +* **Recording**: +{{/isMinutes}} +{{#links}} +* {{title}}: <{{{url}}}> +{{/links}} + +## {{#isMinutes}}Present{{/isMinutes}}{{^isMinutes}}Invited{{/isMinutes}} + +{{#meeting.invited}} +* {{{.}}} +{{/meeting.invited}} + +### Observers/Guests + +{{#meeting.observers}} +* {{{.}}} +{{/meeting.observers}} +{{^meeting.observers}} +_None._ +{{/meeting.observers}} + +## Agenda +{{#isMinutes}} + +### Announcements + +_No announcements._ +{{#meeting.agenda}} + +### {{{title}}} +{{#description}} + +{{{.}}} +{{/description}} +{{#items}} +* {{{.}}} +{{/items}} +{{/meeting.agenda}} +{{/isMinutes}} + +### Issues and Pull Requests + +Extracted from **{{agendaLabel}}** labelled issues and pull requests from the **{{owner}} org** prior to the meeting. +{{#hasAgenda}} +{{#agenda}} + +#### {{{repo}}} + +{{#issues}} +* {{{title}}} [#{{number}}]({{{html_url}}}) +{{/issues}} +{{/agenda}} +{{/hasAgenda}} +{{^hasAgenda}} + +_No agenda items found._ +{{/hasAgenda}} +{{#isMinutes}} + +## Q&A, Other + +## Upcoming Meetings + +* **Calendar**: <{{{calendarPage}}}> + +Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. +{{/isMinutes}} +{{^isMinutes}} + +## Joining the meeting + +{{#joining.participant}} +* To join the meeting: {{{.}}} +{{/joining.participant}} +{{#joining.sessions}} +* To join the {{time}} UTC meeting: {{{participant}}} +{{/joining.sessions}} +{{#joining.observer}} +* To watch the livestream: <{{{.}}}> +{{/joining.observer}} +{{#joining.notes}} +* {{{.}}} +{{/joining.notes}} + +--- + +**Invitees** + +Please use the following emoji reactions in this post to indicate your availability. + +* :+1: - Attending +* :-1: - Not attending +* :confused: - Not sure +{{/isMinutes}} diff --git a/templates/meeting_base_Release b/templates/meeting_base_Release deleted file mode 100644 index 9d6ba2b..0000000 --- a/templates/meeting_base_Release +++ /dev/null @@ -1,19 +0,0 @@ -CALENDAR_FILTER="Node.js Release Working Group Monthly Meeting" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" -USER="nodejs" -REPO="Release" -GROUP_NAME="Release WorkGroup" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -Join URL: - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure -" diff --git a/templates/meeting_base_benchmarking b/templates/meeting_base_benchmarking deleted file mode 100644 index bf88861..0000000 --- a/templates/meeting_base_benchmarking +++ /dev/null @@ -1,11 +0,0 @@ -CALENDAR_FILTER="Benchmarking WG Meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="benchmarking" -GROUP_NAME="Benchmarking WorkGroup" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: -* For those who just want to watch: -* youtube admin page: " diff --git a/templates/meeting_base_build b/templates/meeting_base_build deleted file mode 100644 index 26e65c5..0000000 --- a/templates/meeting_base_build +++ /dev/null @@ -1,24 +0,0 @@ -CALENDAR_FILTER="Build WG Meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="build" -AGENDA_TAG=build-agenda -GROUP_NAME="Build WorkGroup" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: -* For those who just want to watch: We stream our conference call straight to YouTube so anyone can listen to it live, it should start playing at when we turn it on. There's usually a short cat-herding time at the start of the meeting and then occasionally we have some quick private business to attend to before we can start recording & streaming. So be patient and it should show up. -* youtube admin page: - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure -" diff --git a/templates/meeting_base_bundler_collab b/templates/meeting_base_bundler_collab deleted file mode 100644 index 0b2c951..0000000 --- a/templates/meeting_base_bundler_collab +++ /dev/null @@ -1,23 +0,0 @@ -CALENDAR_FILTER="Bundler Working Group Monthly Meeting" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" -USER="openjs-foundation" -HOST="OpenJS Foundation" -REPO="bundler-collab-space" -GROUP_NAME="Bundler Collaboration Space" -AGENDA_TAG=bundler-agenda -HACKMD_TEAM_NAME="openjs" -JOINING_INSTRUCTIONS=" - -link for participants: Please refer to for meeting link - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure yet -" diff --git a/templates/meeting_base_cross_project_council b/templates/meeting_base_cross_project_council deleted file mode 100644 index cc7161a..0000000 --- a/templates/meeting_base_cross_project_council +++ /dev/null @@ -1,26 +0,0 @@ -CALENDAR_FILTER="Cross Project Council Meeting" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" -USER="openjs-foundation" -HOST="OpenJS Foundation" -REPO="cross-project-council" -GROUP_NAME="Cross Project Council" -AGENDA_TAG=cross-project-council-agenda -ISSUE_LABEL=cpc-meeting -HACKMD_TEAM_NAME="openjs" -JOINING_INSTRUCTIONS=" - -link for participants: Please refer to for meeting link - -* For those who just want to watch: - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure yet -" diff --git a/templates/meeting_base_diag b/templates/meeting_base_diag deleted file mode 100644 index eb92853..0000000 --- a/templates/meeting_base_diag +++ /dev/null @@ -1,12 +0,0 @@ -CALENDAR_FILTER="Diagnostics WG Meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="diagnostics" -GROUP_NAME="Diagnostics WorkGroup" -AGENDA_TAG=diag-agenda -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: -* For those who just want to watch: -* youtube admin page: " diff --git a/templates/meeting_base_diag_deepdive b/templates/meeting_base_diag_deepdive deleted file mode 100644 index 8c4b7ec..0000000 --- a/templates/meeting_base_diag_deepdive +++ /dev/null @@ -1,11 +0,0 @@ -CALENDAR_FILTER="Diagnostics Deep Dive Meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="diagnostics" -GROUP_NAME="Diagnostics Deep Dive" -AGENDA_TAG=diag-deepdive-agenda -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: -* For those who just want to watch: youtube admin page: " diff --git a/templates/meeting_base_ecosystem_report b/templates/meeting_base_ecosystem_report deleted file mode 100644 index b14d441..0000000 --- a/templates/meeting_base_ecosystem_report +++ /dev/null @@ -1,23 +0,0 @@ -CALENDAR_FILTER="Ecosystem Report Collab Space" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" -USER="openjs-foundation" -HOST="OpenJS Foundation" -REPO="ecosystem-report-collab-space" -GROUP_NAME="Ecosystem Report Collab Space" -AGENDA_TAG=ecosystem-report-agenda -HACKMD_TEAM_NAME="openjs" -JOINING_INSTRUCTIONS=" - -link for participants: - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure yet -" diff --git a/templates/meeting_base_loaders b/templates/meeting_base_loaders deleted file mode 100644 index 1d3780f..0000000 --- a/templates/meeting_base_loaders +++ /dev/null @@ -1,9 +0,0 @@ -CALENDAR_FILTER="Loaders Team Meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="loaders" -GROUP_NAME="Loaders Team" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS="* link for participants: - -* For those who just want to watch: " diff --git a/templates/meeting_base_modules b/templates/meeting_base_modules deleted file mode 100644 index c5063ff..0000000 --- a/templates/meeting_base_modules +++ /dev/null @@ -1,9 +0,0 @@ -CALENDAR_FILTER="Modules Team Meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="modules" -GROUP_NAME="Modules Team" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS="* link for participants: - -* For those who just want to watch: " diff --git a/templates/meeting_base_next-10 b/templates/meeting_base_next-10 deleted file mode 100644 index 695a60d..0000000 --- a/templates/meeting_base_next-10 +++ /dev/null @@ -1,12 +0,0 @@ -CALENDAR_FILTER="Node.js Next-10 Bi-Weekly Meeting" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" -USER="nodejs" -REPO="next-10" -GROUP_NAME="Next 10 Years team" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: - -* For those who just want to watch: We stream our conference call straight to YouTube so anyone can listen to it live, it should start playing at when we turn it on. There's usually a short cat-herding time at the start of the meeting and then occasionally we have some quick private business to attend to before we can start recording & streaming. So be patient and it should show up. -* youtube admin page: " diff --git a/templates/meeting_base_outreach b/templates/meeting_base_outreach deleted file mode 100644 index 9610261..0000000 --- a/templates/meeting_base_outreach +++ /dev/null @@ -1,11 +0,0 @@ -CALENDAR_FILTER="Node.js Outreach Meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="outreach" -GROUP_NAME="Outreach" -AGENDA_TAG=outreach-agenda -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* Link for participants: -* For those who just want to watch: " diff --git a/templates/meeting_base_package-maintenance b/templates/meeting_base_package-maintenance deleted file mode 100644 index 7c53749..0000000 --- a/templates/meeting_base_package-maintenance +++ /dev/null @@ -1,11 +0,0 @@ -CALENDAR_FILTER="Package Maintenance" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="package-maintenance" -GROUP_NAME="Package Maintenance Team" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: -* For those who just want to watch: -* youtube admin page: " diff --git a/templates/meeting_base_package_metadata_interop b/templates/meeting_base_package_metadata_interop deleted file mode 100644 index 46de997..0000000 --- a/templates/meeting_base_package_metadata_interop +++ /dev/null @@ -1,25 +0,0 @@ -CALENDAR_FILTER="Package Metadata Interoperability" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" -USER="openjs-foundation" -HOST="OpenJS Foundation" -REPO="package-metadata-interoperability-collab-space" -GROUP_NAME="Package Metadata Interoperability Collab Space" -AGENDA_TAG=package-metadata-agenda -HACKMD_TEAM_NAME="openjs" -JOINING_INSTRUCTIONS=" - -link for participants: Zoom link: <> - -* For those who just want to watch: - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure yet -" diff --git a/templates/meeting_base_security-wg b/templates/meeting_base_security-wg deleted file mode 100644 index 8a49bf7..0000000 --- a/templates/meeting_base_security-wg +++ /dev/null @@ -1,11 +0,0 @@ -CALENDAR_FILTER="Node.js Security Working Group Monthly Meeting" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" -USER="nodejs" -REPO="security-wg" -GROUP_NAME="Security team" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: -* For those who just want to watch We stream our conference call straight to YouTube so anyone can listen to it live, it should start playing at when we turn it on. There's usually a short cat-herding time at the start of the meeting and then occasionally we have some quick private business to attend to before we can start recording & streaming. So be patient and it should show up. -" diff --git a/templates/meeting_base_security_collab b/templates/meeting_base_security_collab deleted file mode 100644 index 0319ba3..0000000 --- a/templates/meeting_base_security_collab +++ /dev/null @@ -1,25 +0,0 @@ -CALENDAR_FILTER="Security Collab Space meeting" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" -USER="openjs-foundation" -HOST="OpenJS Foundation" -REPO="security-collab-space" -GROUP_NAME="Security Collab Space" -AGENDA_TAG=security-agenda -HACKMD_TEAM_NAME="openjs" -JOINING_INSTRUCTIONS=" - -link for participants: Zoom link: - -* For those who just want to watch: - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure yet -" diff --git a/templates/meeting_base_standards b/templates/meeting_base_standards deleted file mode 100644 index 2e738e6..0000000 --- a/templates/meeting_base_standards +++ /dev/null @@ -1,25 +0,0 @@ -CALENDAR_FILTER="Standards Working Group Meeting" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" -USER="openjs-foundation" -HOST="OpenJS Foundation" -REPO="standards" -GROUP_NAME="Standards Working Group" -AGENDA_TAG=standards-agenda -HACKMD_TEAM_NAME="openjs" -JOINING_INSTRUCTIONS=" - -link for participants: Zoom link: - -* For those who just want to watch: - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure yet -" diff --git a/templates/meeting_base_sustainability_collab b/templates/meeting_base_sustainability_collab deleted file mode 100644 index d23df06..0000000 --- a/templates/meeting_base_sustainability_collab +++ /dev/null @@ -1,23 +0,0 @@ -CALENDAR_FILTER="Sustainability Collaboration Space" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a0941000002wBygAAE" -USER="openjs-foundation" -HOST="OpenJS Foundation" -REPO="sustainability-collab-space" -GROUP_NAME="Sustainability Collaboration Space" -AGENDA_TAG=sustainability-agenda -HACKMD_TEAM_NAME="openjs" -JOINING_INSTRUCTIONS=" - -link for participants: - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure yet -" diff --git a/templates/meeting_base_test_runner b/templates/meeting_base_test_runner deleted file mode 100644 index c24933b..0000000 --- a/templates/meeting_base_test_runner +++ /dev/null @@ -1,19 +0,0 @@ -CALENDAR_FILTER="Node.js Test Runner" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" -USER="nodejs" -REPO="test-runner" -GROUP_NAME="Test Runner" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -Join URL: - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure -" diff --git a/templates/meeting_base_tooling b/templates/meeting_base_tooling deleted file mode 100644 index 844cde1..0000000 --- a/templates/meeting_base_tooling +++ /dev/null @@ -1,11 +0,0 @@ -CALENDAR_FILTER="Node.js Tooling" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="tooling" -GROUP_NAME="Tooling Group" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: -* For those who just want to watch: -* youtube admin page: " diff --git a/templates/meeting_base_tsc b/templates/meeting_base_tsc deleted file mode 100644 index a5225ba..0000000 --- a/templates/meeting_base_tsc +++ /dev/null @@ -1,28 +0,0 @@ -CALENDAR_FILTER="Node.js TSC Meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="TSC" -HOST="Node.js" -GROUP_NAME="Technical Steering Committee (TSC)" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -Zoom link: -Regular password - -## Public participation - -We stream our conference call straight to YouTube so anyone can listen to it live, it should start playing at **** when we turn it on. There's usually a short cat-herding time at the start of the meeting and then occasionally we have some quick private business to attend to before we can start recording & streaming. So be patient and it should show up. - - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure yet -" diff --git a/templates/meeting_base_typescript b/templates/meeting_base_typescript deleted file mode 100644 index 76477f5..0000000 --- a/templates/meeting_base_typescript +++ /dev/null @@ -1,23 +0,0 @@ -CALENDAR_FILTER="TypeScript team meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="typescript" -GROUP_NAME="TypeScript team" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS="https://zoom.us/j/95749675148 - -* link for participants: <> -* For those who just want to watch We stream our conference call straight to YouTube so anyone can listen to it live, it should start playing at when we turn it on. There's usually a short cat-herding time at the start of the meeting and then occasionally we have some quick private business to attend to before we can start recording & streaming. So be patient and it should show up. -* youtube admin page: - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure yet -" diff --git a/templates/meeting_base_userfeedback b/templates/meeting_base_userfeedback deleted file mode 100644 index b3999ee..0000000 --- a/templates/meeting_base_userfeedback +++ /dev/null @@ -1,16 +0,0 @@ -CALENDAR_FILTER="Node.js User Feedback Meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="user-feedback" -GROUP_NAME="User Feedback" -AGENDA_TAG=user-feedback-agenda -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: -* We stream our conference call straight to YouTube so anyone can listen to it live. - It should start playing at when we turn it on. - There's usually a short cat-herding time at the start of the meeting and then occasionally we - have some quick private business to attend to before we can start recording & streaming. - So be patient and it should show up. -* youtube admin page: " diff --git a/templates/meeting_base_uvwasi b/templates/meeting_base_uvwasi deleted file mode 100644 index 58d8410..0000000 --- a/templates/meeting_base_uvwasi +++ /dev/null @@ -1,12 +0,0 @@ -CALENDAR_FILTER="Node.js uvwasi team meeting" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="uvwasi" -GROUP_NAME="uvwasi team" -AGENDA_TAG=uvwasi-agenda -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: -* For those who just want to watch: -* youtube admin page: " diff --git a/templates/meeting_base_web b/templates/meeting_base_web deleted file mode 100644 index 4490188..0000000 --- a/templates/meeting_base_web +++ /dev/null @@ -1,22 +0,0 @@ -CALENDAR_FILTER="Web Team Monthly" -ICAL_URL="https://webcal.prod.itx.linuxfoundation.org/lfx/a092M00001IV4HSQA1" -USER="nodejs" -REPO="web-team" -AGENDA_TAG=web-agenda -GROUP_NAME="Web Team" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* Check LFX for your invite link. If you are having issues, reach out to @avivkeller. - ---- - -**Invitees** - -Please use the following emoji reactions in this post to indicate your -availability. - -* :+1: - Attending -* :-1: - Not attending -* :confused: - Not sure -" diff --git a/templates/meeting_base_web-server-frameworks b/templates/meeting_base_web-server-frameworks deleted file mode 100644 index 94d2e46..0000000 --- a/templates/meeting_base_web-server-frameworks +++ /dev/null @@ -1,12 +0,0 @@ -CALENDAR_FILTER="Web Server Frameworks" -ICAL_URL="https://calendar.google.com/calendar/ical/c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8%40group.calendar.google.com/public/basic.ics" -USER="nodejs" -REPO="web-server-frameworks" -GROUP_NAME="Web Server Frameworks" -HACKMD_TEAM_NAME="openjs-nodejs" -JOINING_INSTRUCTIONS=" - -* link for participants: - -* For those who just want to watch: We stream our conference call straight to YouTube so anyone can listen to it live, it should start playing at when we turn it on. There's usually a short cat-herding time at the start of the meeting and then occasionally we have some quick private business to attend to before we can start recording & streaming. So be patient and it should show up. -* youtube admin page: " diff --git a/templates/meeting_issue.md b/templates/meeting_issue.md deleted file mode 100644 index 5e1ad51..0000000 --- a/templates/meeting_issue.md +++ /dev/null @@ -1,38 +0,0 @@ -## Time - -**UTC $UTC_TIME$**: - -| Timezone | Date/Time | -| -------- | --------- | -$TIMEZONE_TABLE$ - -Or in your local time: - -* $TIME_AND_DATE_LINK$ -* or $WOLFRAM_ALPHA_LINK$ - -## Links - -* Minutes: <$MINUTES_DOC$> - -## Agenda - -Extracted from **$AGENDA_LABEL$** labelled issues and pull requests from the **$GITHUB_ORG$ org** prior to the meeting. (Updated daily) - -$AGENDA_CONTENT$ - -## Invited - -$INVITEES$ - -### Observers/Guests - -$OBSERVERS$ - -## Notes - -The agenda comes from issues labelled with `$AGENDA_LABEL$` across **all of the repositories in the $GITHUB_ORG$ org**. Please label any additional issues that should be on the agenda before the meeting starts. - -## Joining the meeting - -$JOINING_INSTRUCTIONS$ diff --git a/templates/minutes_base_Release b/templates/minutes_base_Release deleted file mode 100644 index 1b4cfaf..0000000 --- a/templates/minutes_base_Release +++ /dev/null @@ -1,28 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **Release-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_benchmarking b/templates/minutes_base_benchmarking deleted file mode 100644 index 1dc1c92..0000000 --- a/templates/minutes_base_benchmarking +++ /dev/null @@ -1,28 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **benchmarking-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_build b/templates/minutes_base_build deleted file mode 100644 index e0f6d88..0000000 --- a/templates/minutes_base_build +++ /dev/null @@ -1,28 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **build-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_bundler_collab b/templates/minutes_base_bundler_collab deleted file mode 100644 index 85517d0..0000000 --- a/templates/minutes_base_bundler_collab +++ /dev/null @@ -1,27 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -### Announcements - -*Extracted from **bundler-agenda** labeled issues and pull requests from the **openjs-foundation org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_cross_project_council b/templates/minutes_base_cross_project_council deleted file mode 100644 index 79dcd4c..0000000 --- a/templates/minutes_base_cross_project_council +++ /dev/null @@ -1,62 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -### Announcements - -### Board Meeting Updates - -- https://github.com/openjs-foundation/cross-project-council/labels/waiting-on-board - -### Staff Updates - -- https://github.com/openjs-foundation/cross-project-council/labels/waiting-on-legal-info -- https://github.com/openjs-foundation/cross-project-council/labels/waiting-on-staff-update -- https://github.com/openjs-foundation/cross-project-council/labels/waiting-on-website-update - -_Extracted from **cross-project-council-agenda** labeled issues and pull requests from the **openjs-foundation org** prior to the meeting._ - -$AGENDA_CONTENT$ - -### Next week's working session - -Are there any initiatives or agenda items that we should use a working session to further progress on? -- https://github.com/openjs-foundation/cross-project-council/labels/cpc-working-session - -### Regular reviews - -Please review regularly our list of dates and reminders, our quarterly review issues, and check the list of issues that can be closed: - -- https://github.com/openjs-foundation/cross-project-council/blob/main/Dates-and-Reminders.md -- https://github.com/openjs-foundation/cross-project-council/labels/cpc-quartely-review -- https://github.com/openjs-foundation/cross-project-council/labels/cpc-can-issue-be-closed - -### Working Group Updates - -- [ai](https://github.com/openjs-foundation/ai-collab-space) -- [bundler](https://github.com/openjs-foundation/bundler-collab-space) -- [ecosystem-report](https://github.com/openjs-foundation/ecosystem-report) -- [openvis](https://github.com/openjs-foundation/openvis-collab-space) -- [pkg-metadata-interop](https://github.com/openjs-foundation/package-metadata-interoperability-collab-space) -- [pkg-vuln](https://github.com/openjs-foundation/pkg-vuln-collab-space) -- [security](https://github.com/openjs-foundation/security-collab-space) -- [standards](https://github.com/openjs-foundation/standards) -- [sustainability](https://github.com/openjs-foundation/sustainability-collab-space) - -### Q&A, Other - -## Upcoming Meetings - -- **Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_diag b/templates/minutes_base_diag deleted file mode 100644 index 7a3f340..0000000 --- a/templates/minutes_base_diag +++ /dev/null @@ -1,28 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **diag-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_diag_deepdive b/templates/minutes_base_diag_deepdive deleted file mode 100644 index 34c294b..0000000 --- a/templates/minutes_base_diag_deepdive +++ /dev/null @@ -1,28 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **diag-deepdive-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_ecosystem_report b/templates/minutes_base_ecosystem_report deleted file mode 100644 index 280bbc7..0000000 --- a/templates/minutes_base_ecosystem_report +++ /dev/null @@ -1,24 +0,0 @@ -## Links - -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ - -## Agenda - -## Announcements - -*Extracted from **** labelled issues and pull requests from the **Ecosystem report collab space** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **OpenJS Foundation Calendar**: https://calendar.google.com/calendar/u/0/embed?src=linuxfoundation.org_fuop4ufv766f9avc517ujs4i0g@group.calendar.google.com - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_loaders b/templates/minutes_base_loaders deleted file mode 100644 index 3673ea9..0000000 --- a/templates/minutes_base_loaders +++ /dev/null @@ -1,18 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ - -## Present - -$INVITED$ - -## Agenda - -## Announcements - -*Extracted from **loaders-agenda** labeled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ diff --git a/templates/minutes_base_modules b/templates/minutes_base_modules deleted file mode 100644 index a44377c..0000000 --- a/templates/minutes_base_modules +++ /dev/null @@ -1,19 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ - -## Agenda - -## Announcements - -*Extracted from **modules-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ diff --git a/templates/minutes_base_next-10 b/templates/minutes_base_next-10 deleted file mode 100644 index 2fbd845..0000000 --- a/templates/minutes_base_next-10 +++ /dev/null @@ -1,27 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -* Extracted from **next10-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_outreach b/templates/minutes_base_outreach deleted file mode 100644 index 02b3fd4..0000000 --- a/templates/minutes_base_outreach +++ /dev/null @@ -1,29 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ -*Members and Observers: In order to facilitate attendance tracking, don't hesitate do add yourselves to the minutes doc* - -## Agenda - -## Announcements - -*Extracted from **outreach-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_package-maintenance b/templates/minutes_base_package-maintenance deleted file mode 100644 index 6711896..0000000 --- a/templates/minutes_base_package-maintenance +++ /dev/null @@ -1,32 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **package-maintenance-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Project Board Review (maybe, if time permits) - -Ask participants about the state of [Project Board](https://github.com/nodejs/package-maintenance/projects/1) and necessary changes if time permits. - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_package_metadata_interop b/templates/minutes_base_package_metadata_interop deleted file mode 100644 index 06796a9..0000000 --- a/templates/minutes_base_package_metadata_interop +++ /dev/null @@ -1,26 +0,0 @@ -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **** labelled issues and pull requests from the **package metadata interoperability collab space** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Foundation Calendar**: https://nodejs.org/calendar - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_security-wg b/templates/minutes_base_security-wg deleted file mode 100644 index 3161b36..0000000 --- a/templates/minutes_base_security-wg +++ /dev/null @@ -1,31 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **security-wg-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -- [ ] Vulnerability Review - https://github.com/nodejs/nodejs-dependency-vuln-assessments/issues -- [ ] OpenSSF Scorecard Monitor Review - https://github.com/nodejs/security-wg/issues?q=is%3Aissue+OpenSSF+Scorecard+Report+Updated%21+ - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_security_collab b/templates/minutes_base_security_collab deleted file mode 100644 index 9a17a99..0000000 --- a/templates/minutes_base_security_collab +++ /dev/null @@ -1,27 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -### Announcements - -*Extracted from **security-agenda** labeled issues and pull requests from the **openjs-foundation org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_standards b/templates/minutes_base_standards deleted file mode 100644 index 6ed2dae..0000000 --- a/templates/minutes_base_standards +++ /dev/null @@ -1,27 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -### Announcements - -*Extracted from **cross-project-council-agenda** labeled issues and pull requests from the **openjs-foundation org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_sustainability_collab b/templates/minutes_base_sustainability_collab deleted file mode 100644 index 26c9ac8..0000000 --- a/templates/minutes_base_sustainability_collab +++ /dev/null @@ -1,24 +0,0 @@ -## Links - -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ - -## Agenda - -## Announcements - -*Extracted from **** labelled issues and pull requests from the **Ecosystem report collab space** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **OpenJS Foundation Calendar**: https://calendar.google.com/calendar/u/0/embed?src=linuxfoundation.org_fuop4ufv766f9avc517ujs4i0g@group.calendar.google.com - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. \ No newline at end of file diff --git a/templates/minutes_base_tooling b/templates/minutes_base_tooling deleted file mode 100644 index bd1dbd9..0000000 --- a/templates/minutes_base_tooling +++ /dev/null @@ -1,28 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **tooling-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_tsc b/templates/minutes_base_tsc deleted file mode 100644 index faaeab0..0000000 --- a/templates/minutes_base_tsc +++ /dev/null @@ -1,34 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -### Announcements - -### Reminders - -* Remember to nominate people for the [contributor spotlight](https://github.com/nodejs/node/blob/main/doc/contributing/reconizing-contributors.md#bi-monthly-contributor-spotlight) - -### CPC and Board Meeting Updates - -*Extracted from **tsc-agenda** labeled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Strategic Initiatives - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_typescript b/templates/minutes_base_typescript deleted file mode 100644 index f921f8e..0000000 --- a/templates/minutes_base_typescript +++ /dev/null @@ -1,27 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -* Extracted from **typescript-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_userfeedback b/templates/minutes_base_userfeedback deleted file mode 100644 index afc8f2a..0000000 --- a/templates/minutes_base_userfeedback +++ /dev/null @@ -1,26 +0,0 @@ -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ -* **Minutes**: $MINUTES_DOC$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **user-feedback-meeting** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_uvwasi b/templates/minutes_base_uvwasi deleted file mode 100644 index f61e7de..0000000 --- a/templates/minutes_base_uvwasi +++ /dev/null @@ -1,27 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -*Extracted from **uvwasi-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_web b/templates/minutes_base_web deleted file mode 100644 index 00467bd..0000000 --- a/templates/minutes_base_web +++ /dev/null @@ -1,27 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -* Extracted from **web-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/minutes_base_web-server-frameworks b/templates/minutes_base_web-server-frameworks deleted file mode 100644 index 14fa35e..0000000 --- a/templates/minutes_base_web-server-frameworks +++ /dev/null @@ -1,27 +0,0 @@ -# $TITLE$ - -## Links - -* **Recording**: -* **GitHub Issue**: $GITHUB_ISSUE$ - -## Present - -$INVITED$ -$OBSERVERS$ - -## Agenda - -## Announcements - -* Extracted from **web-server-frameworks-agenda** labelled issues and pull requests from the **nodejs org** prior to the meeting. - -$AGENDA_CONTENT$ - -## Q&A, Other - -## Upcoming Meetings - -* **Node.js Project Calendar**: - -Click `Add to Google Calendar` at the bottom left to add to your own Google calendar. diff --git a/templates/observers_Release b/templates/observers_Release deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_benchmarking b/templates/observers_benchmarking deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_build b/templates/observers_build deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_bundler_collab b/templates/observers_bundler_collab deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_cross_project_council b/templates/observers_cross_project_council deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_diag b/templates/observers_diag deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_diag_deepdive b/templates/observers_diag_deepdive deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_ecosystem_report b/templates/observers_ecosystem_report deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_loaders b/templates/observers_loaders deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_modules b/templates/observers_modules deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_next-10 b/templates/observers_next-10 deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_outreach b/templates/observers_outreach deleted file mode 100644 index 891f3ca..0000000 --- a/templates/observers_outreach +++ /dev/null @@ -1,3 +0,0 @@ -Feel free to follow along on the YouTube live stream, or attend meeting as a guest -by calling in to Zoom, using the links below. If you will be attending as a guest, -please comment on this issue to let us know you'll be joining. diff --git a/templates/observers_package-maintenance b/templates/observers_package-maintenance deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_package_metadata_interop b/templates/observers_package_metadata_interop deleted file mode 100644 index 8b13789..0000000 --- a/templates/observers_package_metadata_interop +++ /dev/null @@ -1 +0,0 @@ - diff --git a/templates/observers_security-wg b/templates/observers_security-wg deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_security_collab b/templates/observers_security_collab deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_standards b/templates/observers_standards deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_sustainability_collab b/templates/observers_sustainability_collab deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_test_runner b/templates/observers_test_runner deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_tooling b/templates/observers_tooling deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_tsc b/templates/observers_tsc deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_typescript b/templates/observers_typescript deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_userfeedback b/templates/observers_userfeedback deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_uvwasi b/templates/observers_uvwasi deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_web b/templates/observers_web deleted file mode 100644 index e69de29..0000000 diff --git a/templates/observers_web-server-frameworks b/templates/observers_web-server-frameworks deleted file mode 100644 index e69de29..0000000 diff --git a/test/calendar.test.mjs b/test/calendar.test.mjs index 9ff5a2c..371fd1f 100644 --- a/test/calendar.test.mjs +++ b/test/calendar.test.mjs @@ -107,7 +107,7 @@ describe('calendar.mjs', () => { ]; const result = await calendar.findNextMeetingDate( events, - createMeetingConfig({ CALENDAR_FILTER: 'Triage' }) + createMeetingConfig({ calendar: { filter: 'Triage' } }) ); assert.strictEqual(result, mockDate); }); @@ -123,7 +123,7 @@ describe('calendar.mjs', () => { ]; const result = await calendar.findNextMeetingDate( events, - createMeetingConfig({ CALENDAR_FILTER: 'Meeting' }) + createMeetingConfig({ calendar: { filter: 'Meeting' } }) ); assert.strictEqual(result, date1); }); diff --git a/test/github.test.mjs b/test/github.test.mjs index fc02f5e..e4f56e4 100644 --- a/test/github.test.mjs +++ b/test/github.test.mjs @@ -17,12 +17,16 @@ describe('github.mjs', () => { createIssue(3, 'nodejs/nodejs.org'), ]); - assert.deepStrictEqual(Object.keys(result), [ - 'nodejs/node', - 'nodejs/nodejs.org', - ]); - assert.strictEqual(result['nodejs/node'].length, 2); - assert.strictEqual(result['nodejs/nodejs.org'].length, 1); + assert.deepStrictEqual( + result.map(group => group.repo), + ['nodejs/node', 'nodejs/nodejs.org'] + ); + assert.strictEqual(result[0].issues.length, 2); + assert.strictEqual(result[1].issues.length, 1); + }); + + it('should return an empty array for no issues', () => { + assert.deepStrictEqual(github.sortIssuesByRepo([]), []); }); }); @@ -50,35 +54,25 @@ describe('github.mjs', () => { ]); }); - it('should include issue label when provided', async () => { + it('should include issue labels when provided', async () => { const { client, create } = createMockClient(); await github.createGitHubIssue( client, - createMeetingConfig(), + createMeetingConfig({ github: { issueLabels: ['cpc-meeting'] } }), 'Title', 'Content' ); - assert.deepStrictEqual(create(), [ - [ - { - body: 'Content', - labels: ['meeting'], - owner: 'nodejs', - repo: 'node', - title: 'Title', - }, - ], - ]); + assert.deepStrictEqual(create()[0][0].labels, ['cpc-meeting']); }); - it('should not include labels when ISSUE_LABEL is not provided', async () => { + it('should not include labels when issueLabels is not provided', async () => { const { client, create } = createMockClient(); await github.createGitHubIssue( client, - createMeetingConfig({ ISSUE_LABEL: undefined }), + createMeetingConfig({ github: { issueLabels: undefined } }), 'Title', 'Content' ); @@ -96,27 +90,20 @@ describe('github.mjs', () => { ]); }); - it('should use default org when USER is not provided', async () => { + it('should use the configured owner and repo', async () => { const { client, create } = createMockClient(); await github.createGitHubIssue( client, - createMeetingConfig({ USER: undefined }), + createMeetingConfig({ + github: { owner: 'openjs-foundation', repo: 'standards' }, + }), 'Title', 'Content' ); - assert.deepStrictEqual(create(), [ - [ - { - body: 'Content', - labels: ['meeting'], - owner: 'nodejs', - repo: 'node', - title: 'Title', - }, - ], - ]); + assert.strictEqual(create()[0][0].owner, 'openjs-foundation'); + assert.strictEqual(create()[0][0].repo, 'standards'); }); }); @@ -173,46 +160,21 @@ describe('github.mjs', () => { assert.strictEqual(result, undefined); }); - it('should use default org when USER not provided', async () => { + it('should build the query from the configured owner and repo', async () => { const { client, request } = createMockClient(); await github.findGitHubIssueByTitle( client, 'Test', - createMeetingConfig({ USER: undefined }) + createMeetingConfig({ + github: { owner: 'openjs-foundation', repo: 'standards' }, + }) ); - assert.deepStrictEqual(request(), [ - [ - 'GET /search/issues', - { - advanced_search: true, - per_page: 1, - q: 'in:title repo:"nodejs/node" "Test"', - }, - ], - ]); - }); - - it('should search for open issues only', async () => { - const { client, request } = createMockClient(); - - await github.findGitHubIssueByTitle( - client, - 'Test', - createMeetingConfig() + assert.strictEqual( + request()[0][1].q, + 'in:title repo:"openjs-foundation/standards" "Test"' ); - - assert.deepStrictEqual(request(), [ - [ - 'GET /search/issues', - { - advanced_search: true, - per_page: 1, - q: 'in:title repo:"nodejs/node" "Test"', - }, - ], - ]); }); }); @@ -239,14 +201,16 @@ describe('github.mjs', () => { ]); }); - it('should use default org when USER not provided', async () => { + it('should use the configured owner and repo', async () => { const { client, update } = createMockClient(); await github.updateGitHubIssue( client, 1, 'Content', - createMeetingConfig({ USER: undefined }) + createMeetingConfig({ + github: { owner: 'openjs-foundation', repo: 'standards' }, + }) ); assert.deepStrictEqual(update(), [ @@ -254,8 +218,8 @@ describe('github.mjs', () => { { body: 'Content', issue_number: 1, - owner: 'nodejs', - repo: 'node', + owner: 'openjs-foundation', + repo: 'standards', }, ], ]); @@ -263,13 +227,12 @@ describe('github.mjs', () => { }); describe('getAgendaIssues', () => { - it('should search for issues with agenda label', async () => { + it('should search for issues with the agenda label', async () => { const { client, paginate } = createMockClient(); await github.getAgendaIssues( client, - { meetingGroup: 'tsc' }, - createMeetingConfig({ AGENDA_TAG: 'some-agenda' }) + createMeetingConfig({ github: { agendaLabel: 'some-agenda' } }) ); assert.deepStrictEqual(paginate(), [ @@ -283,33 +246,17 @@ describe('github.mjs', () => { ]); }); - it('should use default org when USER not provided', async () => { + it('should use the configured owner', async () => { const { client, paginate } = createMockClient(); await github.getAgendaIssues( client, - { meetingGroup: 'tsc' }, - createMeetingConfig({ USER: undefined, AGENDA_TAG: 'tsc-agenda' }) - ); - - assert.deepStrictEqual(paginate(), [ - [ - 'GET /search/issues', - { - advanced_search: true, - q: 'is:open label:tsc-agenda org:nodejs', + createMeetingConfig({ + github: { + owner: 'openjs-foundation', + agendaLabel: 'standards-agenda', }, - ], - ]); - }); - - it('should construct default agenda tag from meetingGroup when not provided', async () => { - const { client, paginate } = createMockClient(); - - await github.getAgendaIssues( - client, - { meetingGroup: 'tsc' }, - createMeetingConfig({ AGENDA_TAG: undefined }) + }) ); assert.deepStrictEqual(paginate(), [ @@ -317,13 +264,13 @@ describe('github.mjs', () => { 'GET /search/issues', { advanced_search: true, - q: 'is:open label:tsc-agenda org:nodejs', + q: 'is:open label:standards-agenda org:openjs-foundation', }, ], ]); }); - it('should return sorted issues by repo', async () => { + it('should return issues grouped by repo', async () => { const { client } = createMockClient({ paginate: [ createIssue(1, 'nodejs/node'), @@ -333,12 +280,13 @@ describe('github.mjs', () => { const result = await github.getAgendaIssues( client, - { meetingGroup: 'tsc' }, - createMeetingConfig({ AGENDA_TAG: 'tsc-agenda' }) + createMeetingConfig() ); - assert.ok(result['nodejs/node']); - assert.ok(result['nodejs/nodejs.org']); + assert.deepStrictEqual( + result.map(group => group.repo), + ['nodejs/node', 'nodejs/nodejs.org'] + ); }); }); @@ -418,25 +366,25 @@ describe('github.mjs', () => { assert.deepStrictEqual(update(), []); }); - it('should force update when force flag is true', async () => { + it('should create a new issue when forced', async () => { const existingIssue = { number: 1, title: 'Existing Issue', body: 'Same content', }; - const { client, update } = createMockClient({ + const { client, create } = createMockClient({ request: { items: [existingIssue] }, }); await github.createOrUpdateGitHubIssue( client, - { force: false }, + { force: true }, createMeetingConfig(), 'Existing Issue', 'Same content' ); - assert.deepStrictEqual(update(), []); + assert.strictEqual(create().length, 1); }); }); }); diff --git a/test/helpers.mjs b/test/helpers.mjs index a8ff5d0..db30a8c 100644 --- a/test/helpers.mjs +++ b/test/helpers.mjs @@ -58,18 +58,37 @@ export const createMockClient = (overrides = {}) => { }; }; -export const createMeetingConfig = (overrides = {}) => ({ - properties: { - USER: 'nodejs', - REPO: 'node', - ISSUE_LABEL: 'meeting', - CALENDAR_FILTER: 'Node.js', - ...overrides, +export const createMeetingConfig = ({ + github = {}, + calendar = {}, + ...overrides +} = {}) => ({ + group: 'node', + name: 'Node.js', + host: 'Node.js', + calendar: { + filter: 'Node.js', + url: 'https://example.com/calendar.ics', + ...calendar, }, + github: { + owner: 'nodejs', + repo: 'node', + agendaLabel: 'node-agenda', + issueLabels: ['meeting'], + ...github, + }, + hackmd: { team: 'openjs-nodejs' }, + joining: { participant: 'https://zoom.us/j/1' }, + invited: ['@nodejs/collaborators'], + observers: [], + agenda: [], + ...overrides, }); export const createIssue = (number, repoPath) => ({ number, title: `Issue ${number}`, + html_url: `https://github.com/${repoPath}/issues/${number}`, repository_url: `https://api.github.com/repos/${repoPath}`, }); diff --git a/test/meeting.test.mjs b/test/meeting.test.mjs index 830a165..f3db2a8 100644 --- a/test/meeting.test.mjs +++ b/test/meeting.test.mjs @@ -1,324 +1,485 @@ import assert from 'node:assert'; +import { readdir, readFile } from 'node:fs/promises'; import { describe, it } from 'node:test'; -import { DEFAULT_CONFIG } from '../src/constants.mjs'; +import { createMeetingConfig, createIssue } from './helpers.mjs'; +import { DEFAULT_HOST } from '../src/constants.mjs'; import * as meeting from '../src/meeting.mjs'; +const MEETINGS_DIR = new URL('../meetings/', import.meta.url); + +const groupNames = (await readdir(MEETINGS_DIR)) + .filter(name => name.endsWith('.meeting.json')) + .map(name => name.replace(/\.meeting\.json$/, '')); + describe('meeting.mjs', () => { - describe('generateMeetingTitle', () => { - const titleTestCases = [ - { - name: 'should generate title with host and group name', - config: { meetingGroup: 'tsc' }, - meetingConfig: { properties: { HOST: 'Node.js', GROUP_NAME: 'TSC' } }, - date: new Date('2025-01-15T10:30:00Z'), - expectations: ['Node.js', 'TSC', '2025-01-15'], - }, - { - name: 'should use default host when HOST not provided', - config: { meetingGroup: 'tsc' }, - meetingConfig: { properties: { GROUP_NAME: 'TSC' } }, - date: new Date('2025-01-15T10:30:00Z'), - expectations: [DEFAULT_CONFIG.host, 'TSC'], - }, - { - name: 'should use meetingGroup as GROUP_NAME when GROUP_NAME not provided', - config: { meetingGroup: 'tsc' }, - meetingConfig: { properties: { HOST: 'Node.js' } }, - date: new Date('2025-01-15T10:30:00Z'), - expectations: ['tsc'], - }, - { - name: 'should format date as YYYY-MM-DD', - config: { meetingGroup: 'tsc' }, - meetingConfig: { properties: {} }, - date: new Date('2025-06-15T10:30:00Z'), - expectations: ['2025-06-15'], - }, - { - name: 'should include "Meeting" text in title', - config: { meetingGroup: 'tsc' }, - meetingConfig: { properties: {} }, - date: new Date('2025-01-15T10:30:00Z'), - expectations: ['Meeting'], - }, - ]; + describe('load', () => { + it('should load a known config and attach its group', async () => { + const tsc = await meeting.load('tsc'); - titleTestCases.forEach( - ({ name, config, meetingConfig, date, expectations }) => { - it(name, () => { - const result = meeting.generateMeetingTitle( - config, - meetingConfig, - date - ); - expectations.forEach(expectation => { - assert( - result.includes(expectation), - `Expected "${expectation}" in "${result}"` - ); - }); - }); + assert.strictEqual(tsc.group, 'tsc'); + assert.strictEqual(tsc.name, 'Technical Steering Committee (TSC)'); + assert.strictEqual(tsc.github.owner, 'nodejs'); + assert.strictEqual(tsc.github.repo, 'TSC'); + }); + + it('should default the agenda label to -agenda', async () => { + const tsc = await meeting.load('tsc'); + assert.strictEqual(tsc.github.agendaLabel, 'tsc-agenda'); + }); + + it('should preserve an explicit agenda label', async () => { + const build = await meeting.load('build'); + assert.strictEqual(build.github.agendaLabel, 'build-agenda'); + + const web = await meeting.load('web'); + assert.strictEqual(web.github.agendaLabel, 'web-agenda'); + }); + + it('should default the host to Node.js', async () => { + const build = await meeting.load('build'); + assert.strictEqual(build.host, DEFAULT_HOST); + }); + + it('should preserve a non-default host', async () => { + const standards = await meeting.load('standards'); + assert.strictEqual(standards.host, 'OpenJS Foundation'); + }); + + it('should always expose observers and agenda arrays', async () => { + const tsc = await meeting.load('tsc'); + assert.ok(Array.isArray(tsc.observers)); + assert.ok(Array.isArray(tsc.agenda)); + }); + }); + + describe('every meeting config', () => { + it('should exist', () => { + assert.ok(groupNames.length > 0, 'expected at least one meeting config'); + }); + + groupNames.forEach(group => { + it(`${group} should follow the identical, valid format`, async () => { + const config = await meeting.load(group); + + assert.strictEqual(typeof config.name, 'string'); + assert.ok(config.name.length > 0); + assert.strictEqual(typeof config.host, 'string'); + + assert.strictEqual(typeof config.calendar.filter, 'string'); + assert.match(config.calendar.url, /^https?:\/\//); + + assert.strictEqual(typeof config.github.owner, 'string'); + assert.strictEqual(typeof config.github.repo, 'string'); + assert.strictEqual(typeof config.github.agendaLabel, 'string'); + + assert.strictEqual(typeof config.hackmd.team, 'string'); + + assert.ok(Array.isArray(config.invited)); + assert.ok(config.invited.length > 0); + assert.ok(Array.isArray(config.observers)); + assert.ok(Array.isArray(config.agenda)); + }); + }); + + it('should be valid JSON', async () => { + for (const group of groupNames) { + const raw = await readFile(meeting.configURL(group), 'utf8'); + assert.doesNotThrow( + () => JSON.parse(raw), + `${group} should be valid JSON` + ); } - ); + }); + }); - it('should generate consistent title for same inputs', () => { - const config = { meetingGroup: 'tsc' }; - const meetingConfig = { - properties: { HOST: 'Node.js', GROUP_NAME: 'TSC' }, - }; - const date = new Date('2025-01-15T10:30:00Z'); + describe('generateMeetingTitle', () => { + const date = new Date('2025-01-15T10:30:00Z'); - const result1 = meeting.generateMeetingTitle(config, meetingConfig, date); - const result2 = meeting.generateMeetingTitle(config, meetingConfig, date); + it('should combine host, name and date', () => { + const title = meeting.generateMeetingTitle( + createMeetingConfig({ host: 'Node.js', name: 'TSC' }), + date + ); + assert.strictEqual(title, 'Node.js TSC Meeting 2025-01-15'); + }); - assert.strictEqual(result1, result2); + it('should fall back to the group when name is missing', () => { + const title = meeting.generateMeetingTitle( + createMeetingConfig({ name: undefined, group: 'tsc' }), + date + ); + assert.ok(title.includes('tsc')); }); - const edgeCases = [ - { - name: 'should handle very long group names', - config: { meetingGroup: 'x'.repeat(100) }, - meetingConfig: { - properties: { HOST: 'Node.js', GROUP_NAME: 'y'.repeat(100) }, - }, - date: new Date('2025-01-15T10:30:00Z'), - check: result => result.includes('y'.repeat(100)), - }, - { - name: 'should handle special characters in group names', - config: { meetingGroup: 'tsc' }, - meetingConfig: { - properties: { HOST: 'Node.js', GROUP_NAME: 'TSC & CTC (merged)' }, - }, - date: new Date('2025-01-15T10:30:00Z'), - check: result => result.includes('TSC & CTC (merged)'), - }, - { - name: 'should handle dates at month boundaries', - config: { meetingGroup: 'tsc' }, - meetingConfig: { properties: {} }, - date: new Date('2025-01-31T23:59:59Z'), - check: result => result.includes('2025-01-31'), - }, - { - name: 'should handle leap year dates', - config: { meetingGroup: 'tsc' }, - meetingConfig: { properties: {} }, - date: new Date('2024-02-29T10:30:00Z'), - check: result => result.includes('2024-02-29'), - }, + it('should fall back to the default host when host is missing', () => { + const title = meeting.generateMeetingTitle( + createMeetingConfig({ host: undefined, name: 'TSC' }), + date + ); + assert.ok(title.startsWith(DEFAULT_HOST)); + }); + + it('should format the date as YYYY-MM-DD', () => { + const title = meeting.generateMeetingTitle( + createMeetingConfig(), + new Date('2025-06-15T23:59:59Z') + ); + assert.ok(title.includes('2025-06-15')); + }); + }); + + describe('createTemplateContext', () => { + const date = new Date('2025-01-15T10:30:00Z'); + + it('should expose timezone and link data', () => { + const ctx = meeting.createTemplateContext( + createMeetingConfig(), + date, + [], + [] + ); + + assert.ok(ctx.utc.length > 0); + assert.strictEqual(ctx.timezones.length, 12); + assert.ok(ctx.timeAndDateLink.startsWith('https://')); + assert.ok(ctx.wolframLink.startsWith('https://')); + assert.strictEqual(ctx.isMinutes, false); + }); + + it('should surface the agenda label and owner', () => { + const ctx = meeting.createTemplateContext( + createMeetingConfig({ github: { agendaLabel: 'tsc-agenda' } }), + date, + [], + [] + ); + + assert.strictEqual(ctx.agendaLabel, 'tsc-agenda'); + assert.strictEqual(ctx.owner, 'nodejs'); + }); + + it('should flag whether agenda issues are present', () => { + const empty = meeting.createTemplateContext( + createMeetingConfig(), + date, + [], + [] + ); + assert.strictEqual(empty.hasAgenda, false); + + const withIssues = meeting.createTemplateContext( + createMeetingConfig(), + date, + [{ repo: 'nodejs/node', issues: [createIssue(1, 'nodejs/node')] }], + [] + ); + assert.strictEqual(withIssues.hasAgenda, true); + }); + + it('should escape markdown brackets in issue titles', () => { + const ctx = meeting.createTemplateContext( + createMeetingConfig(), + date, + [ + { + repo: 'nodejs/node', + issues: [ + { + number: 1, + title: 'Issue with [brackets]', + html_url: 'https://github.com/nodejs/node/issues/1', + }, + ], + }, + ], + [] + ); + + assert.strictEqual( + ctx.agenda[0].issues[0].title, + 'Issue with \\[brackets\\]' + ); + }); + + it('should pass isMinutes and title through', () => { + const ctx = meeting.createTemplateContext( + createMeetingConfig(), + date, + [], + [], + { isMinutes: true, title: 'Node.js TSC Meeting 2025-01-15' } + ); + assert.strictEqual(ctx.isMinutes, true); + assert.strictEqual(ctx.title, 'Node.js TSC Meeting 2025-01-15'); + }); + + it('should default the calendar page from the host', () => { + const node = meeting.createTemplateContext( + createMeetingConfig({ host: 'Node.js' }), + date, + [], + [] + ); + assert.strictEqual(node.calendarPage, 'https://nodejs.org/calendar'); + + const openjs = meeting.createTemplateContext( + createMeetingConfig({ host: 'OpenJS Foundation' }), + date, + [], + [] + ); + assert.strictEqual(openjs.calendarPage, 'https://calendar.openjsf.org'); + }); + + it('should allow the calendar page to be overridden', () => { + const ctx = meeting.createTemplateContext( + createMeetingConfig({ calendar: { page: 'https://example.com/cal' } }), + date, + [], + [] + ); + assert.strictEqual(ctx.calendarPage, 'https://example.com/cal'); + }); + }); + + describe('resolveJoining', () => { + const sessions = [ + { time: '13:00', participant: 'https://zoom/13' }, + { time: '17:00', participant: 'https://zoom/17' }, ]; - edgeCases.forEach(({ name, config, meetingConfig, date, check }) => { - it(name, () => { - const result = meeting.generateMeetingTitle( - config, - meetingConfig, - date - ); - assert(check(result), `Check failed for "${name}"`); - }); + it('should return the single participant when there are no sessions', () => { + const result = meeting.resolveJoining( + createMeetingConfig({ joining: { participant: 'https://zoom/1' } }), + new Date('2025-01-15T10:30:00Z') + ); + assert.strictEqual(result.participant, 'https://zoom/1'); + assert.strictEqual(result.sessions, undefined); + }); + + it('should select the session matching the occurrence UTC time', () => { + const config = createMeetingConfig({ joining: { sessions } }); + + const at13 = meeting.resolveJoining( + config, + new Date('2026-06-24T13:00:00Z') + ); + assert.strictEqual(at13.participant, 'https://zoom/13'); + assert.strictEqual(at13.sessions, undefined); + + const at17 = meeting.resolveJoining( + config, + new Date('2026-07-01T17:00:00Z') + ); + assert.strictEqual(at17.participant, 'https://zoom/17'); + }); + + it('should list all sessions when none matches (e.g. dry-run)', () => { + const result = meeting.resolveJoining( + createMeetingConfig({ joining: { sessions } }), + new Date('2026-06-24T09:00:00Z') + ); + assert.strictEqual(result.participant, undefined); + assert.deepStrictEqual(result.sessions, sessions); }); }); - describe('generateMeetingAgenda', () => { - const agendaTestCases = [ - { - name: 'should format single repo with issues', - input: { - 'nodejs/node': [ - { - number: 1, - title: 'Issue 1', - html_url: 'https://github.com/nodejs/node/issues/1', - }, - { - number: 2, - title: 'Issue 2', - html_url: 'https://github.com/nodejs/node/issues/2', - }, - ], - }, - checks: ['nodejs/node', 'Issue 1', 'Issue 2', '#1', '#2'], - }, - { - name: 'should format multiple repos with issues', - input: { - 'nodejs/node': [ - { - number: 1, - title: 'Issue 1', - html_url: 'https://github.com/nodejs/node/issues/1', - }, - ], - 'nodejs/nodejs.org': [ - { - number: 2, - title: 'Issue 2', - html_url: 'https://github.com/nodejs/nodejs.org/issues/2', - }, - ], - }, - checks: ['nodejs/node', 'nodejs/nodejs.org', 'Issue 1', 'Issue 2'], - }, - { - name: 'should skip repos with no issues', - input: { - 'nodejs/node': [ - { - number: 1, - title: 'Issue 1', - html_url: 'https://github.com/nodejs/node/issues/1', - }, - ], - 'nodejs/empty': [], - }, - checks: ['nodejs/node'], - excludes: ['nodejs/empty'], - }, + describe('render', () => { + const date = new Date('2025-01-15T10:30:00Z'); + const agenda = [ { - name: 'should escape markdown special characters in issue titles', - input: { - 'nodejs/node': [ - { - number: 1, - title: 'Issue with [brackets] and stuff', - html_url: 'https://github.com/nodejs/node/issues/1', - }, - ], - }, - checks: ['\\[brackets\\]'], + repo: 'nodejs/node', + issues: [ + { + number: 42, + title: 'Discuss [streams]', + html_url: 'https://github.com/nodejs/node/issues/42', + }, + ], }, - { - name: 'should format as markdown list', - input: { - 'nodejs/node': [ - { - number: 1, - title: 'Issue 1', - html_url: 'https://github.com/nodejs/node/issues/1', - }, - ], + ]; + + it('should render an issue with invited, agenda and joining sections', async () => { + const config = createMeetingConfig({ + name: 'TSC', + invited: ['@nodejs/tsc'], + joining: { + participant: 'https://zoom.us/j/1', + observer: 'https://youtube.com/live', }, - checks: ['* ', '### nodejs/node'], - }, - { - name: 'should include issue links', - input: { - 'nodejs/node': [ - { - number: 1, - title: 'Issue 1', - html_url: 'https://github.com/nodejs/node/issues/1', - }, + }); + + const output = await meeting.render( + meeting.createTemplateContext(config, date, agenda, [ + { title: 'Minutes', url: 'https://hackmd.io/abc' }, + ]) + ); + + assert.ok(output.includes('## Time')); + assert.ok(output.includes('## Invited')); + assert.ok(output.includes('@nodejs/tsc')); + assert.ok(output.includes('Minutes: ')); + assert.ok(output.includes('### Issues and Pull Requests')); + assert.ok(output.includes('**node-agenda**')); + assert.ok(output.includes('#### nodejs/node')); + assert.ok(output.includes('Discuss \\[streams\\] [#42]')); + assert.ok(output.includes('## Joining the meeting')); + assert.ok(output.includes('To join the meeting: https://zoom.us/j/1')); + assert.ok(!output.includes('## Q&A, Other')); + assert.ok(!output.includes('**Recording**')); + }); + + it('should render the matching session link for an alternating meeting', async () => { + const config = createMeetingConfig({ + name: 'TSC', + joining: { + observer: 'https://youtube.com/live', + sessions: [ + { time: '13:00', participant: 'https://zoom/13' }, + { time: '17:00', participant: 'https://zoom/17' }, ], }, - checks: ['[#1]', '(https://github.com/nodejs/node/issues/1)'], - }, - { - name: 'should handle empty agenda', - input: {}, - isEmpty: true, - }, - { - name: 'should handle multiple issues in one repo', - input: { - 'nodejs/node': [ - { number: 1, title: 'First', html_url: 'https://example.com/1' }, - { number: 2, title: 'Second', html_url: 'https://example.com/2' }, - { number: 3, title: 'Third', html_url: 'https://example.com/3' }, + }); + + const output = await meeting.render( + meeting.createTemplateContext( + config, + new Date('2026-06-24T13:00:00Z'), + [], + [{ title: 'Minutes', url: 'https://hackmd.io/abc' }] + ) + ); + + assert.ok(output.includes('To join the meeting: https://zoom/13')); + assert.ok(!output.includes('https://zoom/17')); + }); + + it('should list all session links when none matches (dry-run)', async () => { + const config = createMeetingConfig({ + name: 'TSC', + joining: { + sessions: [ + { time: '13:00', participant: 'https://zoom/13' }, + { time: '17:00', participant: 'https://zoom/17' }, ], }, - lineCountMin: 4, - }, - { - name: 'should preserve issue title exactly', - input: { - 'nodejs/node': [ + }); + + const output = await meeting.render( + meeting.createTemplateContext( + config, + new Date('2026-06-24T09:00:00Z'), + [], + [{ title: 'Minutes', url: 'https://hackmd.io/abc' }] + ) + ); + + assert.ok( + output.includes('To join the 13:00 UTC meeting: https://zoom/13') + ); + assert.ok( + output.includes('To join the 17:00 UTC meeting: https://zoom/17') + ); + }); + + it('should render minutes with Present and Q&A sections', async () => { + const config = createMeetingConfig({ name: 'TSC' }); + + const output = await meeting.render( + meeting.createTemplateContext( + config, + date, + agenda, + [ + { title: 'Minutes', url: 'https://hackmd.io/abc' }, { - number: 123, - title: 'Add feature X to Node.js', - html_url: 'https://github.com/nodejs/node/issues/123', + title: 'GitHub Issue', + url: 'https://github.com/nodejs/node/issues/1', }, ], - }, - checks: ['Add feature X to Node.js'], - }, - { - name: 'should list issues in order', - input: { - 'nodejs/node': [ - { number: 1, title: 'First', html_url: 'https://example.com/1' }, - { number: 2, title: 'Second', html_url: 'https://example.com/2' }, - { number: 3, title: 'Third', html_url: 'https://example.com/3' }, - ], - }, - checkOrder: ['First', 'Second', 'Third'], - }, - ]; + { isMinutes: true, title: 'Node.js TSC Meeting 2025-01-15' } + ) + ); - agendaTestCases.forEach( - ({ - name, - input, - checks = [], - excludes = [], - isEmpty, - lineCountMin, - checkOrder, - }) => { - it(name, () => { - const result = meeting.generateMeetingAgenda(input); - - if (isEmpty) { - assert.strictEqual(result.trim(), ''); - } - - checks.forEach(check => { - assert(result.includes(check), `Expected "${check}" in result`); - }); - - excludes.forEach(exclude => { - assert( - !result.includes(exclude), - `Did not expect "${exclude}" in result` - ); - }); - - if (lineCountMin) { - const lines = result.split('\n'); - assert( - lines.length >= lineCountMin, - `Expected at least ${lineCountMin} lines, got ${lines.length}` - ); - } - - if (checkOrder) { - let lastIndex = -1; - checkOrder.forEach(item => { - const index = result.indexOf(item); - assert( - index > lastIndex, - `Expected "${item}" to appear after previous items` - ); - lastIndex = index; - }); - } - }); - } - ); + assert.ok(output.startsWith('# Node.js TSC Meeting 2025-01-15')); + assert.ok(output.includes('* **Recording**:')); + assert.ok(output.includes('## Present')); + assert.ok(output.includes('### Announcements')); + assert.ok(output.includes('## Q&A, Other')); + assert.ok(output.includes('## Upcoming Meetings')); + assert.ok(!output.includes('## Joining the meeting')); + }); - it('should trim whitespace from result', () => { - const agendaIssues = { - 'nodejs/node': [ - { number: 1, title: 'Issue', html_url: 'https://example.com/1' }, + it('should render curated agenda sections only in the minutes', async () => { + const config = createMeetingConfig({ + name: 'CPC', + host: 'OpenJS Foundation', + agenda: [ + { + title: 'Working Group Updates', + description: 'Standing review of the collab spaces:', + items: [ + '[security](https://github.com/openjs-foundation/security-collab-space)', + ], + }, ], - }; + }); - const result = meeting.generateMeetingAgenda(agendaIssues); + const minutes = await meeting.render( + meeting.createTemplateContext(config, date, [], [], { + isMinutes: true, + title: 'OpenJS CPC Meeting 2025-01-15', + }) + ); + assert.ok(minutes.includes('### Working Group Updates')); + assert.ok(minutes.includes('Standing review of the collab spaces:')); + assert.ok( + minutes.includes( + '[security](https://github.com/openjs-foundation/security-collab-space)' + ) + ); + // OpenJS host uses the OpenJS public calendar. + assert.ok(minutes.includes('')); - assert.strictEqual(result, result.trim()); + const issue = await meeting.render( + meeting.createTemplateContext(config, date, [], []) + ); + assert.ok(!issue.includes('### Working Group Updates')); + }); + + it('should show a fallback when there are no agenda items', async () => { + const output = await meeting.render( + meeting.createTemplateContext(createMeetingConfig(), date, [], []) + ); + + assert.ok(output.includes('_No agenda items found._')); + }); + + it('should render every real meeting config without throwing', async () => { + for (const group of groupNames) { + const config = await meeting.load(group); + const output = await meeting.render( + meeting.createTemplateContext( + config, + date, + [], + [{ title: 'Minutes', url: 'https://hackmd.io/x' }] + ) + ); + assert.ok( + output.includes('## Time'), + `${group} should render a header` + ); + assert.ok( + output.includes('## Invited'), + `${group} should render invited` + ); + assert.ok( + output.includes(`**${config.github.agendaLabel}**`), + `${group} should reference its agenda label` + ); + } }); }); }); diff --git a/test/utils/templates.test.mjs b/test/utils/templates.test.mjs deleted file mode 100644 index 1022866..0000000 --- a/test/utils/templates.test.mjs +++ /dev/null @@ -1,227 +0,0 @@ -import assert from 'node:assert'; -import { describe, it } from 'node:test'; - -import * as templates from '../../src/utils/templates.mjs'; - -describe('templates utility', () => { - describe('parseVariables', () => { - it('should replace a single variable placeholder', () => { - const template = 'Hello $NAME$!'; - const variables = { NAME: 'World' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Hello World!'); - }); - - it('should replace multiple variable placeholders', () => { - const template = 'Hello $FIRST_NAME$ $LAST_NAME$!'; - const variables = { FIRST_NAME: 'John', LAST_NAME: 'Doe' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Hello John Doe!'); - }); - - it('should replace the same variable multiple times', () => { - const template = '$NAME$ says hello to $NAME$'; - const variables = { NAME: 'Alice' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Alice says hello to Alice'); - }); - - it('should handle variables with underscores in the name', () => { - const template = 'Value: $MEETING_DATE$'; - const variables = { MEETING_DATE: '2025-01-15' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Value: 2025-01-15'); - }); - - it('should replace remaining unmatched placeholders with empty strings', () => { - const template = 'Hello $NAME$! Welcome $UNKNOWN_VAR$!'; - const variables = { NAME: 'Alice' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Hello Alice! Welcome !'); - }); - - it('should handle empty template', () => { - const template = ''; - const variables = { NAME: 'World' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, ''); - }); - - it('should handle template with no variables', () => { - const template = 'Hello World!'; - const variables = { NAME: 'Alice' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Hello World!'); - }); - - it('should handle empty variables object', () => { - const template = 'Hello $NAME$!'; - const variables = {}; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Hello !'); - }); - - it('should handle special characters in variable values', () => { - const template = 'Command: $CMD$'; - const variables = { CMD: 'grep "test" file.txt' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Command: grep "test" file.txt'); - }); - - it('should handle newlines in variable values', () => { - const template = 'Content:\n$CONTENT$\nEnd'; - const variables = { CONTENT: 'Line 1\nLine 2' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Content:\nLine 1\nLine 2\nEnd'); - }); - - it('should handle multiple placeholders on the same line', () => { - const template = '$USER$ at $TIME$ on $DATE$'; - const variables = { USER: 'John', TIME: '10:30 AM', DATE: '2025-01-15' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'John at 10:30 AM on 2025-01-15'); - }); - - it('should not match partial placeholders', () => { - const template = '$NAM is not $NAME$'; - const variables = { NAM: 'test', NAME: 'Alice' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, '$NAM is not Alice'); - }); - - it('should handle variables with numeric and alphabetic characters', () => { - const template = '$VAR1$ and $VAR_2$'; - const variables = { VAR1: 'value1', VAR_2: 'value2' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'value1 and value2'); - }); - - it('should handle empty string as variable value', () => { - const template = 'Start$EMPTY$End'; - const variables = { EMPTY: '' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'StartEnd'); - }); - - it('should handle null-like string values', () => { - const template = 'Value: $VAL$'; - const variables = { VAL: 'null' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Value: null'); - }); - - it('should handle complex template with markdown', () => { - const template = `# Meeting: $TITLE$ - -Date: $DATE$ -Time: $TIME$ - -## Agenda -$AGENDA$`; - - const variables = { - TITLE: 'Node.js TSC', - DATE: '2025-01-15', - TIME: '10:30 UTC', - AGENDA: '* Item 1\n* Item 2', - }; - - const result = templates.parseVariables(template, variables); - - assert(result.includes('# Meeting: Node.js TSC')); - assert(result.includes('Date: 2025-01-15')); - assert(result.includes('Time: 10:30 UTC')); - assert(result.includes('* Item 1\n* Item 2')); - }); - - it('should handle very long variable values', () => { - const longContent = 'x'.repeat(10000); - const template = 'Content: $CONTENT$'; - const variables = { CONTENT: longContent }; - const result = templates.parseVariables(template, variables); - - assert(result.includes(longContent)); - }); - - it('should be case-sensitive for variable names', () => { - const template = '$name$ and $NAME$'; - const variables = { name: 'lowercase', NAME: 'UPPERCASE' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'lowercase and UPPERCASE'); - }); - - it('should handle variables with similar names', () => { - const template = '$VAR$ $VAR_NAME$ $VAR_NAME_LONG$'; - const variables = { - VAR: 'a', - VAR_NAME: 'b', - VAR_NAME_LONG: 'c', - }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'a b c'); - }); - - it('should process regex special characters in placeholder correctly', () => { - const template = 'Value: $SPECIAL_CHARS$'; - const variables = { SPECIAL_CHARS: '.*+?^${}()|[\\]' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Value: .*+?^${}()|[\\]'); - }); - - it('should replace all instances of a variable', () => { - const template = '$X$ $X$ $X$'; - const variables = { X: 'Y' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Y Y Y'); - }); - - it('should handle undefined variables as empty string', () => { - const template = 'Value: $UNDEFINED$'; - const variables = {}; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Value: '); - }); - - it('should handle template with only a variable', () => { - const template = '$CONTENT$'; - const variables = { CONTENT: 'Full content' }; - const result = templates.parseVariables(template, variables); - - assert.strictEqual(result, 'Full content'); - }); - - it('should replace variables at line boundaries', () => { - const template = `Line1: $VAR1$ -Line2: $VAR2$ -Line3: $VAR3$`; - - const variables = { VAR1: 'A', VAR2: 'B', VAR3: 'C' }; - const result = templates.parseVariables(template, variables); - - const lines = result.split('\n'); - assert.strictEqual(lines[0], 'Line1: A'); - assert.strictEqual(lines[1], 'Line2: B'); - assert.strictEqual(lines[2], 'Line3: C'); - }); - }); -});